Appearance
Bezier curves
This example was inspired by Freya Holmér's excellent video on Bézier curves
t =
Code
vue
<script setup lang="ts">
import { Mafs, Cartesian, Parametric, Segment, Point, MovablePoint, useMovablePoint, useStopwatch, Theme, vec } from 'mafs-vue'
import { easeInOutCubic } from "js-easing-functions"
import { computed, ref, watch, onMounted } from 'vue'
function xyFromBernsteinPolynomial(
p1: vec.Vector2,
c1: vec.Vector2,
c2: vec.Vector2,
p2: vec.Vector2,
t: number
) {
return [
vec.scale(p1, -(t ** 3) + 3 * t ** 2 - 3 * t + 1),
vec.scale(c1, 3 * t ** 3 - 6 * t ** 2 + 3 * t),
vec.scale(c2, -3 * t ** 3 + 3 * t ** 2),
vec.scale(p2, t ** 3),
].reduce(vec.add, [0, 0])
}
function inPairs<T>(arr: T[]) {
const pairs: [T, T][] = []
for (let i = 0; i < arr.length - 1; i++) {
pairs.push([arr[i], arr[i + 1]])
}
return pairs
}
const t = ref(0.5)
const opacity = computed(() => 1 - (2 * t.value - 1) ** 6)
const p1 = useMovablePoint([-5, 2])
const p2 = useMovablePoint([5, -2])
const c1 = useMovablePoint([-2, -3])
const c2 = useMovablePoint([2, 3])
const lerp1 = computed<vec.Vector2>(() => vec.lerp(p1.point, c1.point, t.value))
const lerp2 = computed<vec.Vector2>(() => vec.lerp(c1.point, c2.point, t.value))
const lerp3 = computed<vec.Vector2>(() => vec.lerp(c2.point, p2.point, t.value))
const lerp12 = computed<vec.Vector2>(() => vec.lerp(lerp1.value, lerp2.value, t.value))
const lerp23 = computed<vec.Vector2>(() => vec.lerp(lerp2.value, lerp3.value, t.value))
const lerpBezier = computed<vec.Vector2>(() => vec.lerp(lerp12.value, lerp23.value, t.value))
const pointsControlline = computed<vec.Vector2[]>(() => [p1.point, c1.point, c2.point, p2.point])
const pointsFirstOrder = computed<vec.Vector2[]>(() => [lerp1.value, lerp2.value, lerp3.value])
const pointsSecondOrder = computed<vec.Vector2[]>(() => [lerp12.value, lerp23.value])
const duration = 2
const { time, start } = useStopwatch({
endTime: duration,
})
onMounted(() => {
setTimeout(() => start(), 500)
})
watch(time, () => {
t.value = easeInOutCubic(time.value, 0, 0.75, duration)
},
{
immediate: true
})
</script>
<template>
<div>
<Mafs :viewBox="{ x: [-5, 5], y: [-4, 4] }">
<Cartesian :xAxis="{ axis: false, label: false }" :yAxis="{ axis: false, label: false }" />
<Segment v-for="([p1, p2], index) in inPairs(pointsControlline)" :key="index" :point1="p1" :point2="p2"
:stroked="{ opacity: 0.5, color: Theme.pink }" />
<Segment v-for="([p1, p2], index) in inPairs(pointsFirstOrder)" :key="index" :point1="p1" :point2="p2"
:stroked="{ opacity: opacity * 0.5, color: Theme.red }" />
<Point v-for="([x, y], index) in pointsFirstOrder" :key="index" :x="x" :y="y" :opacity="opacity"
:color="Theme.red" />
<Segment v-for="([p1, p2], index) in inPairs(pointsSecondOrder)" :key="index" :point1="p1" :point2="p2"
:stroked="{ opacity: opacity * 0.5, color: Theme.yellow }" />
<Point v-for="([x, y], index) in pointsSecondOrder" :key="index" :x="x" :y="y" :opacity="opacity"
:color="Theme.yellow" />
<Point v-for="([x, y], index) in [lerpBezier]" :key="index" :x="x" :y="y" :opacity="opacity"
:color="Theme.foreground" />
<Parametric :t="[0, t]" :xy="(t) => xyFromBernsteinPolynomial(p1.point, c1.point, c2.point, p2.point, t)"
:stroked="{ weight: 3 }" />
<Parametric :t="[1, t]" :xy="(t) => xyFromBernsteinPolynomial(p1.point, c1.point, c2.point, p2.point, t)"
:stroked="{ weight: 3, opacity: 0.5, strokeStyle: 'dashed' }" />
<MovablePoint :ctx="p1" />
<MovablePoint :ctx="p2" />
<MovablePoint :ctx="c1" />
<MovablePoint :ctx="c2" />
</Mafs>
<div class="range">
<span class="font-display">t =</span>{{ " " }}
<input type="range" :min="0" :max="1" :step="0.005" v-model="t" />
</div>
</div>
</template>
<style scoped>
.range {
padding: 1rem;
border-color: #374151;
background-color: black;
color: white;
}
</style>