Sketch – Week 3

Concept

For this week’s assignment I wanted to create fish swimming towards ripples in the water. The ripple would create attraction for the fish, and the fish would slow down as they reached the ripple. I used force and drag to create these effects.

Code Snippet

if (this.target != null) {
  let force = p5.Vector.sub(this.target, this.pos); // Force towards target
  let dist = force.mag(); // Distance to target
  force.setMag(map(dist, 0, 100, 0, 1)); // Adjust force based on distance
  this.applyForce(force);

  let drag = this.vel.copy().mult(-1.6).mult(0.1); // Create drag force
  this.applyForce(drag); // Apply drag to slow down near target

  if (dist < 10) this.target = null; // Clear target if close
} else {
  // If no target, allow fish to move randomly
  // Add slight randomness to velocity
  if (this.vel.mag() < 1) {
    this.vel.x += random(-0.1, 0.1);
    this.vel.y += random(-0.1, 0.1);
  }
  this.vel.limit(6); // Limit speed to ensure natural movement
}

this.vel.add(this.acc); // Update velocity
this.pos.add(this.vel); // Update position
this.acc.mult(0); // Reset acceleration

Embedded Code

 

Reflections

If I had more time, I would have the fish flow more seamlessly. Additionally, I think the drag continues to slow the fish down indefinitely, and I would trouble shoot that. I would also try to gamify the sketch a bit more, but having a start screen and an animation of fish food entering the water.

week3 – attractors and movers

Concept:

The concept for this project was to be able to create a recognizable pattern while also using movers and attractors. I was inspired by the flower-dotted patterns as well as Dan Gries’s work. In this project, I try to integrate what we learned in class to recreate such a pattern. However, I decided to add more forces that can disturb the pattern as if it’s almost a shadow.

Highlight of some code:

For this project, I decided to build upon the practice we had in class. I initially wrote a code similar to what we did in class and then watched the nature of the code video and added elements to that. I played around with the names to see what patterns I got. After that, I decided to create some turbulence by creating a bouncing effect when the balls reached the borders of the canvas and by creating gravity between some of the particles. I had so many issues with debugging the code because I was experimenting with it mostly to create a pattern I liked. I think the most challenging part was making the different bodies work relatively in response to one another while also creating interesting shapes as they moved around the canvas. 

These images are different iterations at different stages of my code until I reach the desired result.

class Mover {
  constructor(x, y, m) {
    this.pos = createVector(x, y);
    this.vel = p5.Vector.random2D();
     // this.vel.mult(5);
    //     the acceleration will change when forces act on it
    this.acc = createVector(0, 0);
    this.mass = m;//this store mass m of mover []
    this.r = sqrt(this.mass) * 0.15; //r=sqr m * a constant this is for the radious 
  }

  applyForce(force) {
    let f = p5.Vector.div(force, this.mass);
    this.acc.add(f); //add the forces to acceleration 
  }
//   attract to one another gravity btw diff objects
  TwoAttract(mover) {
    //this.pos-mover=vector(from mover to another mover)
    let force = p5.Vector.sub(this.pos, mover.pos); // 
    let distanceSq = constrain(force.magSq(), 1000, 10000); //limit btwn 1000 and 10000
    let G = 0.2; //gravatational constant 
    let strength = (G * (this.mass * mover.mass)) / distanceSq; //f=G*(m1m2)/d'2 law of gravity 
    force.setMag(strength); //set the force and mag as calculated 
    mover.applyForce(force); //apply calculated to mover 
  }

  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.acc.set(0, 0);
    
        // Boundary checks 
    if (this.pos.x > width) {
      this.pos.x = width;
      this.vel.x *= -1; // Reverse horizontal velocity when hitting right edge
    } else if (this.pos.x < 0) {
      this.pos.x = 0;
      this.vel.x *= -0.1; // Reverse horizontal velocity when hitting left edge
    }
    
    if (this.pos.y > height) {
      this.pos.y = height;
      this.vel.y *= -0.2; // Reverse vertical velocity when hitting bottom edge
    } else if (this.pos.y < 0) {
      this.pos.y = 0;
      this.vel.y *= -0.2; // Reverse vertical velocity when hitting top edge
    }
    // Reset acceleration to 0 after each frame
    this.acc.mult(0);
    
    
  }
  RandomColors(){
  //     colors function
   
   let R = random(90, 100); // r is a random number between 0 - 255
   let G = random(100, 120); // g is a random number betwen 100 - 200
   let B = random(150, 200); // b is a random number between 0 - 100
  return color(R,G,B);
}

  show() {
     strokeWeight(0);
    fill(this.RandomColors());
    ellipse(this.pos.x, this.pos.y, this.r * 2);
  }
}
//class to attract objects to the attractor to show how gravity is btwn 2 objects the attractor and the objects 
class Attractor {
  constructor(x, y, m) {
    this.pos = createVector(x, y);
    this.mass = m;
    this.r = sqrt(this.mass) *115;
  }

  attract(mover) {
    let force = p5.Vector.sub(this.pos, mover.pos);
    let distanceSq = constrain(force.magSq(), 1000, 1500);
    let G = 4;
    let strength = (G * (this.mass * mover.mass)) / distanceSq;
    force.setMag(strength);
    mover.applyForce(force);
  }

  show() {
  
  }
}
let movers = [];
let twoattract;
let attractor;

function setup() {
  createCanvas(600, 600);
  //movers attracted to attracor
  for (let i = 0; i < 6; i++) {
    let x = random(width / 2);
    let y = random(height / 2);
    let m = random(50, 100);
    movers[i] = new Mover(x, y, m);
  }
  attractor = new Attractor(width / 2.5, height / 2, 100);

  background(0);
}

function draw() {
  //3 movers attracted to each other
  for (let t = 0; t < 4; t++) {
    movers[t].update();
    movers[t].show();
    //attractor applies force to each other
    attractor.attract(movers[t]);

    for (let u = 0; u < 6; u++) {
      for (let other of movers) {
        if (t !== u) {
          movers[t].TwoAttract(movers[u]);
        }
      }
    }
  }

  attractor.show();
}

 

Embedded sketch:

Reflection and Future Work:

I am happy with the result of this project; however, I think there is room for improvement.  I think the forces acting on the objects and the attraction more coherent with one another to make them feel a little more pattern would create even more pleasing results. Further, I want to experiment with other shapes rather than circles maybe lines or triangles that would potential;;y create other patterns as they move.

Resources:

https://www.researchgate.net/publication/258499541_Classification_of_symmetry_groups_for_planar_n-body_choreographies

https://dangries.com/rectangleworld/demos/nBody/

https://github.com/nature-of-code/noc-book-2/tree/main/content/examples/01_vectors/example_1_9_motion_101_velocity_and_random_acceleration

https://thecodingtrain.com/tracks/the-nature-of-code-2/noc/2-forces/6-mutual-attraction

Week 3 – Supernova

Inspiration

The inspiration for this sketch is a supernova. A supernova is a powerful and luminous explosion that occurs at the end of a star’s life cycle. It marks the catastrophic death of a star, resulting in the release of an immense amount of energy and light, often outshining entire galaxies for a brief period.

 

 

 

 

Sketch

Highlight of Code

let stars = [];
let numStars = 5000; 
let attractor; 
let state = 'attract'; 
let attractionStrength = 0.1; 
let explosionSpeed = 2; 
let explosionStartFrame; 
let damping = 0.95;

// array of possible attractor positions
let attractorPositions = [];
let attractorIndex = 0; 

function setup() {
  createCanvas(800, 800);
  
  // define the attractor positions: center, 1/4, and 3/4
  attractorPositions = [
    createVector(width / 2, height / 2), // center
    createVector(width / 4, height / 4), // top-left (1/4)
    createVector(3 * width / 4, 3 * height / 4) // bottom-right (3/4)
  ];
  attractor = attractorPositions[attractorIndex]; 

  // initialize stars with random positions 
  for (let i = 0; i < numStars; i++) {
    let x = random(width);
    let y = random(height);
    stars.push(new Star(x, y));
  }
}

function draw() {
  background(0);

  // updates stars based on the current state
  for (let star of stars) {
    if (state === 'attract') {
      star.attractToCenter(attractor, attractionStrength);
    } else if (state === 'explode') {
      star.attractToCenter(attractor, -1 * attractionStrength); 
    }
    star.display();
  }


  if (state === 'attract' && stars.every(star => star.isAtCenter())) {
    state = 'explode';
    explosionStartFrame = frameCount;
  } else if (state === 'explode' && frameCount > explosionStartFrame + 180) {
    state = 'attract';
    changeAttractorPosition(); // change attractor position after each explosion
  }
}

// change the attractor's position
function changeAttractorPosition() {
  attractorIndex = (attractorIndex + 1) % attractorPositions.length; // cycle through positions
  attractor = attractorPositions[attractorIndex];
}




// star class
class Star {
  constructor(x, y) {
    this.position = createVector(x, y);
    this.initialPosition = createVector(x, y);
    this.velocity = createVector(0, 0);
    this.exploding = false; 
    this.distanceToCenter = p5.Vector.dist(this.position, attractor); // initial distance to center
  }

  // attracts/repulse to/from the center 
  attractToCenter(center, attractionStrength) {
    let force = p5.Vector.sub(center, this.position);
    force.normalize();
    force.mult(attractionStrength);
    this.velocity.add(force); // add the positive attraction force to the velocity
    this.velocity.mult(damping); // apply damping to gradually slow down stars as they approach the center
    this.position.add(this.velocity);
    this.distanceToCenter = p5.Vector.dist(this.position, center); 
  }

  // check if the star is close to the center
  isAtCenter() {
    return this.distanceToCenter < 5; 
  }
  
  display() {
    noStroke();
    fill(255, 204, 0); // yellow color for the stars
    ellipse(this.position.x, this.position.y, 5);
  }
}

The full code for the project is attached above. The hardest part was getting the attractor right. I’m using the attractor as a repulsor with a negative attraction strength which makes the force vetro opposite and that was challenging to implement at first. I had also to include a dumping factor to make the simulation go smoothly otherwise the force will just pull all the stars to the attractor and repulse them because the vector becomes negative. For instance, changing the dumping to one makes the simulation an infinite loop of attraction and repulsion without any obvious explosion.

Week 3-Simulating Planetary Motion

Concept:

In this assignment I set out to create a simulation of planetary motion using the p5.js framework. The concept behind this project is to visualize planets rotating in a simple, stylized environment.

I took inspiration Daniel Shiffman’s Nature of Code and his lessons on simulating forces in p5.js helped shape my understanding of how to manipulate objects using vectors and forces.

Code Highlight:

A key part of the project involves rotating and rendering the planets. This code snippet shows how I used two loops to manage the rendering of the “top” and “bottom” halves of the planets:

function draw() {
  background(5,0,20);
  strokeWeight(0);

  // Bottom
  for (let i = 12; i > 0; i--) {
    for (let j = 0; j < p.length; j++) {
      p[j].renderBottom(i);
      p[j].rotate();
    }
  }

  // Top
  for (let i = 0; i < 12; i++) {
    for (let j = 0; j < p.length; j++) {
      p[j].renderTop(i);
      p[j].rotate();
    }
  }
}

The draw() function uses two loops to alternate between rendering the top and bottom sections of each planet. These planets are stored in an array (p), and each planet has a renderBottom() and renderTop() function that handles its respective display on the canvas.

I was particularly proud of this section because it allowed me to divide the rendering into two distinct phases (top and bottom), creating a visually interesting dynamic. This setup gave the planets a layered appearance and made their rotations feel more interactive and complex.

Embedded Sketch:


The “mover” planets appear randomly on the screen, except for the largest one, which always starts in the center. The code uses an “attractor” function that handles three main pieces of information: the “mover”, the “attractor”, and the “force”. The “attractor” function checks if the mover is within the attractor’s orbit. If it’s not, the mover moves towards the orbit. Regardless of its position, the mover will continuously rotate around the attractor by moving in the same direction while subtracting PI / 2 from its angle. The 3D effect is achieved by stacking multiple 2D sprites on top of each other.

Challenges I Faced:
One of the hardest parts was making sure the planets rotated smoothly. At first, the movement was uneven, and the animation didn’t look as good as I wanted. To fix this, I adjusted the rotation functions in the planet class and experimented with different frame rates until the motion looked natural. It was tricky to get the right balance between performance and visual quality, but it felt great once everything worked smoothly.

Reflection and Future Improvements:

Looking back, I feel this project pushed me to think deeply about how to simulate movement in a way that is both computationally efficient and visually appealing. I learned a lot about managing objects in p5.js and how to structure code for animation sequences.

In the future, I’d love to expand on this project by adding more interactivity. For example, I’d like to incorporate user input that changes the speed or direction of the planet rotations, or even allow users to “create” new planets by clicking on the canvas. Another interesting addition would be to simulate gravitational forces between the planets, making their interactions more dynamic and unpredictable. Lastly, I’d like to add sound effects that are tied to the planets’ movement to deepen the sensory experience.

Week 3 – “Be Not Afraid” by Dachi

Sketch

Concept Inspiration

My project, titled “Be Not Afraid,” was inspired by the concept of biblically accurate angels, specifically the Thrones (also known as Ophanim). In biblical and extrabiblical texts, Thrones are described as extraordinary celestial beings. The prophet Ezekiel describes them in Ezekiel 1:15-21 as wheel-like creatures: “Their appearance and structure was as it were a wheel in the middle of a wheel.” They are often depicted as fiery wheels covered with many eyes.

I wanted to recreate this awe-inspiring and somewhat unsettling image using digital art. The multiple rotating rings adorned with eyes in my project directly represent the wheel-within-wheel nature of Thrones, while the overall structure aims to capture their celestial and otherworldly essence. By creating this digital interpretation, I hoped to evoke the same sense of wonder and unease that the biblical descriptions might have inspired in ancient times.

Process of Development

I started by conceptualizing the basic structure – a series of rotating rings with eyes to represent the Thrones’ form. Initially, I implemented sliders for parameter adjustment, thinking it would be interesting to allow for interactive manipulation. However, as I developed the project, I realized I preferred a specific aesthetic that more closely aligned with the biblical descriptions and decided to remove the sliders and keep fixed values.

A key requirement of the project was to use invisible attractors and visible movers to create a pattern or design. This led me to implement a system of attractors that influence the movement of the entire Throne structure. This is mainly expressed in rotation around the center and more turbulent up and down movement. Values for these were adjusted to make motion smooth and graceful, corresponding to that of divine being.

As I progressed, I kept adding new elements to enhance the overall impact and atmosphere. The central eye came later in the process, as did the cloud background and sound elements. The project was all about refinement after refinement. Even at this stage I am sure there are lots of things to improve since lot of is visual representation which at times can be quite subjective.

How It Works

My project uses p5.js to create a 3D canvas with several interacting elements:

  1. Rings: I created four torus shapes with different orientations and sizes to form the base structure, representing the “wheel within a wheel” form of Thrones. Those wheels or rings were taken to be different values but eventually settled for four as it is not too overcrowded while delivering needed effect.
  2. Eyes: I positioned multiple eyes of varying sizes on these rings, reflecting the “full of eyes” description associated with Thrones.
  3. Central Eye: I added a larger eye in the center that responds to mouse movement when the cursor is over the canvas, symbolizing the all-seeing nature of these beings.
  4. Attractors and Movement: I implemented a system of invisible attractors that influence the movement of the entire structure. This includes:
  5. A central attractor that creates a circular motion.
  6. Vertical attractors that add turbulence and complexity to the movement. These attractors work together to create the organic, flowing motion of the Throne structure, evoking a sense of constant, ethereal rotation as described in biblical texts.
  7. Background: I used a cloud texture to provide a heavenly backdrop.
  8. Audio: I incorporated background music and a rotation sound whose volume correlates with the ring speeds to enhance the atmosphere.

Code I’m Proud Of

There are several pieces of code in this project that I’m particularly proud of, as they work together to create the complex, ethereal movement of the Thrones:

  1. The attractor system:
// Calculate attractor position
let attractorX = cos(attractorAngle) * attractorRadius;
let attractorY = sin(attractorAngle) * attractorRadius;

// Calculate vertical attractor position with increased turbulence
let verticalAttractorY = 
  sin(verticalAttractorAngle1) * verticalAttractorAmplitude1 +
  sin(verticalAttractorAngle2) * verticalAttractorAmplitude2 +
  sin(verticalAttractorAngle3) * verticalAttractorAmplitude3;

// Move the entire scene based on the attractor position
translate(attractorX, attractorY + verticalAttractorY, 0);

This code creates complex, organic motion by combining a circular attractor with vertical attractors. It achieves a nuanced, lifelike movement that adds significant depth to the visual experience, simulating the constant, ethereal rotation associated with the biblical descriptions of Thrones.

2. The ring and eye movement, including fading effects:

// Update outer ring spin speed
outerRingTimer++;
if (outerRingTimer >= pauseDuration && !isOuterRingAccelerating) {
  isOuterRingAccelerating = true;
  outerRingTimer = 0;
  fadeOutStartTime = 0;
} else if (outerRingTimer >= accelerationDuration && isOuterRingAccelerating) {
  isOuterRingAccelerating = false;
  outerRingTimer = 0;
  fadeOutStartTime = frameCount;
}

if (isOuterRingAccelerating) {
  outerRingSpeed += ringAcceleration;
  rotationSoundVolume = min(rotationSoundVolume + 0.01, 1);
} else {
  outerRingSpeed = max(outerRingSpeed - ringAcceleration / 3, 0.01);
  
  if (frameCount - fadeOutStartTime < decelerationDuration - fadeOutDuration) {
    rotationSoundVolume = 1;
  } else {
    let fadeOutProgress = (frameCount - (fadeOutStartTime + decelerationDuration - fadeOutDuration)) / fadeOutDuration;
    rotationSoundVolume = max(1 - fadeOutProgress, 0);
  }
}

rotationSound.setVolume(rotationSoundVolume);

// Update ring spins
rings[1].spin += outerRingSpeed;
rings[3].spin += innerRingSpeed;

// Draw and update eyes
for (let eye of eyes) {
  let ring = rings[eye.ring];
  let r = ring.radius + ring.tubeRadius * eye.offset;
  let x = r * cos(eye.angle);
  let y = r * sin(eye.angle);
  
  push();
  rotateX(ring.rotation.x + sin(angle + ring.phase) * 0.1);
  rotateY(ring.rotation.y + cos(angle * 1.3 + ring.phase) * 0.1);
  rotateZ(ring.rotation.z + sin(angle * 0.7 + ring.phase) * 0.1);
  if (eye.ring === 1 || eye.ring === 3) {
    rotateZ(ring.spin);
  }
  translate(x, y, 0);
  
  let eyePos = createVector(x, y, 0);
  let screenCenter = createVector(0, 0, -1);
  let directionVector = p5.Vector.sub(screenCenter, eyePos).normalize();
  
  let rotationAxis = createVector(-directionVector.y, directionVector.x, 0).normalize();
  let rotationAngle = acos(directionVector.z);
  
  rotate(rotationAngle, rotationAxis);
  
  if (eye.isInner) {
    rotateY(PI);
  }
  
  texture(eyeTexture);
  sphere(eye.size);
  pop();
}


This code manages the complex movement of the rings and eyes, including acceleration, deceleration, and fading effects. It creates a mesmerizing visual that captures the otherworldly nature of the Thrones. The fading of the rotation sound adds an extra layer of immersion.

I’m particularly proud of how these pieces of code work together to create a cohesive, organic motion that feels both alien and somehow alive, which is exactly what I was aiming for in this representation of biblically accurate angels.

 

Challenges

The biggest challenge I faced was definitely the movement and implementing the attractor system effectively. Creating smooth, organic motion in a 3D space while managing multiple rotating elements was incredibly complex. I struggled with:

  1. Coordinating the rotation of rings with the positioning and rotation of eyes.
  2. Implementing the acceleration and deceleration of ring rotations smoothly.
  3. Balancing the various movement elements (ring rotation, attractor motion, eye tracking) to create a cohesive, not chaotic, visual effect.

Another significant challenge was accurately representing the complex, wheel-within-wheel structure of Thrones. Balancing the need for a faithful representation with artistic interpretation and technical limitations required careful consideration and multiple iterations.

Reflection

Looking back, I’m satisfied with how my “Be Not Afraid” project turned out. I feel I’ve successfully created an interesting  and slightly unsettling visual experience that captures the essence of Thrones as described in biblical texts. The layered motion effects created by the attractor system effectively evoke the constant rotation associated with these beings. I’m particularly pleased with how the central eye and the eyes on the rings work together to create a sense of an all-seeing, celestial entity.

Future Improvements

While I’m happy with the current state of my project, there are several improvements I’d like to make in the future:

  1. Blinking: I want to implement a sophisticated blinking mechanism for the eyes, possibly with randomized patterns or reactive blinking based on scene events. This could add to the lifelike quality of the Throne.
  2. Face Tracking: It would be exciting to replace mouse tracking with face tracking using a webcam and computer vision libraries. This would allow the central eye to follow the viewer’s face, making the experience even more immersive and unsettling.
  3. Increased Realism: I’d like to further refine the eye textures and shading to create more photorealistic eyes, potentially using advanced shaders. This could enhance the “full of eyes” aspect described in biblical texts.
  4. Interactive Audio: Developing a more complex audio system that reacts to the movement and states of various elements in the scene is definitely on my to-do list.
  5. Performance Optimization: I want to explore ways to optimize rendering and calculation to allow for even more complex scenes or smoother performance on lower-end devices.
  6. Enhanced Wheel Structure: While the current ring structure represents the wheel-like form of Thrones, I’d like to further refine this to more clearly show the “wheel within a wheel” aspect. This could involve creating interlocking ring structures or implementing a more complex geometry system.
  7. Fiery Effects: Many descriptions of Thrones mention their fiery nature. Adding particle effects or shader-based fire simulations could enhance this aspect of their appearance.

References

  1. Biblical descriptions of Thrones/Ophanim, particularly from the Book of Ezekiel
  2. Provided Coding Train video about attractors
  3. Various Art depicting thrones
  4. General internet
  5. Royalty free music
  6. Eye texture PNG (Eye (Texture) (filterforge.com))
  7. https://www.geeksforgeeks.org/materials-in-webgl-and-p5-js/

Update: Added eye movement, removed torus shape, increased eye frequency

Update2: removed outer frame, increased distance to Ophanim, fog effect, 2x zoom effect, modified picture (Photoshop Generative AI). Added more extensive comments. Eye twitch movement (random).

Assignment 3- Mixing Water and Salt

Concept

Pure water is one of the simplest chemical compounds to understand, and the concept of mixing solubles with water is something most people learn early on in third grade. However, translating this fundamental idea into a digital simulation presented a significant challenge. Representing molecules and their interactions through code required a deeper understanding of both chemistry and programming. Initially, my goal was to model how water and salt molecules interact with each other using forces of attraction. This involved not only visualizing the individual molecules but also accurately simulating their dynamic interactions. The process of creating this simulation highlighted the complexity behind seemingly simple concepts and provided valuable insights into the behavior of molecules in a digital environment.

 

I tried to implement the particles movements in this seperate code using attractors to mimic an atom: https://editor.p5js.org/mariamalkhoori/sketches/VmTpfxlVA

Water Atoms Stock Illustrations – 1,606 Water Atoms Stock Illustrations, Vectors & Clipart - Dreamstime

Highlight I’m proud of

 

function draw() {
  background(img);
  
  // Draw the glass of water
  fill(177, 177, 199, 225);
  quad(131, 126, 318, 126, 290, 344, 158, 344);
  
  fill(85, 179, 236, 100);
  quad(137, 177, 312, 177, 290, 344, 158, 344);
  
  // Update and display water molecules
  for (let molecule of waterMolecules) {
    molecule.update();
    molecule.display();
  }

  // Update and display salt molecules if showSalt is true
  if (showSalt) {
    for (let molecule of saltMolecules) {
      molecule.update();
      molecule.display();
    }
  }

  // Check if attraction pairs exist and apply attraction during MIX
  if (isMixing) {
    for (let pair of attractPairs) {
      let waterMolecule = pair.water;
      let saltMolecule = pair.salt;

      // Calculate the distance between the water molecule and the salt molecule
      let target = createVector(saltMolecule.x, saltMolecule.y);
      let currentPos = createVector(waterMolecule.x, waterMolecule.y);
      let distance = p5.Vector.dist(currentPos, target);

      // If the water molecule is close enough, snap to the salt molecule and stop moving
      if (distance > 15) { // Adjust the threshold to be equal to their combined radii
        let attractionForce = p5.Vector.sub(target, currentPos);
        attractionForce.setMag(0.5); // Set attraction force strength

        // Apply the attraction force to move the water molecule towards the salt molecule
        waterMolecule.x += attractionForce.x;
        waterMolecule.y += attractionForce.y;
      } else {
        // Snap the water molecule to the salt molecule's position
        waterMolecule.x = saltMolecule.x;
        waterMolecule.y = saltMolecule.y;
        waterMolecule.isAttached = true; // Mark as attached to stop future movement
      }
    }
  }

 

Embedded sketch

https://editor.p5js.org/mariamalkhoori/sketches/Oy3gJilH_

Reflection and ideas for future work or improvements

The project provided a valuable exploration of simulating molecular interactions and highlighted the complexities of translating theoretical chemistry into a visual format. It revealed the challenge of balancing scientific accuracy with effective coding techniques and emphasized the importance of a solid understanding of molecular behavior. Future improvements could include enhancing simulation accuracy, adding interactive user features, incorporating advanced visualization techniques, expanding to more complex scenarios, and developing educational tools to support learning.

Future Work:

  • Accuracy Improvement: Enhance precision of molecular interactions.
  • User Interaction:Add features for user input and parameter adjustments.
References:

//https://www.youtube.com/watch?v=OAcXnzRNiCY&ab_channel=TheCodingTrain

Week 3 – Chaotic Gravity

This concept is inspired by natural phenomena like planets orbiting stars due to gravity. But instead of perfect, smooth motion, we add turbulence to add a bit of chaos to keep things dynamic and fun to watch.

In the sketch below you’ll see several circular objects (the movers) being pulled towards an invisible point in the middle of the canvas (the attractor). I’ve added some randomness to their movements using turbulence, which shakes things up a bit.

 

Key parts of the code are:

  • Movers: These are the objects that move around the canvas. They’re represented as circles that feel the force of the attractor and the added turbulence.
  • Attractors: There’s only one attractor in this sketch, located in the center of the canvas, but we could add more for a more complex pattern.
  • Forces: The attraction is like gravity, pulling the movers toward the center. The turbulence adds some randomness, so the movers don’t just head straight for the attractor.

The following code mimics gravity: the closer the mover gets to the attractor, the stronger the pull, but the force is constrained so that the mover doesn’t get stuck. Here’s how the attraction works:

function calculateAttraction(moverPos, attractorPos) {
  let force = p5.Vector.sub(attractorPos, moverPos);  // Direction of force
  let distance = constrain(force.mag(), 5, 25);  // Constrain the distance to avoid too strong forces
  force.normalize();
  let strength = (0.4 * 10) / (distance * distance);  // Gravitational pull formula
  force.mult(strength);
  return force;
}

code

Week 3 – Cosmic Drift

Inspiration:

The idea behind the project came to me after visiting my uncle over the weekend. I sat with him for a while and heard his stories of watching the stars in the sky between his trips around the different emirates, so in turn, I wanted to create the natural beauty of space.

Concept:

Invisible attractors act as gravitational forces, pulling particles toward them like stars or planets pulling in celestial bodies. Adding turbulence (a random force similar to cosmic wind) makes the movement less predictable, introducing a layer of chaos and randomness. The result is a balance between order and disorder, where the attractors constantly influence the movers but never settle into perfect orbits.

Sketch:

Code Structure

The code is structured into different components to keep it organized and easy to manage:

Attractors: These are invisible points on the canvas that exert a gravitational pull on the movers. They are positioned randomly at the start and never move. Their forces influence the movers’ paths.

Movers: These are the visible particles that move based on the forces applied by the attractors and the random turbulence. They leave a fading trail behind them as they move, creating beautiful, fluid patterns.

Turbulence: This force adds an element of unpredictability by introducing small random pushes to the movers, making their motion more organic.

Wrapping Around Edges: When movers reach the canvas’s edge, they reappear on the opposite side, creating the feeling of an infinite space.

Full Code:

let movers = [];
let attractors = [];
let turbulenceForce = 0.01; // Strength of random force (simulating cosmic wind)

function setup() {
  createCanvas(800, 600);
  background(0); 
  
  // Create invisible attractors that represent stars or celestial bodies
  for (let i = 0; i < 4; i++) {
    attractors.push(createVector(random(width), random(height))); // Each attractor is placed randomly on the canvas
  }
  
  // Creates visible movers, which represent comets or particles moving around space
  for (let i = 0; i < 100; i++) {
    movers.push(new Mover(random(width), random(height))); // Starts each mover at a random position
  }
}

function draw() {
  // Creates a fading background effect by drawing a transparent black rectangle over the canvas
  // This keeps a slight trail behind the movers, making the motion smoother and more fluid
  fill(0, 20); // Black rectangles with some transparency
  rect(0, 0, width, height);

  // For every mover, apply forces from each attractor and turbulence, then update and display
  for (let mover of movers) {
    for (let attractor of attractors) {
      mover.applyAttraction(attractor); // Apply gravitational attraction from the invisible attractors
    }
    mover.applyTurbulence(); // Add a random force to create unpredictable motion (like cosmic wind)
    mover.update(); // Update the mover's position based on the forces acting on it
    mover.display(); // Draw the mover on the screen
  }
}

// The Mover class defines the behavior and properties of each particle (mover)
class Mover {
  constructor(x, y) {
    this.position = createVector(x, y); // Mover's position (starts at the given x and y)
    this.velocity = createVector();     // Mover's speed and direction (starts still, no velocity)
    this.acceleration = createVector(); // The force applied to the mover to change its motion
    this.maxSpeed = 3;                  // Limit the maximum speed to keep the movement smooth
    this.color = color(255, 255, 0);    // Initial color is yellow (R, G, B = 255, 255, 0)
  }

  // Function to apply attraction from an invisible attractor (representing stars or planets)
  applyAttraction(attractor) {
    // Calculate the direction and distance between the mover and the attractor
    let force = p5.Vector.sub(attractor, this.position);
    let distance = constrain(force.mag(), 20, 150); // Limits the distance to avoid extreme attraction forces
    force.normalize(); // Normalize the force vector to get direction only, without affecting magnitude
    force.mult(1 / (distance * distance)); // Apply the inverse-square law (force decreases as distance increases)
    this.acceleration.add(force); // Add the attraction force to the mover's acceleration
  }

  // Function to add a random force (like a cosmic wind) to make the motion unpredictable
  applyTurbulence() {
    // Create a small random force in any direction
    let turbulence = createVector(random(-turbulenceForce, turbulenceForce), random(-turbulenceForce, turbulenceForce));
    this.acceleration.add(turbulence); // Add this random force to the mover's acceleration
  }

  // Updates the mover's position based on its current velocity and acceleration
  update() {
    this.velocity.add(this.acceleration); // Add the acceleration to the velocity (changing the speed/direction)
    this.velocity.limit(this.maxSpeed);   // Make sure the velocity doesn't exceed the maximum speed
    this.position.add(this.velocity);     // Update the position based on the velocity
    this.acceleration.mult(0);            // Reset acceleration after each frame so forces don't accumulate

    // Changes the color of the mover based on its speed (faster movers turn blue, slower ones stay yellow)
    let speed = this.velocity.mag();      // Calculates how fast the mover is going
    let r = map(speed, 0, this.maxSpeed, 255, 0);  // Red decreases as speed increases
    let g = map(speed, 0, this.maxSpeed, 255, 0);  // Green decreases as speed increases
    let b = map(speed, 0, this.maxSpeed, 0, 255);  // Blue increases as speed increases
    this.color = color(r, g, b); // The color will change from yellow to blue as the speed changes

    // Wrapping around the edges of the screen
    if (this.position.x > width) this.position.x = 0;  // If mover goes off the right edge, wrap to left
    if (this.position.x < 0) this.position.x = width;  // If mover goes off the left edge, wrap to right
    if (this.position.y > height) this.position.y = 0; // If mover goes off the bottom, wrap to top
    if (this.position.y < 0) this.position.y = height; // If mover goes off the top, wrap to bottom
  }

  // Function to display (draw) the mover on the screen
  display() {
    stroke(this.color);  // Set the stroke (outline) color of the mover based on its current color
    strokeWeight(2);     // Set the thickness of the mover's stroke
    point(this.position.x, this.position.y); // Draw the mover as a small point on the screen
  }
}

Key Feature in the Code

// Change the color of the mover based on its speed (faster movers turn blue, slower ones stay yellow)
let speed = this.velocity.mag(); // Calculate how fast the mover is going
let r = map(speed, 0, this.maxSpeed, 255, 0);  // Red decreases as speed increases
let g = map(speed, 0, this.maxSpeed, 255, 0);  // Green decreases as speed increases
let b = map(speed, 0, this.maxSpeed, 0, 255);  // Blue increases as speed increases
this.color = color(r, g, b); // Transition from yellow to blue based on speed

As movers speed up, they change from yellow (slow) to blue (fast), creating a clear and beautiful visual representation of their velocity. This color change adds another layer of artistic depth to the simulation, making the movers look like celestial objects glowing as they move faster.

Challenges and Solutions

One of the challenges was balancing the forces so that movers didn’t either get stuck near the attractors or fly off the canvas too quickly. Tuning the strength of both attraction and turbulence forces was the solution. I also implemented the edge wrapping mechanism to prevent movers from disappearing off-screen, which greatly improved the overall flow of the design.

The most rewarding part of the project was seeing how the movers interacted with the invisible attractors and how the random turbulence affected their paths. This gave the final design an organic, flowing feel—something I had aimed for from the beginning.

The only thing I am still unclear about accomplishing is the color change due to my colorblindness; I have zero clue if that function works. But hopefully, it looks as beautiful to you as it does to me.

Future Improvements:

  1. Interactivity: In the future, I would like to add interactivity by allowing users to click and place new attractors on the canvas in real time, changing the flow of the movers.
  2. Sound Design: Pairing the visual elements with generative ambient sounds based on the movement and speed of the movers would create an immersive experience.

Resource: 

https://p5js.org/reference/#/p5/map

Week 3_Electrons

Concept:

For this week’s assignment, I used forces to control electrons moving around their nucleus. In this case, the electrons are the visible movers and the nucleus (with neutrons and protons) is the invisible attractor.

 

Atomic structure explained: The building blocks of matter - Times of India
This image shows the structure of an Atom for more clarification.

Embedded Sketch:

Code Highlight:

// Showing and Updating the movers
for (let i = 0; i < 4; i++) {
  movers[i].update();
  movers[i].show();
  attractor.attract(movers[i]);
}

In this part of the code, I added a for loop to keep showing the movers and updating them, as well as make them go towards the attractor which is centered in the middle of the canvas. I added a seperate class for each, the mover and the attractor.

By changing the value 4 into a lower value, you can see less electrons moving towards the invisible nucleus.

Reflections & Improvement:

I faced a few difficulties while placing the movers in a nice pattern and giving them specific velocity values. For that, I referred to this video which helped me figure that out.

If I were to improve this assignment, I would definitely add more movers and give the user the option to select how many electrons they want to see moving according to whatever chemical element they want to visualize.

Week #3 – Magnetic Waves

Introduction

For this week, I initially planned to create marbles falling from a platform and landing on a magnetic field, where they would accumulate over time and collide with each other. At first, this idea seemed interesting to implement, but once I realized it was going to take more time than I originally planned, I had to scale down my ambition.

However, while trying to implement the original idea, I discovered an interesting behavior between three attractors. That’s when I decided to create the following sketch:

Note: You can interact by holding the left mouse button in the Canva.

The concept

As mentioned, the idea transitioned from a fun physics simulation with marbles and magnetic fields to a set of movers accumulating around randomly defined attractors.

There are 3 attractors in total, each with a random X and Y position determined by the width and range of the canvas. Additionally, the movers can collide with the borders of the scenario, and they also have friction as an extra feature.

Initially, the 3 attractors were fixed, as shown in the following full-screen sketch:  Test #1 of Assignment #3. However, this setup was not dynamic enough, and the movers never collided with the borders of the screen. Therefore, it was necessary to make the attractors’ position constantly random.

As an additional note, the colors of each mover are defined by Perlin noise.

Highlight of the code I am proud of

One segment of code I am most proud of is the following:

This part of the code is found in the file sketch.js, inside the draw() function.

if (attractors[0].g > 0){
        attractors[0].g -= 1;
        attractors[1].g += 1;
        
    } else if (attractors[1].g > 0){
        attractors[1].g -= 1;
        attractors[2].g += 1;
        

    } else if (attractors[2].g > 0){
        attractors[2].g -= 1;
        
      
    //Reset first value and the rest of positions of the attractors.
    } else {
        attractors[0].position.x = random(width);
        attractors[0].position.y = random(height);


        attractors[1].position.x = random(width);
        attractors[1].position.y = random(height);

        attractors[2].position.x = random(width);
        attractors[2].position.y = random(height);

        attractors[0].g = 70;
        attractors[2].g = 0;
    }
}

What this process does is, depending on the movers’ positions, increase the universal gravitational constant (G) of the next attractor while decreasing that of the current one, creating a smooth transition between attractors. When the final attractor is reached and its gravitational constant is reduced to 0, the process restarts by randomly assigning new positions to each attractor and redefining their gravitational constants to prevent any issues.

Reflection and ideas for future improvements

I initially thought it would take me more time to grasp the concepts. While there are interesting patterns I would love to create in the future with the movers and attractors, for now, I am glad I am understanding the concepts presented in class, which allows me to experiment with them. That said, here are some improvements I would like to explore in the future:

      • Experiment with parallel attractors and movers. In other words, have two sets of attractors and movers that can interact with each other, instead of a single group of movers interacting with two attractors at a time.
      • Use the collisions and gravity more creatively to add complexity to the designs.
      • Add more user interaction to manipulate the canvas, along with clear visual cues.

References

Most of the code that I used as a guide is from the following video (which is also provided in the weekly schedule):  Mutual Attraction by The Coding Train