Midterm Progress 1

Project Concept:

This past summer, I had the incredible opportunity to spend time in Accra during a June term, and I quickly fell in love with the city. Everything about it—the culture, the people, the food, and even the clothing—felt so familiar, like a vibrant reflection of home. It was as if I had stepped into a living blueprint of my own country. The connections between Accra and home were unmistakable, especially in the patterns woven into the fabric of daily life.

I found myself especially drawn to the Adinkra symbols, which originated from the Ashanti tribe. These symbols, while simple to the untrained eye, carry deep significance. They appear on fabrics, pottery, logos, and are even on walls as decor—integrated into everyday life with meaning and tradition. Although I cannot fully capture the weight of their history and symbolism, I have been deeply inspired by them.

This project is my way of paying homage to these symbols by integrating their essence into a generative art form that reflects the patterns I admired so much.

 

Project Approach:

My goal is to create a dynamic, generative pattern that evolves with each click of the mouse. Not only will the patterns themselves change, but I also want the process to unfold visibly allowing the viewer to see the art as it’s being created. By adjusting the frame rate and tracking oscillations, I hope to give each sketch a sense of fluidity and movement.

The key elements of this project will be variations in color and symbol placement, ensuring that each generated pattern feels unique. While some patterns may repeat, my challenge is to make them complement one another seamlessly.

Embedded sketches:

code:

This is a piece of the code that I wrote that generates some of the patterns that you see in the sketches. Overall, I’m really proud of them so far but there are a lot of things that I want to add and  possibly some things that I may remove as well. Currently, I’m working on some more patterns and     continuing to test them within the code before I produce another sketch.

// Geometric Triangles
function drawGeometricTriangles(x, y, size) {
  let numTriangles = 5;
  let colorPick = random(colors);
  stroke(colorPick);
  noFill();
  
  let triangleSize = size;
  for (let i = 0; i < numTriangles; i++) {
    triangle(-triangleSize / 2, triangleSize / 2, triangleSize / 2, triangleSize / 2, 0, -triangleSize / 2);
    triangleSize *= 0.7;
  }
}

// Harmonic Motion Style Sine Waves
function drawSineWaves(x, y, size) {
  let waveHeight = size / 4;
  let frequency = 0.2;
  strokeWeight(2);
  noFill();
  
  beginShape();
  for (let i = 0; i < size; i++) {
    let xOffset = i;
    let yOffset = sin(i * frequency) * waveHeight;
    vertex(xOffset - size / 2, yOffset);
  }
  endShape();
}

// Nested Triangles
function drawNestedTriangles(x, y, size) {
  let triangleSize = size;
  noFill();
  stroke(random(colors));
  for (let i = 0; i < 4; i++) {
    triangle(-triangleSize / 2, triangleSize / 2, triangleSize / 2, triangleSize / 2, 0, -triangleSize / 2);
    triangleSize *= 0.7;
  }
}

// West African Symbols/Geometric Shapes
function drawSymbols(x, y, size) {
  noFill();
  let symbolSize = size * 0.6;
  stroke(random(colors));

  // Circle with horizontal/vertical line cross
  ellipse(0, 0, symbolSize, symbolSize);
  line(-symbolSize / 2, 0, symbolSize / 2, 0);
  line(0, -symbolSize / 2, 0, symbolSize / 2);
  
  // Small triangles within
  for (let i = 0; i < 3; i++) {
    let triSize = symbolSize * (0.3 - i * 0.1);
    triangle(0, -triSize / 2, triSize / 2, triSize / 2, -triSize / 2, triSize / 2);
  }
}

// Zebra Print
function drawZebraPrint(x, y, size) {
  let stripes = 10;
  for (let i = 0; i < stripes; i++) {
    let step = i * (size / stripes);
    line(-size / 2 + step, -size / 2, size / 2 - step, size / 2);
    line(size / 2 - step, -size / 2, -size / 2 + step, size / 2);
  }
}

// Diamonds within Diamonds
function drawDiamondsInDiamond(x, y, size) {
  let dSize = size;
  noFill();
  for (let i = 0; i < 5; i++) {
    beginShape();
    vertex(0, -dSize / 2);
    vertex(dSize / 2, 0);
    vertex(0, dSize / 2);
    vertex(-dSize / 2, 0);
    endShape(CLOSE);
    dSize *= 0.7;
  }
}

// Bezier Curves
function drawCurves(x, y, size) {
  noFill();
  strokeWeight(2);
  for (let i = 0; i < 6; i++) {
    bezier(-size / 2, -size / 2, random(-size, size), random(-size, size),
           random(-size, size), random(-size, size), size / 2, size / 2);
  }
}

Challenges:

The biggest challenge lies in generating patterns that I can feel genuinely proud of, especially given the personal connection I have to the source of inspiration. It’s a bit daunting to try and replicate the visual and symbolic depth of Adinkra, but I believe in trial and error as my guide. I’m starting by setting up a grid-based design in my sketches, generating initial patterns based on what we’ve learned in class. From there, I plan to randomly assign patterns to grid positions. The tricky part is not the overall pattern, but the design of each individual grid symbol—finding the balance between randomness and intentionality.

Risk Management:

To reduce the uncertainty, I simply dove in. I knew I wouldn’t find the right direction without first experimenting with the code. While the process is still evolving, I’m happy with some of the early results. I’m now focusing on refining the color combinations and continuing to develop patterns that are both visually compelling and meaningful. At the moment, I have two sketches—one that’s a modified version of the other—and I’m letting the process guide me, tweaking the code until I find something that truly resonates.

Ripple Cubes

https://editor.p5js.org/is2431/sketches/FM0-hjygj

 

Description of the Code

This p5.js sketch creates a 3D cube grid with a dynamic ripple effect. Each cube’s vertical position oscillates based on its distance from the center, while its color changes according to its depth (z coordinate). The concept is inspired by natural wave patterns, where the ripple effect imitates waves traveling through the surface, and color gradients add depth perception to the 3D space. The visual result is a dynamic, pulsating cube, creating an engaging, hypnotic effect.

Functions and Organization

The program organizes the core logic in the draw() function, which is responsible for rendering each frame of the sketch. The ripple effect is achieved through a combination of sin() and dist() functions to compute the oscillation offsets, and the color fill is managed by mapping the z values to RGB values.

Variable and Function Naming

  • w stands for the width of each cube, ensuring the ripple and grid are proportional.
  • rippleHeight controls the amplitude of the waves.
  • cols and rows are used for calculating how many cubes fill the canvas based on the grid spacing.

Reflection and Future Improvements

I’m proud of how well the ripple effect integrates with the 3D rendering and color mapping. However, future improvements could involve adding user interactivity, allowing control over ripple speed, amplitude, or even camera rotation. Another potential idea could be integrating sound, where the ripple responds to external audio input or ambient sounds, enhancing the immersive experience.

The sketch could also evolve into more complex 3D shapes, expanding from simple cubes to spheres or even irregular polygons, to add variety and depth to the visual.

 

Assignemnt 4

Concept

In this project, I aimed to create a mesmerizing visual display that responds dynamically to sound input using the p5.js library. The core concept revolves around simulating particles that mimic harmonic motion while being influenced by real-time audio levels detected through the microphone. This interplay of sound and visuals creates an engaging experience, reminiscent of a light show or a digital aurora, where each particle dances in response to the surrounding environment.

Highlights I’m Proud Of
class Particle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.angle = random(TWO_PI);
    this.length = random(10, 20); // Length of the trails
    this.timeOffset = random(TWO_PI); 
  }

  update(vol) {
   
    let harmonicX = cos(this.angle) * 200 * sin(frameCount * 0.02 + this.timeOffset); // Horizontal oscillation
    let harmonicY = sin(this.angle * 2) * 200 * cos(frameCount * 0.03 + this.timeOffset); // Vertical oscillation

    // Update positions based on combined harmonic motion
    this.x += harmonicX * vol * 0.5; 
    this.y += harmonicY * vol * 0.5; 

  
    this.x += cos(this.angle) * vol * 100; 
    this.y += sin(this.angle) * vol * 100; 

  
    this.x = constrain(this.x, 0, width);
    this.y = constrain(this.y, 0, height);
    
    this.angle += 0.05; // Smooth rotation
  }

 

Embedded sketch

https://editor.p5js.org/mariamalkhoori/full/_9bo6lBl_

Edit Sketch:
https://editor.p5js.org/mariamalkhoori/sketches/_9bo6lBl_
Reflection and Ideas for Future Work
  • Expanded Color Palette: Experiment with a broader range of colors or gradients, perhaps responding to different frequencies or sound levels for a richer visual output.
  • More Complex Patterns: Investigate additional mathematical functions to define the particle movements, creating even more intricate and visually stunning animations.

 

Refrences

Simple Harmonic Motion #11 for Light at Blenheim Palace (2014)

Sound from my favourite band :3 — https://music.youtube.com/watch?v=CaMcDzvwL30&si=ZyQE7yoDhOCHxzFR

Midterm Progress #1 | Equations!

The Concept I am Aiming For

For the midterm, I have two ideas that I wanted to try and make. My first idea is that I would create:

a flowfield representation of mathematical parametric equations, with the mouse or any spawned external objects as an attractor to each field particle. 

Essentially (hopefully), is that the flowfields would be attracted forming, and shaping, a parametric equation on the left. And since different equations can form different fields, it would create variation in the art. I could also create a modifier for the equation so that it’s slightly different. This field can be interrupted and played around with an attractor vertex that is attached to the mouse. I am interested to see how this would work out.

Scouring the vast internet, I found an artist, Ilhan Zulji, who played around a lot with p5.js an interactive elements. My second idea would be to create:

Create waves, squares, ellipses, or any predefined shapes that react to external sources: audio, microphone, or camera, to form a new shape. 

Both of the examples I have shown above were from Zulji and Yankov, where they, through some equations and visuals, generated the art via parameters that respond to sound, acoustics, or noises. I want to see whether I could re-create this with my own personal touch.


Update #01 September 2024

After many thoughts and considerations, plus looking at the capabilities of the pen plotter, I decided to go with the first idea. The sketch below is what I have played around so far:

Essentially, I have particles that follow a certain mathematical equation. This movement is then disrupted by noise and wind (for now). For my final product, I wanted to add a fading feature, as well as more equations, and see how two equations can be combined so that it returns a new shape.

Potential Useful Resources

# Drawing Parametric Equations – Umit Sen 

# Drawing Flow Fields with Vectors – Colorful Coding

# Quadio – Ikko. graphics 

# Audio Reactive Visuals – Leksha Yankov

# Audio Reactive Visuals CS Project – Austin Zhang

Sketch – Week 4

Concept

I wanted to create a lines and circles that moved together, similar to one of Memo Akten’s art pieces. Once I created the basic shapes and movement, I added to the aesthetic by making the sketch resemble flowers in a field.

Code Snippet

let elapsedTime = millis() - startTime;

// adjust speed using lerp over time
if (elapsedTime < 10000) {
  speedFactor = lerp(0.05, 0.5, elapsedTime / 10000);
} else if (elapsedTime < 20000) {
  speedFactor = 0.5;
} else {
  speedFactor = lerp(0.5, 0.05, (elapsedTime - 20000) / 10000);
}

Embedded Sketch 

Reflections

I struggled with making the petals for the flower, and I couldn’t get the petals to touch the circumference of flower. With more time, I would tweak the code and experiment more. I would also try to achieve more interesting/complex visual effects, as Memo Akten’s works are very flashy and colourful.

Letter Jellyfish

The project I made this week was a pure accident. While trying to experiment with one concept, I made a mistake in my code that created something so interesting I decided to keep it. I named this piece “Letter Jellyfish” because the movement of the sine waves reminded me of a jellyfish swimming. The code is on GitHub here.

My original concept, while not fully fleshed out, was intended to be a program where I experimented with creating moving 3D text over a sine wave. From there I planned to experiment with the core to make the program look more interesting. To do this, I would layer multiple sine waves made up of letters to create a “3D” effect of the text. Originally, I wanted the sine wave to iterate over the English alphabet, which would be mapped to its x-coordinate. I decided to make the wave choose random letters instead because it made the piece feel more animated and chaotic. Because the coordinate system is different in WEBGL than in 2D mode, I had trouble seeing the sine wave. I was using the camera() method to try to fix this. After this, I wanted to try to rotate the letters so they spin around while oscillating. While doing this, I accidentally rotated the entire sine waves, creating something similar to what my final piece looks like. I liked how this looked so much, that I pivoted my piece to incorporate these multiple sine waves in a 3D space instead. I tried keeping the aspect of the “3D” text as well, but the program ended up being too laggy to keep it this way. After this, to add some more visual flair to the piece, I made the waves shift their colors over time, based on the current frameCount. The code for these sine waves can be seen here:

for (let i = 0; i < total; i++) {
    let y = map(sin(angles[i]), -1, 1, -height, height / 2);
    let x = map(i, 0, total, -width * 2, width / 2, true);
    let letter;
    letter = map(x, -width / 2, width / 2, 0, alphabet.length - 1, true);
    push();
    for (let i = 0; i < 25; i++) {
      rotateY(45);
      translate(0, 0, 0.5);
      fill((i * 2 + frameCount) % 360, 100, 100);
      text(alphabet[floor(random(alphabet.length))], x, y);
    }
    pop();
    let increment = TWO_PI / 60;
    angles[i] += increment;
  }

 

I was doing a lot of experimenting using the camera() function, something I’d never used before. I was not able to make the camera move as I wanted it to, but I made a nice rudimentary one that would rotate around the piece. Ideally, I wanted the camera to occasionally go inside the center of the piece so that the camera would be surrounded by the letters close-up. This would probably be the main thing I want to improve on for this piece. I would also like to add more waves and make the pseudo-3D text, since it looked even more visually pleasing, but the program becomes too laggy to do this.

Atomic Harmony: Pendulums and Electrons in Motion – Week 4

Concept
Inspired by Memo Akten’s Simple Harmonic Motion, I wanted to create something that combines pendulums and electron orbits, both moving in harmony. Akten’s work is amazing because it shows natural forces and mathematical patterns as art, and I wanted to bring that same feeling into my own sketch. In my sketch, pendulums swing while electrons move around a nucleus, representing atomic motion. I wanted to show how harmonic motion, something we usually see in science, can also be artistic and full of life. The result is an abstract but dynamic experience that connects physics with art.

Code Highlight
A section of the code I’m particularly happy with is the part that updates and draws the electrons. I didn’t want them to just follow a fixed orbit; I wanted them to move more like waves, giving the sketch a more organic and alive feel.

// Electron class to represent electrons orbiting the nucleus
class Electron {
    constructor(orbitalRadius, startAngle) {
        this.orbitalRadius = orbitalRadius; // Radius of the orbital
        this.angle = startAngle; // Starting angle for electron
        this.speed = 0.02; // Angular speed of electron
        this.amplitude = 40; // Amplitude for oscillating motion (to simulate wave-like behavior)
        this.frequency = 0.2; // Frequency of oscillation
        this.phase = random(TWO_PI); // Random phase offset for each electron
    }

    // Update electron's position with simple harmonic motion
    update() {
        this.angle += this.speed; // Increase angle for circular motion
        let displacement = this.amplitude * sin(this.frequency * time + this.phase); // Calculate oscillation in radius
        this.currentRadius = this.orbitalRadius + displacement; // Current radius based on oscillation
    }

    // Display electron and its trail
    display() {
        let x = cos(this.angle) * this.currentRadius; // Calculate x position based on current angle and radius
        let y = sin(this.angle) * this.currentRadius; // Calculate y position based on current angle and radius

        // Draw a trail behind the electron
        stroke(180, 100, 100, 0.2); // Light trail color
        noFill(); 
        beginShape(); // Begin trail shape
        for (let i = 0; i < 20; i++) {
            let trailAngle = this.angle - i * this.speed; // Calculate trail angle
            let trailRadius = this.orbitalRadius + this.amplitude * sin(this.frequency * (time - i * 0.1) + this.phase); // Calculate trailing radius with oscillation
            let tx = cos(trailAngle) * trailRadius; // X position of trail point
            let ty = sin(trailAngle) * trailRadius; // Y position of trail point
            vertex(tx, ty); // Draw vertex at trail point
        }
        endShape(); // Finish trail shape

        // Draw the electron as a filled circle
        fill(180, 100, 100); // Bright blue electron color
        noStroke(); 
        circle(x, y, 12); // Draw electron at current position
    }
}

The key part here is the use of harmonic oscillation to give the electrons a wave-like displacement, which makes them feel less rigid and more alive. I think this detail makes the overall animation more interesting and adds a layer of depth to the movement.

Challenges
One of the biggest challenges I faced was getting the electron orbits to feel natural. At first, they moved too mechanically, just following a circle. It felt too predictable and didn’t fit with the kind of fluid, organic motion I was aiming for. To fix this, I added the harmonic oscillation to their radius, which made them move in and out as they orbit, giving the motion more life.

Embedded Sketch

Reflection and Future Work
I’m happy with how the pendulums and electrons came together in this sketch. The combination of pendulums swinging and electrons orbiting creates an interesting mix of science and art. I liked exploring how these motions can be turned into something visually engaging. In the future, I’d like to add some interactivity to the sketch. For example, letting the viewer control the speed or direction of the pendulums and electrons using their mouse. I think that would make the experience more fun and personal. Finally, using randomness (like Perlin noise) for the electron movement could make it look more natural and less predictable. There are a lot of ways I could continue improving this, but for now, I’m happy with what I’ve created!

Week 4 – Harmony in the Cosmos

Inspiration

Memo Akten’s work on Simple Harmonic Motion made me drawn to the rhythmic, cyclical patterns that form the foundation of so much of our world. Akten’s focus on symmetry and motion led me to think about the natural world—and nothing reflects cosmic symmetry and harmonic motion quite like a galaxy. I drew inspiration from Akten to code a visual representation of a spiral galaxy, exploring the underlying harmony that often governs the universe itself.

The Concept

A galaxy, particularly a spiral galaxy, is a perfect example of natural harmony. Galaxies are bound by gravity, with billions of stars moving in circular or elliptical orbits around a central massive core, often a black hole. While galaxies appear chaotic at first glance, they actually follow harmonious patterns of motion. The stars within them move predictably, the spiral arms wind in beautiful formations, and the entire system evolves slowly over time.

What fascinated me about Akten’s work was his ability to capture movement that is predictable yet fluid, a concept that parallels the dynamics of galaxies. Inspired by his approach to harmonic motion, I wanted to create my own representation of a galaxy, shifting and evolving while remaining within the boundaries of harmonic symmetry.

Sketch

The Core Code Idea

  1. Condensed Spiral Galaxy: I focused on creating a galaxy that’s small enough to fit within the canvas while still representing the vastness and depth of space. A maximum radius of 150 pixels keeps the stars tight and visually coherent.
  2. Stars on Spiral Arms: The stars are distributed along several spiral arms (in this case, five) and move in circular orbits around the center. Their speed is dependent on how far they are from the center, mimicking how stars in real galaxies behave.
  3. Slow Shape Shifting: Inspired by Akten’s harmonic motion, I added a subtle shifting mechanism where the spiral arms slowly evolve over time, creating a dynamic and living representation of a galaxy.

Full Code

let stars = [];
let numStars = 400; // Number of stars
let spiralArms = 5; // Number of spiral arms
let galaxyRadius = 150; // Maximum radius of the galaxy (more condensed)
let timeFactor = 0.001; // Factor for the shape-shifting over time

function setup() {
  createCanvas(600, 500); 
  noStroke();

  // Create stars
  for (let i = 0; i < numStars; i++) {
    let angle = random(TWO_PI); // Random starting angle
    let radius = sqrt(random()) * galaxyRadius; // Random radius within the galaxy radius
    let star = {
      angle: angle, // Initial angle
      radius: radius, // Distance from the center
      speed: map(radius, 0, galaxyRadius, 0.002, 0.015), // Stars closer to the center move faster
      size: map(radius, 0, galaxyRadius, 2, 4), // Smaller stars are further out
      twinkleOffset: random(TWO_PI) // Random phase for twinkle effect
    };
    stars.push(star);
  }
}

function draw() {
  background(10, 10, 30, 80); 
  translate(width / 2, height / 2); // Moves origin to the center of the canvas

  // Slowly shift the shape of the spiral galaxy over time
  let spiralShift = sin(frameCount * timeFactor) * 0.5;

  for (let i = 0; i < stars.length; i++) {
    let star = stars[i];
    
    // Calculates star position with spiral shifting
    let armOffset = (i % spiralArms) * TWO_PI / spiralArms + spiralShift; // Spiral arm offset with time-based shift
    let x = star.radius * cos(star.angle + armOffset);
    let y = star.radius * sin(star.angle + armOffset);
    
    // Updates star's angle to make it rotate around the center
    star.angle += star.speed;
    
    // Twinkle effect: stars slowly change brightness
    let twinkle = map(sin(star.twinkleOffset + frameCount * 0.05), -1, 1, 180, 255);
    fill(twinkle, twinkle, 255); // Soft white-blue color for stars
    
    // Draws the star
    ellipse(x, y, star.size, star.size);
  }

    // Draws the central black hole 
  fill(0);
  ellipse(0, 0, 20, 20); // Central black hole at the center
}

Explanation: 

  • Cos() and Sin() Functions calculate the stars’ X and Y positions based on their angle around the galaxy’s center.
  • The formula x = radius * cos(angle) calculates the horizontal (X) position of the star.
  • Similarly, y = radius * sin(angle) calculates the vertical (Y) position.
  • Angle Update: Each star’s angle property increases slightly in every frame, causing them to move in circular orbits. This simulates the motion of stars around the galaxy’s core.
  • Spiral Shift: The spiralShift variable gradually changes over time, slowly altering the spiral arms’ positions, mimicking a galaxy’s slow evolution.

This code allows us to simulate stars moving in elliptical paths around the galaxy’s center, which is key to creating the harmonic motion of a spiral galaxy.

What Can Be Improved

  • Interactive Zoom and Pan: Allow users to zoom in and out of the galaxy to simulate a sense of scale, as galaxies are vast and complex. Panning around the galaxy would also help users explore different parts of it.
  • Nebulae and Star Clusters: Adding glowing clouds or star clusters would enhance the visual complexity.

 

Week 4-Bringing a Flower to Life

Concept:

I got the idea for this assignment after seeing the work of our Artist of the Week: Memo Akten, especially his piece on Simple Harmonic Motion. His smooth, rhythmic designs instantly made me think of nature—like the gentle sway of a flower’s petals in the wind. That’s when it clicked! I decided to create my own version, a flower-like pattern with oscillating petals, using p5.js. I wanted to bring the flower to life by making its petals pulse and move in a way that mimicked real-life motion.

Embedded Sketch:

The code creates a dynamic flower-like pattern with oscillating petals using the sin() function to control smooth, rhythmic motion. First, the flower is centered on the canvas using translate(), and the petals are drawn in a loop, with each petal’s size oscillating based on the sine wave. Multiple layers of petals are added for depth, giving the flower a complex appearance. The stem and leaves are then positioned right below the flower, and the leaves gently sway using the same oscillation logic. The time variable drives the continuous animation, making the flower feel alive and constantly moving.

What I’m Proud Of in the Code:

One of the highlights of my project is how the petals grow and shrink over time, giving the flower a breathing effect. Here’s a key part of the code:

let petalOscillation = amplitude + sin(time + angle + l) * 20;
ellipse(layerOffset, 0, petalWidth, petalHeight);

This section makes use of the sin() function to create the smooth back-and-forth movement of the petals. By adjusting the size of each petal with a sine wave, the flower seems to come alive, as the petals expand and contract in a rhythmic way. Adding multiple layers of petals helped give the flower more depth, making it look more complex and natural.

Challenges I Faced:

One of the main challenges was figuring out how to position the stem and flower correctly. At first, the stem kept appearing on top of the flower, which looked strange. I had to experiment with the translation (moving the origin point) in p5.js to get the stem to sit underneath the flower, where it belongs.

Reflection:
Looking back, I’m really happy with how the petals move in such a smooth and natural way. Working on this project helped me better understand how to use harmonic motion and how to layer shapes and elements in p5.js. The flower ended up feeling dynamic and alive, which was exactly what I was aiming for.

In the future, I’d like to:

1.Add interactivity: It would be cool if the flower could respond to user input, like mouse movement. For example, maybe the user could change how fast the petals pulse or how big they grow.

2. Try 3D: Moving this concept into 3D would make the flower even more exciting. Imagine the petals not just growing and shrinking, but also rotating and moving in a 3D space.

3.Make the movement more natural: Right now, the movement is smooth, but it could feel even more organic if I added some Perlin noise to the oscillations. This would make the petals and leaves move in a way that’s a little less predictable and more like how things behave in nature.

Week 4: Oscillations

Inspiration

My inspiration for this sketch is Memo Akten. However, I also wanted to include something I have seen before. I initially started experimenting with 16 points to create the Tesseract, a 4-dimensional cube. Later I found that to be a bit difficult and moved on to just experimenting with multiple points and connections to create something that has a DNA helix-like structure but also oscillates and moves.

Sketch

Highlight of Code

function generateLayers(n, r) {
  let layerPoints = [];
  let angleStep = TWO_PI / n;  // divide the circle into equal segments
  
  // create points in a circular layout
  for (let i = 0; i < n; i++) {
    let angle = i * angleStep;
    let x = r * cos(angle);
    let y = r * sin(angle);
    layerPoints.push({ x: x, y: y });
  }
  
  points.push(layerPoints);  // add this layer to the points array

  // base case: stop when there's only one point in the layer
  if (n > 1) {
    // recursive call to generate the next layer with half the points and a smaller radius
    generateLayers(floor(n / 2), r * 0.7);  // reduce the radius and number of points for each layer
  }
}

This is the function that generates the layers of points. The function generates circular layers based on the number of layers and the radius of each layer. Changing the radius and the number of points changes the visualization completely