Anthony Del Ciotto's Website

Digital Art: Seabug Of Paradise

2019-05-30

Seabug of Paradise is an abstract digital art piece built with Processing. It depicts an introverted seabug who must be left alone. They swim gracefully in the deep sea.

The source code is available on Github.

The seabug

Seabug of Paradise running at 2560x1440

Seabug of Paradise running at 2560x1440

The seabug is made up of lines with points that are generated using parametric equations. These equations take in a single parameter t and return an x and y coordinate. Each point is calculated using functions similar to the following:

x = sin(t/a)*b + sin(t/c)*d
y = cos(t/a)*b + sin(t/c)*d

where t is the elapsed time of the application and, a, b, c and d are constant values. For each line, three points are generated using variations of the above function. Two lines are then drawn from the first point to the second and third. This process is repeated 32 times for each leg of the seabug.

void drawSeabug(float t) {
  for (int i = 0; i < 32; i++) {
    // adding the current index ensures there is a gap between each leg
    float ti = t + i;

    float x0 = sin(ti/10.0)*100.0 + sin(ti/5.0)*20.0 + cos(ti/2.0)*3.0;
    float y0 = cos(ti/10.0)*100.0 + sin(ti/5.0)*50.0;

    float x1 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y1 = -sin(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    float x2 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y2 = cos(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    line(x0, y0, x1, y1);
    line(x0, y0, x2, y2);
  }
}

More sin and cos functions can be accumulated for interesting results. I experimented with different variations and values until I achieved the desired effect.

Seabug with no colors or circles

Seabug with no colors or circles

For example, a more predictable circular pattern can be generated if something closer to the parametric equation for a circle is used.

void drawSeabug(float t) {
  for (int i = 0; i < 32; i++) {
    // adding the current index ensures there is a gap between each leg
    float ti = t + i;

    float x0 = sin(t/10.0)*200.0;
    float y0 = cos(t/10.0)*200.0;

    float x1 = -sin(t/10.0)*200.0;
    float y1 = -cos(t/10.0)*200.0;

    line(x0, y0, x1, y1);
  }
}

Seabug with more circular pattern

Seabug with more circular pattern

Each part of the leg is also colored. This is done by generating a linear gradient between two colors with the number of stops equal to the number of legs.

color[] generateLinearGradient(color start, color end, int stops) {
  color colors[] = new color[stops];
  float stepFactor = 1.0 / float(stops-1);

  for (int i = 0; i < stops; i++) {
    float t = stepFactor * float(i);
    colors[i] = lerpColor(start, end, t);
  }

  return colors;
}

The colors chosen for the gradient differ between the first and second line of each leg. The first set of lines use a gradient called Rainbow Blue, and the second set use Ibiza Sunset. These gradients were obtained from uiGradients.

void drawSeabug(float t, color[] firstHalfColors, color[] secondHalfColors) {
  for (int i = 0; i < 32; i++) {
    color firstColor = firstHalfColors[i];
    color secondColor = secondHalfColors[i];

    // adding the current index ensures there is a gap between each leg
    float ti = t + i;

    float x0 = sin(ti/10.0)*100.0 + sin(ti/5.0)*20.0 + cos(ti/2.0)*3.0;
    float y0 = cos(ti/10.0)*100.0 + sin(ti/5.0)*50.0;

    float x1 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y1 = -sin(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    float x2 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y2 = cos(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    stroke(firstColor);
    line(x0, y0, x1, y1);

    stroke(secondColor);
    line(x0, y0, x2, y2);
  }
}

Seabug with colors

Seabug with colors

To achieve the final look demonstrated in the video, the stroke weight is adjusted and circles are drawn at each point generated by the parametric equations.

void drawSeabug(float t, color[] firstHalfColors, color[] secondHalfColors) {
  int circleSize = 4;

  strokeWeight(2);
  for (int i = 0; i < 32; i++) {
    color firstColor = firstHalfColors[i];
    color secondColor = secondHalfColors[i];

    // adding the current index ensures there is a gap between each leg
    float ti = t + i;

    float x0 = sin(ti/10.0)*100.0 + sin(ti/5.0)*20.0 + cos(ti/2.0)*3.0;
    float y0 = cos(ti/10.0)*100.0 + sin(ti/5.0)*50.0;

    float x1 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y1 = -sin(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    float x2 = sin(ti/10.0)*200.0 + sin(ti/4.0)*2.0;
    float y2 = cos(ti/10.0)*200.0 + sin(ti/12.0)*20.0;

    // draw the two lines with different colors
    stroke(firstColor);
    line(x0, y0, x1, y1);
    stroke(secondColor);
    line(x0, y0, x2, y2);

    // fill circles at the generated points
    noStroke();
    fill(firstColor);
    circle(x1, y1, circleSize);
    fill(secondColor);
    circle(x2, y2, circleSize);
    fill(255);
    circle(x0, y0, circleSize);
  }
}

The final seabug

The final seabug

The background

The background is a dynamic radial gradient where the center oscillates horizontally across the x-axis over time. The seabug also oscilattes along with the gradient to give a subtle impression that it's emitting light.

The radial gradient is drawn using a GLSL fragment shader program applied to a black background as a filter. The color of each fragment is calculated using the distance from the center as the t value in a color interpolation function.

uniform vec2 iCenter;
uniform float iThreshold;
uniform vec2 iResolution;
uniform vec4 iStartColor;
uniform vec4 iEndColor;

void main() {
    vec2 uv = gl_FragCoord.xy;
    float dist = distance(iCenter, uv);
    float t = (0.5 - dist/iResolution.x) * iThreshold;
    t = clamp(t, 0.0, 1.0);
    gl_FragColor = mix(iStartColor, iEndColor, t);
}

The colors used for the radial gradient are from Royal.

The background

The background

References