diff --git a/src/content/tutorials/en/intro-to-p5.spline.mdx b/src/content/tutorials/en/intro-to-p5.spline.mdx new file mode 100644 index 0000000000..cacd93029b --- /dev/null +++ b/src/content/tutorials/en/intro-to-p5.spline.mdx @@ -0,0 +1,527 @@ +--- +title: "Splines in p5.js: Curves, Sampling, and Shapes" +description: Learn the full spline toolkit in p5.js, draw smooth curves, sample points and tangents, tune curvature, and build flowing shapes in 2D and 3D. +category: "2.0" +categoryIndex: 0 +featuredImage: ../images/featured/Intro-to-splines.png +featuredImageAlt: ‘Intro to Splines’ title showing three colorful spline curves, labeled control points (p0-p3), and a tangent arrow. +relatedContent: + references: + - en/p5/spline + - en/p5/splinepoint + - en/p5/splinetangent + - en/p5/splineproperty + - en/p5/splinevertex + +authors: + - Perminder Singh +--- + +import EditableSketch from "../../../components/EditableSketch/index.astro"; +import Callout from "../../../components/Callout/index.astro"; + +## Introduction + +Splines are smooth curves that interpolate through a series of points, +forming gentle, flowing shapes. Splines are a mathematical means of representing +a curve, by specifying a series of points at intervals along the curve and defining +a function that allows additional points within an interval to be calculated. +This makes it easy to draw natural-looking curves without manually specifying tangents. + +In this tutorial, we will start with the basics of drawing spline curves, +then move on to sampling points and tangents along a spline, and finally +explore creating custom shapes with splines and advanced controls like +tension and closed loops. + + +## Getting Started with Splines + +Splines are a mathematical means of representing a curve, by +specifying a series of points at intervals along the curve and defining +a function that allows additional points within an interval to be calculated. + +The simplest way to draw a spline curve in p5.js is by using the spline() function. +This function connects four points with a smooth Catmull-Rom curve. You call it with +four pairs of coordinates: + +```js +spline(x1, y1, x2, y2, x3, y3, x4, y4); +``` + +// TODO: Image + +Each of the four points (p0, p1, p2, p3) contributes to the shape of the curve. +By default, the spline will pass directly through all four points in the order +given. In other words, spline(x1,y1, x2,y2, x3,y3, x4,y4) will produce a single +continuous curve that smoothly interpolates through p0 → p1 → p2 → p3. You don’t +need to specify any additional control handles – the library computes the curve for you. + + + +In this code, `spline()` draws a black curve passing through the four red points. + +How it works: Catmull-Rom splines ensure the curve goes through every interior point. +By default, p5.js treats the first and last points as part of the curve as well +(so the curve starts at p0 and ends at p3). This default ends: "INCLUDE" mode +produces a curve that covers the entire span of points provided. The tension of the +curve can be adjusted (we’ll get to that in the advanced section), but with default +settings the curve is fairly even and smooth. + +Optional endpoint behavior: There is also an alternative mode where the endpoints +act only as guides (controls) and the curve is drawn only between the inner points. +If you set splineProperty('ends', EXCLUDE), then p0 and p3 are not on the curve – +they act like control handles similar to how curve() worked in p5.js 1.x. In this +EXCLUDE mode, the drawn curve will start at p1 and end at p2, using p0 and p3 to +shape the tangents at the ends. For example, with ends: EXCLUDE, the earlier code +would only draw the segment between the second and third red points, rather than +the full S-shape. Most of the time, beginners can stick with the default INCLUDE +behavior (which draws the curve through all points). The EXCLUDE option is useful +in more advanced scenarios, like smoothly connecting multiple spline segments without +needing to duplicate endpoints. + + + +Note: The spline() function always expects exactly four points (eight parameters for 2D). +If you have more points to connect, you have two choices: call spline() multiple times +for different segments, or use the splineVertex() approach (covered in the advanced section) +to string many points into one continuous shape. Also, splines work in 3D mode as well – +there is an overloaded spline() that takes twelve arguments (x, y, z for four points) to draw +a 3D Catmull-Rom curve, but in this beginner section we focus on 2D usage. + +Quick peek: + +```js +spline( + x1, y1, z1, + x2, y2, z2, + x3, y3, z3, + x4, y4, z4 +); +``` + +Splines vs. Bezier Curves + +`bezier()` uses a start point, an end point, and extra control points that pull the curve but are not on the curve. + +`spline()` makes a curve that passes through all the points you give. + +Use splines when the curve must go through specific coordinates. +Use Béziers when you want to shape the curve with off-curve handles. + +## Sampling a Spline Curve + +Drawing a curve is great, but sometimes you need to know where a point lies along that curve or the direction of the curve at a certain position. This is where sampling functions come in. p5.js provides two useful functions for Catmull-Rom splines: splinePoint() to get coordinates along the curve and splineTangent() to get the slope (tangent vector) of the curve at a point. These functions are analogous to bezierPoint() and bezierTangent(), but for splines. + +### Using splinePoint() to Interpolate Along the Curve + +The function splinePoint(a, b, c, d, t) returns the coordinate of a point on the spline at a relative position t between the middle points. Here a, b, c, d are four numbers representing one coordinate (all x’s or all y’s of the four spline points), and t ranges from 0 to 1. Essentially, splinePoint() works one axis at a time: you call it once for x and once for y to get an (x,y) point on the curve. The parameter t specifies how far along the curve segment between the second point (p1) and third point (p2) you are: t = 0 corresponds exactly to p1, t = 1 corresponds to p2, and t = 0.5 is the midpoint of the curve between p1 and p2. + +For example: + + + +In the above sketch, we have drawn three points on the curve through `splinePoint()`. In the very first point we have `t = 0` into the splinePoint() which basically represents the p1 of the curve. In the second half we have t=0.5 which basically represents the midway of the curve from p1->p2, and at last we have t = 1 as the curve which means the point p2 in the curve. By varying t from 0 to 1, you could trace the entire curved segment from p1 to p2. + +A typical use of splinePoint() is to animate objects moving along a path. For instance, you could increment t over time and draw an object (like a small circle) at (splinePoint(x0,x1,x2,x3,t), splinePoint(y0,y1,y2,y3,t)) each frame – the object would glide smoothly along the spline segment from the second point to the third. If your spline passes through more than four points (e.g., using splineVertex or multiple segments), you can apply splinePoint piecewise to each segment. + + + +Important: Always supply the points to splinePoint in the same order that you would to spline() – i.e. the four coordinates should correspond to p0, p1, p2, p3 along the curve. The function uses Catmull-Rom interpolation internally with those four values. If you mix up the order, the results will not match your drawn curve. + +Also note that splinePoint (like curvePoint before it) is designed for the segment between p1 and p2. If your spline is using ends: INCLUDE (default) and you want to sample points from the very beginning of the drawn curve (near p0) or the very end (near p3), those portions lie outside the p1→p2 span. In such cases, you might need to extrapolate slightly (t < 0 or t > 1) or simply handle the curve in two segments. For simplicity, many applications focus on the main span (p1 to p2) or ensure that for multi-point curves, they sample one segment at a time. + +### Using splineTangent() to Get the Curve’s Slope + +The function splineTangent(a, b, c, d, t) works similarly, but returns the components of the tangent vector to the spline at a given position. The tangent is a line that “skims” the curve at a single point – its slope equals the curve’s slope at that point. In practical terms, the tangent vector indicates the direction in which the curve is heading at that point. + +`splineTangent` also takes four coordinates (all x or all y) and a parameter t (again 0 to 1 from p1 to p2). It returns a number which is essentially the derivative along that axis at the point. To get the full 2D tangent vector, call it once for x and once for y: + +```js +let tx = splineTangent(x0, x1, x2, x3, t); +let ty = splineTangent(y0, y1, y2, y3, t); +``` + +Now (tx, ty) is a vector pointing in the direction of the curve’s tangent at the point corresponding to parameter t. For example, if you wanted to draw the tangent line at that point, you could draw a line starting at the point (midX, midY) we found earlier and extending a short distance in the direction of (tx, ty): + +Keep in mind that the values returned by splineTangent are not normalized – they represent the actual rate of change of the curve coordinates. A Catmull-Rom spline’s tangent magnitude will vary depending on the spacing of the points. If you need just the direction, you might normalize the (tx, ty) vector. The key point is that splineTangent provides a way to get the instantaneous direction of the spline at any point along the segment. + +Using splinePoint() and splineTangent(), you can retrieve fine-grained information from your spline curves. splinePoint gives you precise coordinates along the curve (useful for plotting points, collision detection, etc.), and splineTangent gives you the curve’s slope (useful for determining orientation or constructing perpendiculars/normals). These functions make Catmull-Rom splines not just pretty to look at, but also practical for guiding motion and shaping geometry in your sketches. + +### Advanced Spline Shapes and Customization + +So far we've been drawing a single curve segment defined by four points. Now, let's explore more advanced possibilities: creating complex shapes from multiple spline segments, closing splines into continuous loops, and customizing the spline’s properties (like its tightness or endpoint handling) to achieve different effects. + +The function splineVertex() allows you to build a custom shape out of spline curves by specifying an arbitrary number of vertices. It works in conjunction with beginShape() and endShape(), similar to how one would use vertex() or curveVertex(). Each call to splineVertex(x, y) adds a point through which the smooth curve will pass. p5.js will then draw a continuous Catmull-Rom spline through all the points you added in order. + +Basic Usage: + +```js +beginShape(); +splineVertex(x0, y0); +splineVertex(x1, y1); +splineVertex(x2, y2); +// ... add as many as you want +splineVertex(xN, yN); +endShape(); +``` + +Let’s illustrate with a concrete example. Suppose we want to draw a wavy shape through five points: + +```js +beginShape(); +splineVertex(50, 200); // p0 +splineVertex(150, 100); // p1 +splineVertex(250, 220); // p2 +splineVertex(350, 80); // p3 +splineVertex(450, 200); // p4 +endShape(); +``` + +This will produce a single continuous spline curve that starts at (50,200), then flows through (150,100), (250,220), (350,80), and ends at (450,200), creating a smooth wave-like line through all five points. Under the hood, it is essentially drawing a series of Catmull-Rom segments that connect each pair of consecutive points while maintaining overall smoothness. + + + +Closed spline loops: You can create closed shapes by passing the constant CLOSE to endShape(). If you use endShape(CLOSE) after adding spline vertices, p5.js will connect the last point back to the first point with a smooth curve, closing the loop seamlessly. This is a powerful feature – it means you can make organic closed shapes (like blobs, loops, rounded polygons, etc.) easily. For example: + +```js +beginShape(); +splineVertex(200, 100); +splineVertex(300, 50); +splineVertex(400, 150); +splineVertex(300, 250); +splineVertex(200, 200); +endShape(CLOSE); +``` + +This might draw a closed, rounded pentagon-like shape. The end of the shape (last vertex at 200,200) connects smoothly back to the start (200,100). The curvature is continuous at the closure point as well – no sharp corners – because splineVertex() ensures continuity of the spline’s slope when closing. + +Remember that splineVertex() also supports 3D coordinates and even texture coordinates if needed. In WEBGL mode, you can pass three arguments (x, y, z) for each splineVertex to create 3D curves. + +For example: + + + +## Adjusting Spline Tightness (Curvature) + +One of the powerful ways to customize the look of your spline is by adjusting its tightness, which essentially controls how sharply the curve bends at the points. In p5.js 1.x, there was a function curveTightness() for this. In p5.js 2.x, tightness is managed via splineProperty('tightness', value) (or the batch version splineProperties() to set multiple properties at once). This function allows you to set a global tightness value that affects all subsequent splines you draw (until you change it again). + +By default, the tightness property is 0, which produces a smooth, natural Catmull-Rom curve that passes evenly through the vertices. Setting tightness to 0 yields the “normal” Catmull-Rom behavior (also known simply as a Cardinal spline with standard tension). When tightness is 0, the curve has a fairly balanced approach and departure at each point. + +If you provide a positive tightness, the spline pulls tighter toward each anchor point, resulting in a path that has sharper turns (it starts to resemble a polyline connecting the points, but still with some curvature). As you increase tightness, the curve becomes more angular around the vertices. On the other hand, a negative tightness will make the spline looser or more bowed out – the curve will overshoot around the points in a wider arc, creating a more rounded, gentle bend. Common tightness values typically range between -1 and 1 for subtle adjustments, but you can use values outside that range for more exaggerated effects. + +To set the tightness, you can do something like: + +```js +splineProperty('tightness', t); +``` + +Where t is your desired tightness value (positive, negative, or zero). This will affect subsequent calls to spline() or splineVertex() + +Let’s see the effect of tightness with an example. Suppose we use the same four points but vary the tightness: + +- Tightness = -5 (very loose): The curve will have a pronounced bulge, rounding out significantly between the points. + +- Tightness = 0 (default): A balanced Catmull-Rom curve through the points. + +- Tightness = 5 (very tight): The curve will cling to the points and form sharper, almost corner-like bends. + + +// TODO: Add image for tightness -5 + +In the image above (t = -5), notice how far the curve arcs outward between the anchor points – it's creating a much rounder shape than the default. The spline is undershooting and overshooting around the anchors, almost as if the curve is more relaxed or elastic. This is great for making very soft, bubbly shapes that need to smoothly flow through points without sharp changes in direction. + +// TODO: Add image for tightness 5 + +In contrast, the second image (t = 5) shows the curve bending abruptly at each red point. The segments between points are much straighter (almost like line segments) before quickly turning near the anchor. This setting might be useful if you want a curve that is almost like a polyline but with slight rounding at the corners (for example, a stylized path or a network diagram where you want mostly straight connections with gentle rounding at junctions). + +If you want to go back to the default behavior after changing tightness, simply set splineProperty('tightness', 0) again. This will restore the standard Catmull-Rom calculations. + + + \ No newline at end of file diff --git a/src/content/tutorials/images/featured/Intro-to-splines.png b/src/content/tutorials/images/featured/Intro-to-splines.png new file mode 100644 index 0000000000..4742996490 Binary files /dev/null and b/src/content/tutorials/images/featured/Intro-to-splines.png differ