canvas = {
const width = 800
const height = 800
const px_per_unit = 1
const origo = [ 0, 0 ]
const map_coordinate = (coordinate, i) => origo[i] + coordinate * px_per_unit
const p0 = [ 50, 200 ].map(map_coordinate)
const p1 = [ 400, 600 ].map(map_coordinate)
const p2 = [ 750, 200 ].map(map_coordinate)
const scale = Math.max(window.devicePixelRatio, 2)
const ctx = DOM.context2d(width, height)
ctx.canvas.style.width = `${width}px`
ctx.canvas.style.height = `${height}px`
ctx.canvas.width = width * scale
ctx.canvas.height = height * scale
ctx.scale(scale, scale)
const circle_radius = 3
const resolution = 1000
const steps = 9
const step = 1 / (steps + 1)
ctx.beginPath()
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.strokeStyle = '#aaa'
ctx.stroke()
const level_points = [[], []]
ctx.strokeStyle = '#aaa'
ctx.beginPath()
for (let i = 0; i < steps; i++) {
const t = (i + 1) * step
const levels = levels_de_cateljau(t, [ p0, p1, p2 ])
for (let i = 0; i < levels.length; i++) {
const level = levels[i]
level_points[i].push(...level)
for (let j = 0; j < level.length; j++) {
const p = level[j]
if (i < level.length - 1) {
ctx.beginPath()
ctx.moveTo(p[0], p[1])
ctx.lineTo(level[i + 1][0], level[i + 1][1])
ctx.stroke()
}
}
}
}
for (let i = 0; i <= resolution; i++) {
const t = i / resolution
const x = coordinate_de_cateljau(t, [ p0[0], p1[0], p2[0] ])
const y = coordinate_de_cateljau(t, [ p0[1], p1[1], p2[1] ])
ctx[i ? 'lineTo' : 'moveTo'](x, y)
}
ctx.lineWidth = 1
ctx.strokeStyle = '#000'
ctx.stroke()
for (let i = 0; i < level_points.length; i++) {
const points = level_points[i]
for (const p of points) {
circle(ctx, p[0], p[1], circle_radius, i === level_points.length - 1 ? 'red' : 'blue', 'black')
}
}
circle(ctx, p0[0], p0[1], circle_radius, 'red' , 'black')
circle(ctx, p1[0], p1[1], circle_radius, 'yellow' , 'black')
circle(ctx, p2[0], p2[1], circle_radius, 'red' , 'black')
return ctx.canvas
}