Interactive 3D balls. Is this even possible in Processing?

Hi while I’m fairly new to processing, I was wondering if this is possible and how I would go about it?
Example: 3D interactive balls - Webflow

I’d like to recreate that effect in Processing 3D to embed the interactive balls as the landing page of my website. (Beginner friendly)

2 Likes

Hello @fijthecreator
Without the inclusion of code in your question, It’s a bit difficult to give advice on how to proceed.
Both topics you have posted are somewhat more advanced than the basics.
Please provide more information on how you would begin.
:nerd_face:

1 Like

It certainly is possible!

I would use p5.js if you want to embed in your website.

Start here:

:)

Thanks for the response! After first feedback provided by @glv I got started with p5.js and this is my first attempt of the moving balls:

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  
  //let xPos = 400
  /*circle(300,70,50) 
  circle(280,200,150)
  circle(250,150, 85)
  circle(310,110, 65)
  circle(270,250, 90)
  */
  
  let ypos = map(mouseX,0,width,75,100)
  circle(300,ypos,50) 
  //circle(280,200,150)
    
  if (mouseX < 200) {
    
    circle(250,150, 85)
    circle(310,110, 65)
    circle(270,250, 90);  
  }
  else {
    //circle(300,xPos + 10,50) 
    //circle(280,xPos + 10,150)
    //circle(250,xPos + 10, 85)
   // circle(310,xPos + 10, 65)
   // circle(270,xPos + 10, 90) 
  }
}

It is still a lot to wrap my head around, but maybe you could show me how it is done, what function to best use or any other advice! :slight_smile:

Hello and welcome.
A little starting point for you.

function setup() {
  //to make 3d you need WEBGL
  createCanvas(600, 600, WEBGL);
}

function draw() {
  background(220);
  //a handy method
  // click and drag to move the camera
 // mouse wheel to zoom
  orbitControl();

  // I'm storing big ball coordinates in a 3D p5Vector
  const big_ball = createVector(150, -100, 0);
  
  // 2D distance from mouse and the vector, just a quick an dirty example
  const d = dist(mouseX, mouseY, big_ball.x, big_ball.y)/50;

  //3d shapes are created at 0,0,0
  box(20);

  //and we move them with tranformations
  //this push() isolate transformations
  push();
  // again in a quick way, using dist from mouse to move the balls
  translate(big_ball.x - d, big_ball.y + d/2, big_ball.z);
  sphere(85);
  //pop is counterpart of push(), close the "parenthesis"
  pop();
  
  // the other ball, let;s also move it a little :P
  push();
  translate(d/2, -120, 90);
  sphere(45);
  pop()
    
  //the axis so you can see :)
  line(0,-500,0, 0, 500, 0);
  line(-500, 0, 0,500,0, 0);
  line(0, 0, -500, 0, 0, 500);
  
  
}

Note how easy is to read proper formatted code. Please when posting code use the code button so we can read it easily.
cheers

2 Likes

Thank you so so much. This is a great starting point to explore further the effect I’m trying to recreate! For further code submission, I will definitely follow along with the formatted code. Sorry for that once again.

1 Like

After more and more experimentation, and adding additional spheres with their individual behavior, I tried to texture the spheres and create shadows with the use of lights (pointlight/ ambientmaterial). Additionally, I have added an additional function to implement a gradient background that fills the screen no matter the screen size.
Unfortunately, the outcome resulted in something funky. Is anybody able to help with texturing, lighting, and adding a gradient background to my artwork? Ideally it would look something like this:

Here is my current code:

function setup() {
  //to make 3d you need WEBGL
  createCanvas(windowWidth, windowHeight, WEBGL);
}

function draw() {
  background(220);
  
  // gradient background
  var color1 = color(211, 211, 211);
  var color2 = color(255, 255, 255);
  setGradient(0, 0, windowWidth, windowHeight, color1, color2, "Y");
  
  pointLight(255, 0, 0, 200, 0, 0);
  //pointLight(0, 0, 255, -200, 0, 0);
  noStroke();
  //a handy method
  // click and drag to move the camera
 // mouse wheel to zoom
  orbitControl();

  // I'm storing big ball coordinates in a 3D p5Vector
  const big_ball = createVector(150, -100, 0);
  
  // 2D distance from mouse and the vector, just a quick an dirty example
  const d = dist(mouseX, mouseY, big_ball.x, big_ball.y)/50;

  //3d shapes are created at 0,0,0
  //box(20);

  //and we move them with tranformations
  //this push() isolate transformations
  push();
  // again in a quick way, using dist from mouse to move the balls
  translate(big_ball.x - d, big_ball.y + d/2, big_ball.z);
  normalMaterial();
  sphere(95);
  //pop is counterpart of push(), close the "parenthesis"
  pop();
  
  // the other ball, let;s also move it a little :P
  push();
  translate(d/2, -120, 90);
  ambientMaterial(255);
  sphere(45);
  pop()
  
  // third ball
  push();
  translate(d*2, -20 + d , 100)
  normalMaterial();
  sphere(60);
  pop()
  
  // fourth ball
  push();
  translate(90 + d, -15 + d , 250)
  normalMaterial();
  sphere(20);
  pop()
  
  // fifth ball
  push();
  translate(90, 15 + d , 100)
  normalMaterial();
  sphere(20);
  pop()
  
  // sixth ball
  push();
  translate(90 - d, 30 + d/2 , 200)
  normalMaterial();
  sphere(5);
  pop()
  
  // seventh ball
  push();
  translate(240 + d*2, -200 - d, -150)
  normalMaterial();
  sphere(80);
  pop()
  
  
    
  //the axis so you can see :)
 // line(0,-500,0, 0, 500, 0);
 // line(-500, 0, 0,500,0, 0);
 // line(0, 0, -500, 0, 0, 500);
  
  
}

function setGradient(x, y, w, h, c1, c2, axis) {
  noFill();
  if (axis == "Y") {  // Top to bottom gradient
    for (let i = y; i <= y+h; i++) {
      var inter = map(i, y, y+h, 0, 1);
      var c = lerpColor(c1, c2, inter);
      stroke(c);
      line(x, i, x+w, i);
    }
  }  
  else if (axis == "X") {  // Left to right gradient
    for (let j = x; j <= x+w; j++) {
      var inter2 = map(j, x, x+w, 0, 1);
      var d = lerpColor(c1, c2, inter2);
      stroke(d);
      line(j, y, j, y+h);
    }
  }
}

It might be worth mentioning that, in the Webflow example you linked, the effect is accomplished without any 3D or even JavaScript.

The shapes are made with divs and color gradients…

From what I could gather from looking at the source code, the whole thing is done in pure CSS and HTML which is pretty impressive. In other words: it is “fake” 3D.

Reproducing the exact same look in p5.js with WebGL is possible, but it might not be as beginner friendly as you hoped, and you might have an easier time faking it in 2d mode, either with gradients and blur or even loading image assets for the “spheres”.

1 Like

Thanks a lot for your feedback @sableRaph!
I continued working on it, and I got quite close to the result that I’m aiming for. But while I did so, I’m not quite sure if I followed the best practices. If you have any additional recommendations based on my latest version/ progress please let me know.

How it looks now:

And the matching code:

var img;
function preload() {
  img = loadImage("White-gradient3 Klein.jpeg");
}


function setup() {
  //to make 3d you need WEBGL
  createCanvas(900, 600, WEBGL);
}

function draw() {
  background(255);
  
  orbitControl();
  push();
  translate(0, 0, -300)
  noStroke();
  texture(img);
  plane(1500, 1000);
  pop();
  
  pointLight(255, 255, 255, -300, -300, 150);
  //pointLight(255, 255, 255, 100, 100, 500);
  ambientLight(90);
  noStroke();
  
  // I'm storing big ball coordinates in a 3D p5Vector
  const big_ball = createVector(150, -100, 0);
  
  // 2D distance from mouse and the vector, just a quick an dirty example
  const d = dist(mouseX, mouseY, big_ball.x, big_ball.y)/50;

  //3d shapes are created at 0,0,0
  //box(20);

  //and we move them with tranformations
  //this push() isolate transformations
  push();
  // again in a quick way, using dist from mouse to move the balls
  translate(big_ball.x - d, big_ball.y + d/2, big_ball.z);
  specularMaterial(255, 100, 50);
  shininess(20); 
  sphere(95);
  //pop is counterpart of push(), close the "parenthesis"
  pop();
  
  // the other ball, let;s also move it a little :P
  push();
  translate(d/2, -120, 90);
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(45);
  pop()
  
  // third ball
  push();
  translate(d*2, -20 + d , 100)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(60);
  pop()
  
  // fourth ball
  push();
  translate(90 + d, -15 + d , 250)
  specularMaterial(255, 100, 50);
  shininess(20)
  sphere(20);
  pop()
  
  // fifth ball
  push();
  translate(90, 15 + d , 100)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(20);
  pop()
  
  // sixth ball
  push();
  translate(90 - d, 30 + d/2 , 200)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(5);
  pop()
  
  // seventh ball
  push();
  translate(240 + d*2, -200 - d, -150)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(80);
  pop()
  
  
    
  //the axis so you can see :)
 // line(0,-500,0, 0, 500, 0);
 // line(-500, 0, 0,500,0, 0);
 // line(0, 0, -500, 0, 0, 500);
  
  
}
1 Like

You could get a closer result using a different material. specularMaterial() gives a shiny aspect that is quite different from the reference.

Have a look at this example:

To place one ball on the surface of another you just need to add their respective radius together. This will be the distance between their center points.

You can place the center ball at the (0, 0, 0) coordinate. For the other balls you generate a 3d vector with random (X,Y,Z) components, where each component is a value between -1 and +1. You then pass this vector to the normalize() function which will scale the vector to be of length 1, this will give you a random direction vector. You then again scale the vector using the distance (explained above) and the resulting vector will represent the center point of a ball sitting at a random location on the surface.

Note that this also works for balls not orbiting the origin, by adding this vector to the center point of another ball you can move the ball to its surface.

Hi Pieter, that sounds really interesting.
I was looking for ways to include randomness in the artwork in order to separate it from a simple animation. I’m really interested in how your approach would look like but unfortunately, I do not know how to go about it. Any possibility to give me a sneak peek on how I could go about it?

1 Like

Hi Community,
After experimenting for days, I tried to make the created artwork generative and utilize functions as random(), for example, to generate different sphere sizes, and make the spheres appear at a different location each time the artwork is loaded new.
Unfortunately, I always ran into issues where the balls flickered and switch sizes each frame. Is anyone able to help, or give me a good starting point for further exploration?

Hello @fijthecreator
Can you show us where you’ve placed the random function(s) in your code so we can run it?
It’s much easier to help when we can see what’s happening. :slightly_smiling_face:
:nerd_face:

1 Like

Of course, while all of my approaches ended up flickering, I will provide you with the simple idea I had in mind to add a little bit of generative behavior to my artwork. Furthermore, I tried to declare a variable and insert the variable inside sphere() but the same flicker effect was triggered.

If you additionally have any ideas, recommendations on how to add generative behavior (location of spheres, sphere sizes, etc.) I’d highly appreciate any inspirations.

var img;
function preload() {
  img = loadImage("White-gradient3 Klein.jpeg");
}


function setup() {
  //to make 3d you need WEBGL
  createCanvas(900, 600, WEBGL);
}

function draw() {
  background(255);
  
  orbitControl();
  push();
  translate(0, 0, -300)
  noStroke();
  texture(img);
  plane(1500, 1000);
  pop();
  
  pointLight(255, 255, 255, -300, -300, 150);
  //pointLight(255, 255, 255, 100, 100, 500);
  ambientLight(90);
  noStroke();
  
  // I'm storing big ball coordinates in a 3D p5Vector
  const big_ball = createVector(150, -100, 0);
  
  // 2D distance from mouse and the vector, just a quick an dirty example
  const d = dist(mouseX, mouseY, big_ball.x, big_ball.y)/50;

  //3d shapes are created at 0,0,0
  //box(20);

  //and we move them with tranformations
  //this push() isolate transformations
  push();
  // again in a quick way, using dist from mouse to move the balls
  translate(big_ball.x - d, big_ball.y + d/2, big_ball.z);
  specularMaterial(255, 100, 50);
  shininess(20); 
  sphere(random(75-95));
  //pop is counterpart of push(), close the "parenthesis"
  pop();
  
  // the other ball, let;s also move it a little :P
  push();
  translate(d/2, -120, 90);
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(45);
  pop()
  
  // third ball
  push();
  translate(d*2, -20 + d , 100)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(60);
  pop()
  
  // fourth ball
  push();
  translate(90 + d, -15 + d , 250)
  specularMaterial(255, 100, 50);
  shininess(20)
  sphere(20);
  pop()
  
  // fifth ball
  push();
  translate(90, 15 + d , 100)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(20);
  pop()
  
  // sixth ball
  push();
  translate(90 - d, 30 + d/2 , 200)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(5);
  pop()
  
  // seventh ball
  push();
  translate(240 + d*2, -200 - d, -150)
  specularMaterial(255, 100, 50);
  shininess(20);
  sphere(80);
  pop()
  
  
    
  //the axis so you can see :)
 // line(0,-500,0, 0, 500, 0);
 // line(-500, 0, 0,500,0, 0);
 // line(0, 0, -500, 0, 0, 500);
  
  
}