Trying to Draw on Sphere

Hey! I’m new here. I’m trying to draw on a sphere. I have basically no idea how.

The sphere rotates when you move the mouse. I want you to be able to draw on the sphere when you hold down the mouse. The sphere stops moving when you hold the mouse down.

I’m really lost trying to do this. The only idea I currently have is really lame and a work around: using directional light. If I can get the surface to react to a certain concentration of directional light, I should be good. The code is super simple for lights like this, super simple to get them to follow the mouse. But I can’t figure out how to get objects to react to light.

The other option is figuring out the math and I have no idea what to do.

Help would be greatly appreciated : |

P.S: I’m slightly simplifying the problem as I want objects on the surface of the sphere within a certain radius of my mouse to react when the mouse is held down. But this is basically drawing, very similar.

Edit: I can also post the example code if that would help.

Share your code. Make sure you format it with the </> button.

This is an edit of esfera which comes with processing. I want the hairs to grow only when you are holding down near them with the mouse. Right now all of them on the globe grow at once. I want to apply my grow function only to the hairs near where you click the mouse.

Edit: The findPelos() method will hopefully eventually return an arrayList of the Pelos near the mouse. But I don’t know how to figure out how the mouse coordinates translate onto the sphere.

If there is a simple way for objects to react to light, then I know how I can do it painlessly.

The code is fairly complicated but really if I can figure out how to map the mouse position onto the sphere in any way, I should be fine. But I can’t figure it out.

import java.util.Iterator; 
import java.lang.Math;

/**
 * Esfera
 * by David Pena.  
 * 
 * Distribucion aleatoria uniforme sobre la superficie de una esfera. 
 */

int cuantos = 40000;
ArrayList<Pelo> lista;
float radio = 400;
float rx = 0;
float ry =0;

void setup() {
  //size(1024, 1000, P3D);
  fullScreen(P3D, 1);
  

  radio = height/3.5;

  lista = new ArrayList<Pelo>();
  for (int i = 0; i < cuantos; i++) {
    Pelo p = new Pelo();
    lista.add(p);
  }
  noiseDetail(3);
}

void draw() {
  background(0);
  radio = 400;
  //print( "x: " + mouseX + " y: " + mouseY, mouseX, mouseY );
  
  
  if (!mousePressed) { 
  //velocity?
  float rxp = (mouseX-(width/2)) * 0.005;
  float ryp = (mouseY-(height/2)) * 0.005;
  rx = rx*0.9 + rxp*0.1;
  ry = ry*0.9 + ryp*0.1;
  }

  translate(width/2, height/2);
  rotateY(rx);
  rotateX(ry);
  fill(0);
  noStroke();
  sphere(radio);
  
  Iterator itr = lista.iterator();
  ArrayList<Pelo> removeThese = new ArrayList<Pelo>();
    
  while (itr.hasNext()) {
    Pelo p = (Pelo) itr.next(); 
      
    if (p.dead) {
      removeThese.add(p);
    } else {
      p.dibujar();
      grow(p);
    }
  }
  
  for (Pelo p : removeThese) {
    lista.remove(p);
  }
  
}

void keyPressed() {
  if (key == ' ') {
    reset();
  }
}

 void grow(Pelo p) {
   ++p.age;
   if (p.dying) {
     if (p.largo > .84) {
     p.largo -= p.growthRate * .01;
   } else {
     p.largo = 0;
     p.c = color(0,0,0);
     p.dead = true;
   }
  } else {
      if (p.largo < p.maxHeight) {
        p.largo += p.growthRate * .05;
        p.thickness += p.growthRate * .025;
      }
      
      if ((p.type == 1) && (p.age >= 250)) {
        p.dying = true; 
        p.c = color(79, 34, 9);
        
      } else if ((p.type == 2) && (p.age >= 300)) {
        p.dying = true; 
        p.c = color(135, 206, 235);
        
      } else if ((p.type == 3) && (p.age >= 160)) {
        p.dying = true; 
        p.c = color(135, 206, 235);
        
      } else if ((p.type == 4) && (p.age >= 160)) {
        p.dying = true; 
        p.c = color(139, 69, 19);
        
      } else if ((p.type == 5) && (p.age >= 140)) {
        p.dying = true; 
        p.c = color(139, 69, 19);
        
      } else if ((p.type == 7) && (p.age >= 1200)) {
        p.dying = true; 
        p.c = color(10, 10, 10);
      } 
  }
}

//should calculate the position on the sphere corresponding to your mouse position and then find the pelos within a certain radius of that point

ArrayList<Pelo> findPelos(){
  float realX = mouseX - (width/2);
  float realY = mouseY - (height/2); 
  double realZ = Math.sqrt(Math.pow(radio, 2) + Math.pow(realX, 2) + Math.pow(realY, 2));
  
  
  return new ArrayList<Pelo>();
  
}


class Pelo
{
  float z = random(-radio, radio);
  float phi = random(TWO_PI);
  float largo = random(.85, .90);
  float theta = asin(z/radio);
  float rand = random (0.00, 1.01);
  int type;
  
  int age = 0;
  boolean dying = false;
  boolean dead = false; 
  color c; 
  float thickness;
  float maxHeight; 
  float growthRate;


  Pelo() { // what's wrong with a constructor here
    z = random(-radio, radio);
    phi = random(TWO_PI);
    largo = random(.85, .90);
    theta = asin(z/radio);
    rand = random (0.00, 1.02);
    age = 0;
    dying = false;
    dead = false;
    
    
    if (rand <= .75) { //tree
      type = 1;
      c = color(0, 100, 0); 
      thickness = 1.5;
      maxHeight = 1.1; 
      growthRate = .1;
    
    } else if (rand <= .90) { //blue tree
      type = 2;
      c = color(0, 0, 100); 
      thickness = 1.2;
      maxHeight = 1.1; 
      growthRate = .1;
  
    } else if (rand <= .95) { //red flower
      type = 3;
      c = color(240, 0, 0); 
      thickness = 1;
      maxHeight = .01; 
      growthRate = .07;
  
    } else if (rand <= .99) { //yellow flower
      type = 4;
      c = color(240, 240, 0); 
      thickness = .8;
      maxHeight = .05; 
      growthRate = .08;
  
    } else if (rand <= 1.00) { //orange up shoot
      type = 5;
      c = color(255, 69, 0); 
      thickness = .9;
      maxHeight = 1.4; 
      growthRate = 3;
    } else if (rand <= 1.005) { //rock
      type = 6;
      c = color(128, 128, 128); 
      thickness = 1;
      maxHeight = 0; 
      growthRate = 0;
    } else { //purple tree
      type = 7;
      c = color(50, 0, 50); 
      thickness = 5;
      maxHeight = 1.3; 
      growthRate = .007;
    }
  }

  void dibujar() {

    float off = (noise(millis() * 0.0005, sin(phi))-0.5) * .3;
    float offb = (noise(millis() * 0.0007, sin(z) * 0.01)-0.5) * .3;

    float thetaff = theta+off;
    float phff = phi+offb;
    float x = radio * cos(theta) * cos(phi);
    float y = radio * cos(theta) * sin(phi);
    float z = radio * sin(theta);

    float xo = radio * cos(thetaff) * cos(phff);
    float yo = radio * cos(thetaff) * sin(phff);
    float zo = radio * sin(thetaff);

    float xb = xo * largo;
    float yb = yo * largo;
    float zb = zo * largo;

    strokeWeight(thickness);
    beginShape(LINES);
    stroke(c);
    vertex(x, y, z);
    stroke(c, 50);
    vertex(xb, yb, zb);
    endShape();
  }
}

void reset() {
  //lista = new Pelo[cuantos];
  //for (int i = 0; i < lista.length; i++) {
  //  lista[i] = new Pelo();
  //}
  setup();
}

Here’s one approach you can take.

import java.util.Iterator; 
import java.lang.Math;

/**
 * Esfera
 * by David Pena.  
 * 
 * Distribucion aleatoria uniforme sobre la superficie de una esfera. 
 */

int cuantos = 40000;
ArrayList<Pelo> lista;
float radio = 400;
float rx = 0;
float ry =0;

void setup() {
  //size(1024, 1000, P3D);
  fullScreen(P3D, 1);
  reset();
}

void reset() {
  radio = height/3.5;
  lista = new ArrayList<Pelo>();
  for (int i = 0; i < cuantos; i++) {
    Pelo p = new Pelo();
    lista.add(p);
  }
  noiseDetail(3);
}

void draw() {
  background(0);
  radio = 400;
  //print( "x: " + mouseX + " y: " + mouseY, mouseX, mouseY );


  if (!mousePressed) { 
    //velocity?
    float rxp = (mouseX-(width/2)) * 0.005;
    float ryp = (mouseY-(height/2)) * 0.005;
    rx = rx*0.9 + rxp*0.1;
    ry = ry*0.9 + ryp*0.1;
  }

  translate(width/2, height/2);
  rotateY(rx);
  rotateX(ry);
  fill(0);
  noStroke();
  sphere(radio);

  Iterator itr = lista.iterator();
  ArrayList<Pelo> removeThese = new ArrayList<Pelo>();

  while (itr.hasNext()) {
    Pelo p = (Pelo) itr.next(); 

    if (p.dead) {
      removeThese.add(p);
    } else {
      p.dibujar();
      grow(p);
    }
  }

  for (Pelo p : removeThese) {
    lista.remove(p);
  }
}

void keyPressed() {
  if (key == ' ') {
    reset();
  }
}

void grow(Pelo p) {
  
  if(!p.near) return;
  
  ++p.age;
  if (p.dying) {
    if (p.largo > .84) {
      p.largo -= p.growthRate * .01;
    } else {
      p.largo = 0;
      p.c = color(0, 0, 0);
      p.dead = true;
    }
  } else {
    if (p.largo < p.maxHeight) {
      p.largo += p.growthRate * .05;
      p.thickness += p.growthRate * .025;
    }

    if ((p.type == 1) && (p.age >= 250)) {
      p.dying = true; 
      p.c = color(79, 34, 9);
    } else if ((p.type == 2) && (p.age >= 300)) {
      p.dying = true; 
      p.c = color(135, 206, 235);
    } else if ((p.type == 3) && (p.age >= 160)) {
      p.dying = true; 
      p.c = color(135, 206, 235);
    } else if ((p.type == 4) && (p.age >= 160)) {
      p.dying = true; 
      p.c = color(139, 69, 19);
    } else if ((p.type == 5) && (p.age >= 140)) {
      p.dying = true; 
      p.c = color(139, 69, 19);
    } else if ((p.type == 7) && (p.age >= 1200)) {
      p.dying = true; 
      p.c = color(10, 10, 10);
    }
  }
}

//should calculate the position on the sphere corresponding to your mouse position and then find the pelos within a certain radius of that point

/*
ArrayList<Pelo> findPelos() {
  float realX = mouseX - (width/2);
  float realY = mouseY - (height/2); 
  double realZ = Math.sqrt(Math.pow(radio, 2) + Math.pow(realX, 2) + Math.pow(realY, 2));


  return new ArrayList<Pelo>();
}
*/

class Pelo
{
  float z = random(-radio, radio);
  float phi = random(TWO_PI);
  float largo = random(.85, .90);
  float theta = asin(z/radio);
  float rand = random (0.00, 1.01);
  int type;

  int age = 0;
  boolean dying = false;
  boolean dead = false; 
  color c; 
  float thickness;
  float maxHeight; 
  float growthRate;
  boolean near;

  Pelo() { // what's wrong with a constructor here - nothing.
    z = random(-radio, radio);
    phi = random(TWO_PI);
    largo = random(.85, .90);
    theta = asin(z/radio);
    rand = random (0.00, 1.02);
    age = 0;
    dying = false;
    dead = false;


    if (rand <= .75) { //tree
      type = 1;
      c = color(0, 100, 0); 
      thickness = 1.5;
      maxHeight = 1.1; 
      growthRate = .1;
    } else if (rand <= .90) { //blue tree
      type = 2;
      c = color(0, 0, 100); 
      thickness = 1.2;
      maxHeight = 1.1; 
      growthRate = .1;
    } else if (rand <= .95) { //red flower
      type = 3;
      c = color(240, 0, 0); 
      thickness = 1;
      maxHeight = .01; 
      growthRate = .07;
    } else if (rand <= .99) { //yellow flower
      type = 4;
      c = color(240, 240, 0); 
      thickness = .8;
      maxHeight = .05; 
      growthRate = .08;
    } else if (rand <= 1.00) { //orange up shoot
      type = 5;
      c = color(255, 69, 0); 
      thickness = .9;
      maxHeight = 1.4; 
      growthRate = 3;
    } else if (rand <= 1.005) { //rock
      type = 6;
      c = color(128, 128, 128); 
      thickness = 1;
      maxHeight = 0; 
      growthRate = 0;
    } else { //purple tree
      type = 7;
      c = color(50, 0, 50); 
      thickness = 5;
      maxHeight = 1.3; 
      growthRate = .007;
    }
  }

  void dibujar() {

    float off = (noise(millis() * 0.0005, sin(phi))-0.5) * .3;
    float offb = (noise(millis() * 0.0007, sin(z) * 0.01)-0.5) * .3;

    float thetaff = theta+off;
    float phff = phi+offb;
    float x = radio * cos(theta) * cos(phi);
    float y = radio * cos(theta) * sin(phi);
    float z = radio * sin(theta);

    float xo = radio * cos(thetaff) * cos(phff);
    float yo = radio * cos(thetaff) * sin(phff);
    float zo = radio * sin(thetaff);

    float xb = xo * largo;
    float yb = yo * largo;
    float zb = zo * largo;

    strokeWeight(thickness);
    beginShape(LINES);
    stroke(c);
    vertex(x, y, z);
    near = dist(mouseX, mouseY, screenX(x,y,z), screenY(x,y,z)) < 60;
    stroke(c, 50);
    vertex(xb, yb, zb);
    endShape();
  }
}

Here’s how this works: When you draw the line for a hair, you work out a position in space, (x,y,z), where the base of the hair is. Using screenX() and screenY(), it is possible to know where on the 2D screen that 3D point would be drawn. You can then check to see if that point is near the mouse pointer (mouseX, mouseY).

This check is happening for each hair. It’s this line:

    near = dist(mouseX, mouseY, screenX(x,y,z), screenY(x,y,z)) < 60;

So each hair now knows if it is near the mouse. Knowing this, each hair can decide not to grow if it is not near the mouse:

void grow(Pelo p) {
  if(!p.near) return; // Not near? No growing for you!

The downside to this is that it also grows the hairs near a spot on the opposite side of the ball. If this is not a problem for you, great!

You are, of course, free to use this near-to-mouse detecting abilities however you like. Maybe try drawing only the hairs near the mouse?

Oh, also I fixed how you should be doing your reset() function.

1 Like

This is great! Thank you so much.

Using this solution is there anyway to avoid hair growing on the back of the ball? I feel like there has to be a way to fix this issue. If not, is there another approach to this problem which could suffice? I feel like there has to be a work around avoiding this problem.

Edit: This is such a significant improvement, even with the issue. Thank you so much for the help.

There is a solution, yes, but sadly, the solution is “Do the math.” Since you know the exact position of each hair and the exact position that the ball is rotated, it is, theoretically, possible to determine if a hair is currently on the near or far side of the ball. But it’s the sort of problem people DON’T solve for fun…

Okay, thanks for the help and clarification. I don’t want to give up on fixing the problem if I can avoid it. I have a few more questions if you or someone else wants to answer:

  1. The z value used in screenX(x, y, z) is a z value in relation to the alignment of the sphere, correct? So this value doesn’t correspond to the z axis of the entire screen. Would it be possible to keep track of the rotation of the sphere’s z axis and then figure out the relationship between the relative z axis and the absolute z axis of the screen? Am I misunderstanding this relationship?

  2. Can I check a specific pixel on the screen for it’s color? If so there could probably be a way to tell if the line was drawn behind the sphere.

  3. Is there a simple way for light to interact with objects? I could have the back of the ball lit and not allow growth when an object is exposed to light.

I’m just trying to think of solutions/work arounds.

  1. Yes. But this is the same solution as “Do the math.”.

  2. Yes. You can use get(x,y), to get the color of the pixel at (x,y).

  3. Not that I am aware of.

Okay I’m trying to check the screen to see if the hair is currently being drawn.

near = ((dist(mouseX, mouseY, screenX(x,y,z), screenY(x,y,z)) < 60) 
&& ((pixels[(int) screenX(x,y,z) * width + (int) screenY(x,y,z)]) == c));
    //(get((int) screenX(x,y,z), (int) screenY(x,y,z)) == c))

I’m getting a null pointer exception.

The bottom commented section is an alternate bit of code. I read on get()'s page that using pixel[] was faster so I’m trying to use that. But I got the same error both ways.

Would this approach work? I know it would at least decrease the amount of growth on the back side.

Edit: Actually with the bottom code it’s not crashing, it’s just incredibly slow to the point of halting. It’s probably way too much work to check this much information.

With the top it was only crashing because I left out loadPixels(). But that line freezes when it is executed. When I put in updatePixels(), it takes about ten seconds for the simulation to move again. Both processes are very very slow. Also this is with the number of hairs dramatically decreased.

Hi CAUP,

the math for this kind of problem has been already covered plenty of time and you can find a lot of informations on the web.

For example you have this wipedia page:

You can directly apply what’s in this to get your points of intersection :slight_smile:

1 Like