AI Inverse Kinematics Tentacle - Snake Chain Animation Mesmerizing snake-like chain that follows yo

This sketch creates a mesmerizing inverse kinematics (IK) tentacle that follows your mouse with smooth, snake-like motion. A chain of 15 connected segments undulates organically, with a glowing purple head that transitions to cyan at the tail, creating a fluid, living animation.

πŸŽ“ Concepts You'll Learn

Inverse KinematicsAnimation loopTrigonometry (atan2, sin, cos)Color gradientsMouse trackingSmooth interpolation (lerp)Array-based chain simulationOscillation and wobble effects

πŸ”„ Code Flow

Code flow showing setup, draw, updatetentacle, dragsegment, drawtentacle, windowresized

πŸ’‘ Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] click setup href "#fn-setup" click draw href "#fn-draw" setup --> canvas-creation[Canvas Setup] setup --> segment-length-calc[Segment Length Calculation] setup --> segment-initialization[Segment Array Initialization] setup --> color-setup[Color Initialization] click canvas-creation href "#sub-canvas-creation" click segment-length-calc href "#sub-segment-length-calc" click segment-initialization href "#sub-segment-initialization" click color-setup href "#sub-color-setup" draw --> background-clear[Background Clearing] draw --> wobble-calculation[Wobble Effect Calculation] draw --> smooth-following[Smooth Mouse Following] draw --> ik-update[Inverse Kinematics Update] draw --> drawtentacle[drawtentacle] click background-clear href "#sub-background-clear" click wobble-calculation href "#sub-wobble-calculation" click smooth-following href "#sub-smooth-following" click ik-update href "#sub-ik-update" click drawtentacle href "#fn-drawtentacle" ik-update --> chain-loop[Segment Chain Loop] ik-update --> angle-calculation[Angle Calculation] ik-update --> wobble-oscillation[Per-Segment Wobble] ik-update --> position-update[Position Update] click chain-loop href "#sub-chain-loop" click angle-calculation href "#sub-angle-calculation" click wobble-oscillation href "#sub-wobble-oscillation" click position-update href "#sub-position-update" drawtentacle --> rendering[Tentacle Rendering] rendering --> segment-drawing-loop[Segment Drawing Loop] rendering --> head-circle[Glowing Head Circle] click rendering href "#sub-rendering" click segment-drawing-loop href "#sub-segment-drawing-loop" click head-circle href "#sub-head-circle" windowresized[windowResized] --> canvas-resize[Canvas Resizing] windowresized --> reset-segments[Segment Reset] click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" click reset-segments href "#sub-reset-segments"

πŸ“ Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the canvas, calculates segment sizes, creates all the arrays that store segment positions, and sets up colors. This is where you define everything the sketch needs before animation begins.

function setup() {
  createCanvas(windowWidth, windowHeight);

  segLength = min(width, height) / (numSegments + 3);

  // Initialize all segments at center
  for (let i = 0; i < numSegments; i++) {
    baseX[i] = width / 2;
    baseY[i] = height / 2;
    endX[i] = width / 2;
    endY[i] = height / 2;
    angles[i] = 0;
  }

  // Gradient: purple (head) β†’ cyan (tail)
  headColor = color(180, 80, 255);
  tailColor = color(0, 255, 255);

  strokeCap(ROUND);

  // Start target in the center
  targetX = width / 2;
  targetY = height / 2;
}

πŸ”§ Subcomponents:

calculation Canvas Setup createCanvas(windowWidth, windowHeight)

Creates a full-window canvas that fills the entire browser window

calculation Segment Length Calculation segLength = min(width, height) / (numSegments + 3)

Calculates the length of each chain segment based on window size so the tentacle scales responsively

for-loop Segment Array Initialization for (let i = 0; i < numSegments; i++) { baseX[i] = width / 2; baseY[i] = height / 2; endX[i] = width / 2; endY[i] = height / 2; angles[i] = 0; }

Sets all 15 segments to start at the center of the canvas with zero angle

calculation Color Initialization headColor = color(180, 80, 255); tailColor = color(0, 255, 255);

Defines the gradient colors: purple for the head and cyan for the tail

Line by Line:

createCanvas(windowWidth, windowHeight)
Creates a canvas that matches the full browser window size, allowing the tentacle to use the entire screen
segLength = min(width, height) / (numSegments + 3)
Calculates how long each segment should be by dividing the smaller dimension by the number of segments plus 3 (for padding). This ensures the tentacle fits nicely on screen regardless of window size
for (let i = 0; i < numSegments; i++)
Loops through all 15 segments to initialize their starting positions and angles
baseX[i] = width / 2; baseY[i] = height / 2
Sets the base (start) of each segment to the center of the canvas
endX[i] = width / 2; endY[i] = height / 2
Sets the end (tip) of each segment to the center of the canvas initially
angles[i] = 0
Initializes all segment angles to 0 radians (pointing to the right)
headColor = color(180, 80, 255)
Creates a purple color (RGB: 180 red, 80 green, 255 blue) for the head of the tentacle
tailColor = color(0, 255, 255)
Creates a cyan color (RGB: 0 red, 255 green, 255 blue) for the tail of the tentacle
strokeCap(ROUND)
Makes the ends of drawn lines rounded instead of square, creating a smoother appearance
targetX = width / 2; targetY = height / 2
Sets the initial target position (where the tentacle head aims) to the center of the canvas

draw()

draw() runs 60 times per second (by default). Each frame, it clears the background, calculates wobble and smooth following, updates the IK chain, and draws everything. The key to smooth animation is using lerp() for the target positionβ€”this creates the fluid, organic feel rather than the tentacle snapping directly to the mouse.

function draw() {
  background(5, 3, 20); // dark, slightly bluish background

  const time = frameCount * 0.03;

  // Small organic wobble applied to the *target* itself
  const wobbleRadius = segLength * 0.3;
  const wobbleX = cos(time) * wobbleRadius;
  const wobbleY = sin(time * 1.3) * wobbleRadius;

  // Smoothly follow the mouse (low-pass filter)
  const followSpeed = 0.15;
  const desiredX = mouseX + wobbleX;
  const desiredY = mouseY + wobbleY;

  targetX = lerp(targetX, desiredX, followSpeed);
  targetY = lerp(targetY, desiredY, followSpeed);

  // Update IK chain so that the first segment's end follows targetX/Y
  updateTentacle(targetX, targetY);

  // Draw segments from tail β†’ head so head is on top
  drawTentacle();
}

πŸ”§ Subcomponents:

calculation Background Clearing background(5, 3, 20)

Clears the canvas each frame with a dark bluish color, preventing motion trails

calculation Wobble Effect Calculation const wobbleRadius = segLength * 0.3; const wobbleX = cos(time) * wobbleRadius; const wobbleY = sin(time * 1.3) * wobbleRadius;

Creates organic circular motion around the mouse cursor using sine and cosine waves

calculation Smooth Mouse Following targetX = lerp(targetX, desiredX, followSpeed); targetY = lerp(targetY, desiredY, followSpeed);

Smoothly interpolates the target position toward the mouse using lerp, creating fluid motion instead of jerky tracking

calculation Inverse Kinematics Update updateTentacle(targetX, targetY)

Calculates the positions of all 15 segments so the chain follows the target

calculation Tentacle Rendering drawTentacle()

Draws all segments on the canvas with gradual color and thickness changes

Line by Line:

background(5, 3, 20)
Fills the entire canvas with a very dark bluish color, clearing any previous frame content
const time = frameCount * 0.03
Creates a time variable that increases each frame. Multiplying by 0.03 slows down the oscillation so it's not too fast
const wobbleRadius = segLength * 0.3
Sets how far the wobble effect extends from the mouse cursor (30% of segment length)
const wobbleX = cos(time) * wobbleRadius
Creates horizontal wobble using cosine wave, oscillating left and right
const wobbleY = sin(time * 1.3) * wobbleRadius
Creates vertical wobble using sine wave at a slightly different speed (1.3x) to create elliptical motion
const followSpeed = 0.15
Controls how quickly the tentacle follows the mouse (0.15 = 15% of the way per frame). Lower values = smoother, laggier motion
const desiredX = mouseX + wobbleX
Combines the mouse position with the wobble offset to create the target the tentacle aims for
targetX = lerp(targetX, desiredX, followSpeed)
Smoothly moves the target position toward the desired position using linear interpolation (lerp)
updateTentacle(targetX, targetY)
Calls the inverse kinematics function to calculate where all 15 segments should be positioned
drawTentacle()
Renders all segments on the canvas with their colors and thicknesses

updateTentacle(tx, ty)

This function implements the core of inverse kinematics. Instead of calculating all segments at once, it processes them sequentially: the head segment reaches toward the mouse, then the next segment reaches toward the head's base, and so on. This creates a natural chain-following behavior where each segment pulls the next one along.

function updateTentacle(tx, ty) {
  // Head segment follows the (smoothed + wobbly) target
  let currentTargetX = tx;
  let currentTargetY = ty;

  for (let i = 0; i < numSegments; i++) {
    dragSegment(i, currentTargetX, currentTargetY);

    // The base of this segment becomes the target for the next one
    currentTargetX = baseX[i];
    currentTargetY = baseY[i];
  }
}

πŸ”§ Subcomponents:

for-loop Segment Chain Loop for (let i = 0; i < numSegments; i++) { dragSegment(i, currentTargetX, currentTargetY); currentTargetX = baseX[i]; currentTargetY = baseY[i]; }

Iterates through all segments, positioning each one so it reaches toward the previous segment's base, creating a chain effect

Line by Line:

let currentTargetX = tx; let currentTargetY = ty
Initializes the target position to the mouse location (or smoothed target)
for (let i = 0; i < numSegments; i++)
Loops through all 15 segments from head (i=0) to tail (i=14)
dragSegment(i, currentTargetX, currentTargetY)
Positions segment i so its end reaches toward currentTargetX/Y
currentTargetX = baseX[i]; currentTargetY = baseY[i]
Updates the target for the next segment to be the base of the current segment, creating the chain effect

dragSegment(i, tx, ty)

This is the heart of the inverse kinematics system. For each segment, it: (1) calculates the angle pointing toward the target, (2) adds a wobble effect for organic motion, (3) sets the end position to the target, and (4) calculates where the base must be to maintain the fixed segment length. The trigonometry ensures each segment is always exactly segLength units long.

function dragSegment(i, tx, ty) {
  const dx = tx - baseX[i];
  const dy = ty - baseY[i];

  let a = atan2(dy, dx);

  // Per-segment wavy oscillation (snake-like)
  const wobblePhase = frameCount * 0.08 + i * 0.5;
  const wobbleAmount = 0.35; // radians (~20 degrees)
  a += sin(wobblePhase) * wobbleAmount;

  angles[i] = a;

  // The end of this segment should be exactly at (tx, ty)
  endX[i] = tx;
  endY[i] = ty;

  // Compute base position from end position and angle
  baseX[i] = tx - cos(a) * segLength;
  baseY[i] = ty - sin(a) * segLength;
}

πŸ”§ Subcomponents:

calculation Angle Calculation const dx = tx - baseX[i]; const dy = ty - baseY[i]; let a = atan2(dy, dx);

Calculates the angle from the segment's base to the target using atan2

calculation Per-Segment Wobble const wobblePhase = frameCount * 0.08 + i * 0.5; const wobbleAmount = 0.35; a += sin(wobblePhase) * wobbleAmount;

Adds a snake-like wave pattern to each segment's angle, with each segment offset by phase

calculation Position Update endX[i] = tx; endY[i] = ty; baseX[i] = tx - cos(a) * segLength; baseY[i] = ty - sin(a) * segLength;

Sets the segment's end position and calculates the base position using trigonometry

Line by Line:

const dx = tx - baseX[i]; const dy = ty - baseY[i]
Calculates the horizontal and vertical distance from the segment's base to the target
let a = atan2(dy, dx)
Uses atan2 to calculate the angle pointing from the base toward the target. atan2 is better than atan because it handles all four quadrants correctly
const wobblePhase = frameCount * 0.08 + i * 0.5
Creates a unique phase for each segment so they wobble at different times, creating a wave pattern
const wobbleAmount = 0.35
Sets the maximum wobble to 0.35 radians (about 20 degrees), controlling how much the segments deviate from straight
a += sin(wobblePhase) * wobbleAmount
Adds a sine-based oscillation to the angle, making the tentacle undulate like a snake
angles[i] = a
Stores the final angle for this segment (though it's not used in the current code, it could be used for debugging or effects)
endX[i] = tx; endY[i] = ty
Sets the end point of this segment to the target position
baseX[i] = tx - cos(a) * segLength; baseY[i] = ty - sin(a) * segLength
Calculates the base position by moving backward from the end point by segLength in the direction opposite to angle a. This ensures the segment is always exactly segLength units long

drawTentacle()

This function handles all the visual rendering. It draws segments from tail to head (so the head appears on top), applies a color gradient from purple to cyan, and varies the thickness from thick at the head to thin at the tail. The backward loop ensures proper layering. Finally, it adds a glowing purple circle at the head tip for visual emphasis.

function drawTentacle() {
  noFill();

  for (let i = numSegments - 1; i >= 0; i--) {
    const t = i / (numSegments - 1); // 0 = head, 1 = tail

    // Gradient from headColor (purple) to tailColor (cyan)
    const c = lerpColor(headColor, tailColor, t);
    stroke(c);

    // Thicker at head, thinner at tail
    const sw = lerp(18, 4, t);
    strokeWeight(sw);

    // Draw each segment as a line from base to end
    // line(): https://p5js.org/reference/#/p5/line
    line(baseX[i], baseY[i], endX[i], endY[i]);
  }

  // Glowing head "tip"
  noStroke();
  fill(headColor);
  const headRadius = 24;
  circle(endX[0], endY[0], headRadius); // circle(): https://p5js.org/reference/#/p5/circle
}

πŸ”§ Subcomponents:

for-loop Segment Drawing Loop for (let i = numSegments - 1; i >= 0; i--) { const t = i / (numSegments - 1); const c = lerpColor(headColor, tailColor, t); stroke(c); const sw = lerp(18, 4, t); strokeWeight(sw); line(baseX[i], baseY[i], endX[i], endY[i]); }

Draws all segments from tail to head with gradient colors and varying thickness

calculation Glowing Head Circle noStroke(); fill(headColor); const headRadius = 24; circle(endX[0], endY[0], headRadius);

Draws a purple circle at the tip of the head segment for a glowing effect

Line by Line:

noFill()
Disables fill for lines, so only the stroke (outline) will be visible
for (let i = numSegments - 1; i >= 0; i--)
Loops backward from segment 14 (tail) to segment 0 (head). This ensures the head is drawn last and appears on top
const t = i / (numSegments - 1)
Calculates a normalized value from 0 (head) to 1 (tail). Used for color and thickness gradients
const c = lerpColor(headColor, tailColor, t)
Interpolates between purple (head) and cyan (tail) based on position t. At t=0, returns headColor; at t=1, returns tailColor
stroke(c)
Sets the stroke color to the interpolated gradient color
const sw = lerp(18, 4, t)
Interpolates stroke weight from 18 pixels (head, thick) to 4 pixels (tail, thin)
strokeWeight(sw)
Sets the thickness of the line to the calculated stroke weight
line(baseX[i], baseY[i], endX[i], endY[i])
Draws a line from the segment's base to its end point
noStroke()
Disables stroke for the circle so only the fill is visible
fill(headColor)
Sets the fill color to purple for the head circle
circle(endX[0], endY[0], headRadius)
Draws a 24-pixel diameter circle at the tip of the first segment (the head)

windowResized()

windowResized() is a special p5.js function that automatically runs whenever the browser window is resized. This sketch uses it to resize the canvas and recalculate all positions so the tentacle adapts smoothly to different screen sizes. Without this, the tentacle would break or disappear when the window changes.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);

  // Recompute segment length to fit new window
  segLength = min(width, height) / (numSegments + 3);

  // Re-center tentacle so resize doesn't break it
  for (let i = 0; i < numSegments; i++) {
    baseX[i] = width / 2;
    baseY[i] = height / 2;
    endX[i] = width / 2;
    endY[i] = height / 2;
    angles[i] = 0;
  }

  targetX = width / 2;
  targetY = height / 2;
}

πŸ”§ Subcomponents:

calculation Canvas Resizing resizeCanvas(windowWidth, windowHeight); segLength = min(width, height) / (numSegments + 3);

Resizes the canvas to match the new window size and recalculates segment length

for-loop Segment Reset for (let i = 0; i < numSegments; i++) { baseX[i] = width / 2; baseY[i] = height / 2; endX[i] = width / 2; endY[i] = height / 2; angles[i] = 0; }

Resets all segments to the center of the new canvas to prevent visual glitches

Line by Line:

resizeCanvas(windowWidth, windowHeight)
Updates the canvas size to match the new browser window dimensions
segLength = min(width, height) / (numSegments + 3)
Recalculates segment length based on the new window size so the tentacle still fits properly
for (let i = 0; i < numSegments; i++)
Loops through all segments to reset them
baseX[i] = width / 2; baseY[i] = height / 2; endX[i] = width / 2; endY[i] = height / 2
Resets each segment to start at the center of the new canvas
angles[i] = 0
Resets all angles to 0 to prevent any leftover rotation values
targetX = width / 2; targetY = height / 2
Resets the target position to the center of the new canvas

πŸ“¦ Key Variables

numSegments number

Stores the number of chain segments (15). Controls how long and detailed the tentacle is

let numSegments = 15;
segLength number

Stores the length of each individual segment in pixels. Calculated based on window size so the tentacle scales responsively

segLength = min(width, height) / (numSegments + 3);
baseX array

Array storing the x-coordinate of each segment's base (starting point). baseX[0] is the head's base, baseX[14] is the tail's base

let baseX = [];
baseY array

Array storing the y-coordinate of each segment's base (starting point). Parallel to baseX

let baseY = [];
endX array

Array storing the x-coordinate of each segment's end (tip point). endX[0] is the head tip, endX[14] is the tail tip

let endX = [];
endY array

Array storing the y-coordinate of each segment's end (tip point). Parallel to endX

let endY = [];
angles array

Array storing the angle (in radians) of each segment. Used for wobble calculations and could be used for debugging

let angles = [];
headColor color

Stores the purple color used for the head of the tentacle. Used in gradient calculations

let headColor = color(180, 80, 255);
tailColor color

Stores the cyan color used for the tail of the tentacle. Used in gradient calculations

let tailColor = color(0, 255, 255);
targetX number

Stores the current smoothed x-coordinate where the tentacle head is aiming. Updated each frame with lerp for smooth following

let targetX;
targetY number

Stores the current smoothed y-coordinate where the tentacle head is aiming. Updated each frame with lerp for smooth following

let targetY;

πŸ§ͺ Try This!

Experiment with the code by making these changes:

  1. Increase numSegments from 15 to 25 or 30 to create a longer, more detailed tentacle. Notice how it becomes more flexible and snake-like
  2. Change the wobbleAmount in dragSegment() from 0.35 to 0.1 or 0.8 to make the tentacle less or more wavy. Try values between 0 and 1
  3. Modify the followSpeed in draw() from 0.15 to 0.05 (slower) or 0.3 (faster) to change how quickly the tentacle follows your mouse
  4. Change the headColor to color(255, 0, 0) and tailColor to color(0, 255, 0) to create a red-to-green gradient instead of purple-to-cyan
  5. Increase the wobbleRadius multiplier in draw() from 0.3 to 0.8 to make the circular wobble around the mouse much larger
  6. Change the head circle radius from 24 to 50 or 10 to make the glowing head tip bigger or smaller
  7. Modify the strokeWeight values in drawTentacle() from lerp(18, 4, t) to lerp(30, 2, t) to make the head thicker and tail thinner
Open in Editor & Experiment β†’

πŸ”§ Potential Improvements

Here are some ways this code could be enhanced:

PERFORMANCE drawTentacle() - color calculation

lerpColor() is called every frame for every segment (15 times per frame). This is computationally expensive when not necessary

πŸ’‘ Cache colors in an array during setup or update them less frequently. Alternatively, use a simpler color interpolation method or pre-compute a color palette

BUG dragSegment() - angle calculation

If the base and target are at the same position (dx=0, dy=0), atan2(0, 0) returns 0, but this edge case could cause unexpected behavior

πŸ’‘ Add a check: if (dx === 0 && dy === 0) return; to skip processing when base and target coincide

STYLE Global variables

Many global variables (baseX, baseY, endX, endY, angles) could be organized into a single array of segment objects for better code organization

πŸ’‘ Create a Segment class or use an array of objects: let segments = []; segments[i] = {baseX, baseY, endX, endY, angle} for cleaner code

FEATURE draw() and updateTentacle()

The tentacle always follows the mouse. There's no way to pause, change behavior, or add interaction modes

πŸ’‘ Add keyboard controls (e.g., press 'P' to pause, 'W' to toggle wobble) or mouse click detection to enable different behaviors

BUG windowResized()

When the window is resized, the tentacle is instantly reset to the center, causing a jarring visual pop

πŸ’‘ Gradually interpolate segments back to center over several frames instead of resetting instantly, or preserve relative positions during resize

PERFORMANCE draw() - wobble calculation

Wobble calculations use frameCount which increases indefinitely, potentially causing precision issues after very long sessions

πŸ’‘ Use millis() % 10000 or a local counter that resets periodically to keep time values in a manageable range

Preview

AI Inverse Kinematics Tentacle - Snake Chain Animation Mesmerizing snake-like chain that follows yo - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Inverse Kinematics Tentacle - Snake Chain Animation Mesmerizing snake-like chain that follows yo - Code flow showing setup, draw, updatetentacle, dragsegment, drawtentacle, windowresized
Code Flow Diagram