How to move on a given sphere

edited April 2014 in How To...

Hello All,

This question may seem very basic, but after a few years of coding, and a few days of searching on the web, I have to admit that I failed to properly move an object on a sphere :'-( After developping a fairly cool 2D flocking system, I wanted to push it to the next dimension, having my boids move on a sphere.

Before interacting with each other, my boids should be able to move along a 'straight' axis on the sphere, which does not necessarily go through the pole(s). I guess that implies some kind of constant speed (radial or not) and that's where I get stuck. I found very few topics on the web about that, mentionning quaternions, spherical coordinates and such, but I couldn't figure it out anyway.

Please share your thoughts about this, any comment is welcome :-)

Answers

  • Before interacting with each other, my boids should be able to move along a 'straight' axis on the sphere, which does not necessarily go through the pole(s).

    draw us a picture, show us what you mean.

    i'd be tempted to extend the 2d to 3d and then just map the 3d to a sphere by normalising the distances from the origin. won't be totally accurate, will probably look ok.

  • Your projection idea is worth a try, I'll let you know when I implement it. About your question, imagine a satellite rotating around the earth. It could look like something like this: abstract-sphere2

    Or, as I tried to explain in my previous post, the trajectory could be even simpler, and remain similar throughout time, along the equator for instance (a sphere has an infinite number of "equators", that's what I tried to express above).

  • ok, so all those circles are centred on the origin, got it.

    oh, and this is before interacting? i think i missed that bit before. don't they always interact?

    orbits are easy enough - have a particle moving along the equator and then rotate the equator (in two directions). will post code (when i've written it)

  • Answer ✓
    // acd 2014, random orbits
    // everything orbits around the dark sphere in the middle
    import peasy.*;
    import processing.opengl.*;
    
    private final static float RAD = 200;
    private final static int ORBITS = 10;
    
    PeasyCam cam;
    Orbit[] orbits = new Orbit[ORBITS];
    
    void setup() {
      size(600, 600, OPENGL);
      cam = new PeasyCam(this, 500);
      sphereDetail(10);
      for (int i = 0 ; i < ORBITS ; i++) {
        orbits[i] = new Orbit();
      }
    }
    
    void draw() {
      background(0);
      sphereDetail(20);
      stroke(64, 64, 64);
      fill(64, 64, 64);
      sphere(10);
      sphereDetail(10);
      for (Orbit o : orbits) {
        o.move();
        o.draw();
      }
    }
    
    class Orbit {
      float ay, az;
      float angle, delta;
      int r, g, b;
    
      Orbit() {
        ay = random(TWO_PI);
        az = random(TWO_PI);
        angle = random(TWO_PI);
        delta = random(-.02, .02);
        r = (int)random(128, 256);
        g = (int)random(128, 256);
        b = (int)random(128, 256);
      }
    
      void move() {
        angle += delta;
      }
    
      void draw() {
        // move around the equator
        PVector pos = new PVector(RAD * cos(angle), RAD * sin(angle), 0);
        // rotate equator in two direction
        myRotateY(pos, ay);
        myRotateZ(pos, az);
        // draw
        stroke(r, g, b);
        fill(r, g, b);
        pushMatrix();
        translate(pos.x, pos.y, pos.z);
        sphere(10);
        popMatrix();
      }
    
      // rotate around y  
      void myRotateY(PVector p, float a) {
        float x = p.x * cos(a) + p.z * sin(a);
        float y = p.y;
        float z = p.x * -sin(a) + p.z * cos(a);
        p.set(x, y, z);
      }
    
      // rotate around z 
      void myRotateZ(PVector p, float a) {
        float x = p.x * cos(a) + p.y * sin(a);
        float y = p.x * -sin(a) + p.y * cos(a);
        float z = p.z;
        p.set(x, y, z);
      }
    }
    
  • Your code is realy nice'n easy koogs, thx for sharing I'll use it some day for sure. However, that said I wouldn't know how to have my boids interact with each other now; Except if, as you mentionned, I dealt with them as if in a 3D environment, then mapping them onto a sphere again. Before reading your code, that's what I tried to do and here's what it looks like:

    final int NB_BIRDIES_MAX = 750;
    final int NB_BIRDIES_MIN = 20;
    final static int SPHERE_W = 380;
    int nbBirdies = 450;//(int)random(NB_BIRDIES_MIN, NB_BIRDIES_MAX);
    float excludeStrength = .3;
    float includeStrength = .2;
    float mimeStrength = .1;
    float excludeZone = 80;
    float includeZone = 70;
    float mimeZone = 50;
    int profile = 0;
    Boolean isColorAlive = true;
    Boolean controlMode = false;
    int bgColor = 55;
    float R, G, B, Rspeed, Gspeed, Bspeed;
    Birdy[] birdies;
    
    void setup()
    {
      size(720, 530, P3D);
      birdies = new Birdy[NB_BIRDIES_MAX];
      initialize(); 
    
    }
    void initialize()
    {
      background(bgColor);
      for (int i = 0; i < NB_BIRDIES_MAX; i++)
      {
        birdies[i] = new Birdy(i);
      }
      generateColors();
    }
    void draw()
    {
      background(120);
      lights();
      translate(width/2, height/2);
      rotateX(map(mouseY, 0, height, 0, TWO_PI));
      rotateY(map(mouseX, 0, width, 0, TWO_PI));
    
      for (Birdy b : birdies)
      {
        b.behave();
        b.display();
      }
      fill(120, 150);
      noStroke();
      sphere(SPHERE_W/2);
      if (isColorAlive)
      {
        Rspeed = ((R += Rspeed) > 255 || (R < 0)) ? -Rspeed : Rspeed;
        Gspeed = ((G += Gspeed) > 255 || (G < 0)) ? -Gspeed : Gspeed;
        Bspeed = ((B += Bspeed) > 255 || (B < 0)) ? -Bspeed : Bspeed;
      }
    }
    void generateColors()
    {
      R = random(255);
      G = random(255);
      B = random(255);
      Rspeed = (random(1) > .5 ? 1 : -1) * random(.5, 1.2);
      Gspeed = (random(1) > .5 ? 1 : -1) * random(.5, 1.2);
      Bspeed = (random(1) > .5 ? 1 : -1) * random(.5, 1.2);
    }
    
    class Birdy
    {
      final static float BOID_RAD = 2;
      final static float MAX_SPEED = 1;
      final static int LINE_LENGTH = 35;
    
      int rank;
      PVector acc;
      PVector speed = new PVector(random(-1, 1), random(-1, 1), random(-1, 1));
      PVector pos = new PVector(random(SPHERE_W), random(SPHERE_W), random(SPHERE_W));
      PVector[] pts = new PVector[LINE_LENGTH];
    
      Birdy(int p_rank)
      {
        rank = p_rank;
        for (int i = 0; i < LINE_LENGTH; i++)
        {
          pts[i] = new PVector(SPHERE_W/2, SPHERE_W/2, SPHERE_W/2);
        }
      }
      void behave()
      {
        acc = new PVector(0, 0, 0);
        int countExclude = 0;
        int countInclude = 0;
        int countMime = 0;
        PVector exclusion = new PVector(0, 0, 0);
        PVector inclusion = new PVector(0, 0, 0);
        PVector mime = new PVector(0, 0, 0);
        for (int i = 0; i < nbBirdies; i++)
        {
          if (i != rank)
          {
            Birdy nextBirdy = birdies[i];
            float dist = PVector.dist(pos, nextBirdy.pos);
            /////////////////////////////////////////////////
            //exculsion
            if (dist < excludeZone)
            {
              PVector diff = pos.get();      
              diff.sub(nextBirdy.pos);
              diff.normalize();
              exclusion.add(diff);
              countExclude ++;
            }        
            /////////////////////////////////////////////////
            //inclusion
            if (dist < includeZone)
            {
              inclusion.add(nextBirdy.pos);
              countInclude ++;
            }
            /////////////////////////////////////////////////
            //mime
            if (dist < mimeZone)
            {
              mime.add(nextBirdy.speed);
              countMime ++;
            }
          }
        }
        if (countExclude > 0)
        {
          exclusion.div(countExclude);
          exclusion.mult(excludeStrength);
          acc.add(exclusion);
        }
        if (countInclude > 0)
        {
          inclusion.div(countInclude);
          inclusion.sub(pos);
          inclusion.normalize();
          inclusion.mult(includeStrength);
          acc.add(inclusion);
        }
        if (countMime > 0)
        {
          mime.div(countMime);
          mime.mult(mimeStrength);
          acc.add(mime);
        }
      }
      void display()
      {
        speed.add(acc);
        speed.limit(MAX_SPEED);
        pos.add(speed);
    
        pushMatrix();
        strokeWeight(1.5);
        stroke(200);
        fill(R, G, B);    
    
        /////////////////////////
        /////////////////////////   
        PVector p = pos.get();//or use directly pos
        float l = p.mag();
        p.sub(new PVector(SPHERE_W/2, SPHERE_W/2, SPHERE_W/2));
        p.normalize();
        p.mult(SPHERE_W/2);
        translate(p.x, p.y, p.z);
        PVector.add(p, new PVector(SPHERE_W/2, SPHERE_W/2, SPHERE_W/2), pos);
        /////////////////////////
        /////////////////////////
        //translate(pos.x, pos.y, pos.z);
        ellipse(0, 0, BOID_RAD, BOID_RAD);
        popMatrix();
      }
    }
    
  • edited April 2014

    yeah, i'm not sure that my code was ever going to butt* with your code at all but if it's useful in any way...

    the "Before interacting with each other" thing is puzzling because i thought that boids were always interacting with each other... (ah, it's a distance thing)

    your sketch looks a bit odd but i can't put my finger on why. something about the movement? might be the projection... i see what you mean about the unaffected birds, those flying around on their own - erratic.

    the big spike in the main sphere is a known problem. i think the solution is to use a unit sphere that's scaled rather than a sphere with a given radius. (ok, fixed in processing2 by the looks. or maybe i just can't see it as the sphere is now opaque)

    ellipse in line 172 is notoriously slow as well, maybe use a triangle?

    you are also checking all the boids against all the other boids including the ones you've already updated. might cause problems.

    (* "to place end to end or side to side without overlapping" *2)

    (*2 "Butt is currently in the top 30% of lookups on Merriam-Webster.com." - slow dictionary day!)

  • What I meant by "Before interacting with each other", is that boids should have kinda 'regular' speed (which in your code is the 'angle' variable), a speed that could be modified by the other boids. In your case, other boids should have an impact on 'angle', as well as 'ay' and 'az' I believe.

    Otherwise, you're totally right about "you are also checking all the boids against all the other boids including the ones you've already updated. might cause problems." When rectified, things seem to go a bit better, thanks again :)>-

  • After wandering on other sketches, here is the final result, quite close to what I expected <):) http://www.openprocessing.org/sketch/147633

Sign In or Register to comment.