Randomly placing and orienting ellipsoids in 3D space using Shapes 3D Library

edited March 2014 in Library Questions

Hello!

I am trying to randomly place and orient ellipsoids in 3D space using Shapes 3D Library. The ultimate goal is to model the random placement and orientation of synapses (components of brain cells) in 3D space.

Here is a drawing of what I hope to do

So far, I have been able to place just one ellipsoid in 3D space using Shapes 3D Library (see code below). I am unsure as to how to proceed to randomly place several ellipsoids.

Specifically, I am very confused about what parameters synapse.draw() can take. Could I input random x, y, and z values? What if I want to rotate the ellipsoids in random orientations?

Before you answer... I am not looking for a fully-coded answer to how to achieve my ultimate goal (this defeats the purpose of learning, I think). Rather, I am looking for some helpful advice on how to take the next steps going forward.

Note: I am using Mac OS X and Processing 2

// PeasyCam library for an easy-to-use camera
import processing.opengl.*;
import peasy.*;
PeasyCam cam;

// Shapes 3D library for creating 3D shapes like ellipsoids
import shapes3d.utils.*;
import shapes3d.animation.*;
import shapes3d.*;

// A "synapse" is part of a brain cell that can be modeled as an ellipsoid
Ellipsoid synapse;

void setup() {
  size(500, 500, OPENGL);
  cam = new PeasyCam(this, 100);
  cam.setMinimumDistance(50);
  cam.setMaximumDistance(500);

  synapse = new Ellipsoid(this, 15, 15);
  synapse.setRadius(15, 10, 10);
  synapse.fill(#19BF38);
  synapse.drawMode(Shape3D.TEXTURE);

}

void draw() {
  lights();
  directionalLight(255, 255, 255, -1, 0, 0); // Ambient white light
  background(0);
  synapse.draw();
}
Tagged:

Answers

  • you need to learn about transformations. http://processing.org/tutorials/p3d/

    to position the object in 3d space you need a translate: http://processing.org/reference/translate_.html

    to rotate it you use http://processing.org/reference/rotate_.html

    transformation are a bit hard to get used to. maybe you check out first how this is done in 2d space: http://processing.org/tutorials/transform2d/

  • Answer ✓

    Specifically, I am very confused about what parameters synapse.draw() can take. Could I input random x, y, and z values? What if I want to rotate the ellipsoids in random orientations?

    You need to look at the examples and reference that come with Shapes3D but just to give a brief explanation why the draw() method takes no parameters.

    Every shape that you create with Shapes3D stores its own current position and rotation in 3D space. The draw() method will draw the shape in its current position and with its current rotation. If you want it to appear somewhere else or with a different rotation then there are a whole bunch of methods that you can use to move and rotate the shape e.g. moveTo(), moveBy, rotateTo, rotateBy etc. You can find them in the Shape3D class API reference that comes with the library.

  • edited March 2014

    Thanks so much, @mschi and @quark !

    I think I got this all figured out except that... - My camera (using PeasyCam Library) does not work - All of my synapses are oddly clustered in the bottom right corner of the window

    Any help would be much appreciated!

    // Imports OpenGL library for 3D modeling
    import processing.opengl.*;
    
    // Imports PeasyCam library for an easy-to-use camera
    import peasy.*;
    PeasyCam cam;
    
    // Imports Shapes 3D library for creating 3D shapes like ellipsoids
    import shapes3d.utils.*;
    import shapes3d.animation.*;
    import shapes3d.*;
    
    // Declares an Ellipsoid from the Shapes 3D library and names it "synapse"
    // A "synapse" is part of a brain cell that can be modeled as an ellipsoid
    Ellipsoid synapse;
    
    // Sets dimensions of each synapse
    float semiaxisA = 20;
    float semiaxisB = 10;
    float semiaxisC = 10;
    
    // Creates arrays to store random coordinates of each synapse
    float[] x = new float[1];
    float[] y = new float[1];
    float[] z = new float[1];
    
    void setup() {
    
      // Sets size of window, background color, and framerate 
      size(500, 500, OPENGL);
      background(0);
      frameRate(1000);
    
      // Defines position and maximum/minimum distance of camera
      cam = new PeasyCam(this, 500);
      cam.setMinimumDistance(50);
      cam.setMaximumDistance(500);
    
      // Draws the first synapse and stores its random coordinates and angle orientations in the arrays
      // semiaxisA is used instead of semiaxisB or semiaxisC because it is the longest dimension of the synapse
      // This will ensure that no synapse overlaps with another synapse
      x[0] = random(semiaxisA, width/2-semiaxisA);
      y[0] = random(semiaxisA, height/2-semiaxisA);
      z[0] = random(semiaxisA, 500/2 - semiaxisA);
      drawSynapse(x[0], y[0], z[0]);
    }
    
    void draw() {
    
      // Creates directional light
      lights();
      directionalLight(255, 255, 255, -1, 0, 0);
    
      // Checks to see if there are less than 10 elements in the z coordinate array
      if(x.length < 10){
    
        // Generates random x, y, and z coordinates for a new synapse
        float tempX = random(semiaxisA, width/2-semiaxisA);
        float tempY = random(semiaxisA, height/2-semiaxisA);
        float tempZ = random(semiaxisA, 500/2-semiaxisA);
    
        // Checks to see if the generated x, y, and z coordinates are too close to any (x, y, z) coordinates in the arrays
        for(int i = 0; i < x.length; i++){
          if (sqrt(sq(x[i] - tempX) + sq(y[i] - tempY)+ sq(z[i] - tempZ)) < semiaxisA) {
            println("Too close to another synapse!");
            return;
          }
        }
    
        // Draws synapse using drawSynapse function and adds its coordinates to the x, y, and z coordinate arrays
        drawSynapse(tempX, tempY, tempZ);
        x = append(x, tempX);
        y = append(y, tempY);
        z = append(z, tempZ);
      }
    }
    
    // Defines function used to draw each synapse
    void drawSynapse(float x, float y, float z){
      pushMatrix();
    
      // Defines how many gridlines make up the synapse
      synapse = new Ellipsoid(this, 15, 15);
    
      // Sets size, color, texture, location, and orientation of the synapse to be drawn
      synapse.setRadius(semiaxisA, semiaxisB, semiaxisC);
      synapse.fill(#19BF38);
      synapse.drawMode(Shape3D.TEXTURE);
      synapse.moveTo(x, y, z);
      synapse.rotateBy(random(0,360.0), random(0,360.0), random(0,360.0));
    
      // Draws synapse
      synapse.draw();
      popMatrix();
    }
    
  • OK you are getting the idea but there are several mistakes/errors. Of these two are particularly important

    1) You create a single Ellipsoid and try and draw it in different locations. Instead you should create a new Ellipsoid for every location you want to see one.

    2) The drawSynapse method is being called every frame but a new Ellipsoid is being created in line 83. You should try and avoid creating complex objects inside the draw loop it will have significant impact on performance.

    The other mistakes are straightforward

    In Processing and this library angles are expressed in radians not degrees so instead of using the range 0-360 it should be 0-2 Pi.

    The reason the synapses all occur in one quadrant is because the calculated x/y/z values are positive and the origin (0,0,0) is in the centre of the display.

    Anyway I have recreated the sketch taking into account all these points so you have a good base to work from. PeasyCam also works in this example.

    // Imports OpenGL library for 3D modeling
    import processing.opengl.*;
    
    // Imports PeasyCam library for an easy-to-use camera
    import peasy.*;
    PeasyCam cam;
    
    // Imports Shapes 3D library for creating 3D shapes like ellipsoids
    import shapes3d.utils.*;
    import shapes3d.animation.*;
    import shapes3d.*;
    
    // Declares an Ellipsoid from the Shapes 3D library and names it "synapse"
    // A "synapse" is part of a brain cell that can be modeled as an ellipsoid
    int nbrSynapses = 10;
    Ellipsoid[] synapses;
    
    float maxDist = 100;
    
    // Sets dimensions of each synapse
    float semiaxisA = 20;
    float semiaxisB = 10;
    float semiaxisC = 10;
    
    void setup() {
      // Sets size of window, background color, and framerate
      size(500, 500, OPENGL);
      background(0);
      frameRate(1000);
      synapses = new Ellipsoid[nbrSynapses];
      for (int i = 0; i < synapses.length; i++) {
        synapses[i] = new Ellipsoid(this, 15, 15);
        synapses[i].setRadius(semiaxisA, semiaxisB, semiaxisC);
        synapses[i].fill(#19BF38);
        synapses[i].drawMode(Shape3D.TEXTURE);
        float x = random(-maxDist, maxDist);
        float y = random(-maxDist, maxDist);
        float z = random(-maxDist, maxDist);
        synapses[i].moveTo(x, y, z);
        synapses[i].rotateBy(random(0, TWO_PI), random(0, TWO_PI), random(0, TWO_PI));
      }
      // Defines position and maximum/minimum distance of camera
      cam = new PeasyCam(this, 500);
      cam.setMinimumDistance(50);
      cam.setMaximumDistance(500);
    }
    
    void draw() {
      background(0);
      // Creates directional light
      lights();
      directionalLight(255, 255, 255, -1, 0, 0);
      for (int i = 0; i < synapses.length; i++) {
        synapses[i].draw();
      }
    }
    

    BTW drawMode(Shape3D.TEXTURE); is only needed if you have set the texture using a bitmap image.

  • edited March 2014

    Hi @quark

    Thank you so much for the thorough help! I'm guessing you are the author of the Shapes 3D library so thank you for that as well!

    I've been working for two and a half hours since you posted your latest reply in an attempt to ensure that no synapses overlap with one another.

    If you don't mind, I had just a few more questions:

    -- Every now and then when I run the program, two or more synapses will overlap despite my best efforts. Can you see any problems in my code?

    Screenshot of overlap

    -- Sometimes when I run the program, I will get a grey screen and java.lang.NullPointerException. Where is this coming from?

    Screenshot of error in console

    -- What should I use instead of drawMode(Shape3D.TEXTURE); ?

    // Imports OpenGL library for 3D modeling
    import processing.opengl.*;
    
    // Imports PeasyCam library for an easy-to-use camera
    import peasy.*;
    PeasyCam cam;
    
    // Imports Shapes 3D library for creating and manipulating 3D shapes like ellipsoids
    import shapes3d.utils.*;
    import shapes3d.animation.*;
    import shapes3d.*;
    
    // A "synapse" is part of a brain cell that can be modeled as an ellipsoid
    
    // Defines number of synapses to be drawn (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
    int numberSynapses = 10;
    
    // Declares an array for Ellipsoid objects from the Shapes 3D library and names it "synapses" (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
    Ellipsoid[] synapses;
    
    // Defines the maximum distance for each synapse from the center of the display (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
    float maximumDistance = 100;
    
    // Defines dimensions of each synapse
    float semiaxisA = 20;
    float semiaxisB = 10;
    float semiaxisC = 10;
    
    // Creates arrays to store random coordinates of each synapse NO LONGER NEEDED?
    float[] x = new float[1];
    float[] y = new float[1];
    float[] z = new float[1];
    
    void setup() {
    
      // Sets size of window, background color, and framerate 
      size(500, 500, OPENGL);
      background(0);
      frameRate(1000);
    
      // Defines position and maximum/minimum distance of camera
      cam = new PeasyCam(this, 500);
      cam.setMinimumDistance(50);
      cam.setMaximumDistance(500);
    
      // Makes "synapses" - the array of Ellipsoid objects - and passes in a specified number of slots for synapses, numberSynapses
      //(Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
      synapses = new Ellipsoid[numberSynapses]; 
    
      // ** Create random x, y, and z values for first synapse
      float initialX = random(-maximumDistance, maximumDistance);
      float initialY = random(-maximumDistance, maximumDistance);
      float initialZ = random(-maximumDistance, maximumDistance);
    
      // ** Store those values in the first slot of the array of x, y, and z values.
      x[0] = initialX;
      y[0] = initialY;
      z[0] = initialZ;
    
      println(x);
      println(y);
      println(z);
    
      // ** Create your first synapse and store it in the first slot of the array, "synapses"
      synapses[0] = new Ellipsoid(this, 15, 15);
    
      // ** Define dimensions, color, texture, position, and orientation of first synapse
      synapses[0].setRadius(semiaxisA, semiaxisB, semiaxisC);
      synapses[0].fill(#17BF38);
      synapses[0].drawMode(Shape3D.TEXTURE);
      synapses[0].moveTo(initialX,initialY,initialZ);
      synapses[0].rotateBy(random(0, 2*PI), random(0, 2*PI), random(0, 2*PI));
    
      // For each remaining object in the array "synapses"... 
      // (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
      for(int i = 1; i < synapses.length; i++){
        //** Change to start from i = 1 to avoid overwriting already-occupied i = 0 slot
    
        // ** Generate random x, y, and z values
        float tempX = random(-maximumDistance, maximumDistance);
        float tempY = random(-maximumDistance, maximumDistance);
        float tempZ = random(-maximumDistance, maximumDistance);
    
        // ** Compare against the existing arrays of x, y, and z values
        // semiaxisA is used instead of semiaxisB or semiaxisC because it is the longest dimension of the synapse
        // ** If it is too close, return
        for(int j = 0; j < x.length; j++){
          if (sqrt(sq(x[j] - tempX) + sq(y[j] - tempY)+ sq(z[j] - tempZ)) < semiaxisA) {
            println("Too close to another synapse!");
            return;
          }
        }
        // ** If it is far enough away, create a new ellipsoid and put it in slot i of the "synapses" array with its dimensions, color, texture, location, and rotation
        // Creates a new Ellipsoid and stores it in the array "synapses"
        synapses[i] = new Ellipsoid(this, 15, 15);
    
        // Defines its dimensions, color, texture, position, and orientation
        synapses[i].setRadius(semiaxisA, semiaxisB, semiaxisC);
        synapses[i].fill(#19BF38);
        synapses[i].drawMode(Shape3D.TEXTURE);
        synapses[i].moveTo(tempX,tempY,tempZ);
        synapses[i].rotateBy(random(0, 2*PI), random(0, 2*PI), random(0, 2*PI));
    
        // Appends tempX, tempY, tempZ coordinates of the new synapse to the existing x, y, and z arrays
        x = append(x, tempX);
        y = append(y, tempY);
        z = append(z, tempZ);
      }
    }
    
    void draw() {
      // Sets background color to black before each new frame is drawn
      // (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
      background(0);
    
      // Creates directional light
      lights();
      directionalLight(255, 255, 255, -1, 0, 0);
    
      // For each object in the array "synapses"...
      // (Source: http://forum.processing.org/two/discussion/3860/randomly-placing-and-orienting-ellipsoids-in-3d-space-using-shapes-3d-library#Item_5)
    
      for(int i = 0; i < synapses.length; i++){
          // Draws synapses stored in the array "synapses" for each frame
          synapses[i].draw();
      }
    }
    
  • edited March 2014

    Note: Any code specifically written for the purposes of preventing synapses from overlap is preceded by a comment of the form:

    // ** Insert Comment Here
    
  • edited March 2014

    Yes I am the creator of Shapes3D if you are interested inmy other libraries and tools have a look at my website.

    The algorithm you are using is very inefficient in that the time taken to calculate the synapse positions is proportional to the square of number of synapses. It means that if you double the number of synapses then you quadruple the time needed.

    So I have not modified your code, but I have modified mine. The algorithm is linear so that the time taken is proportional to the number of synapses. It means that if you double the number of synapses then you only double the time needed.

    The only thing to watch out for is that

    `8 * gmax * gmax * gmax > 10 * nbrSynapses'

    This would mean at worst there is a 1 in 10 chance of having to recalculate values of gx, gy and gz.

    // Import HashSet
    import java.util.*;
    
    // Imports OpenGL library for 3D modeling
    import processing.opengl.*;
    
    // Imports PeasyCam library for an easy-to-use camera
    import peasy.*;
    PeasyCam cam;
    
    // Imports Shapes 3D library for creating 3D shapes like ellipsoids
    import shapes3d.utils.*;
    import shapes3d.animation.*;
    import shapes3d.*;
    
    // Declares an Ellipsoid from the Shapes 3D library and names it "synapse"
    // A "synapse" is part of a brain cell that can be modeled as an ellipsoid
    int nbrSynapses = 10;
    Ellipsoid[] synapses;
    
    
    // Sets dimensions of each synapse
    float semiaxisA = 20;
    float semiaxisB = 10;
    float semiaxisC = 10;
    
    float maxSemiAxis;
    
    void setup() {
      // Sets size of window, background color, and framerate
      size(500, 500, OPENGL);
      background(0);
      frameRate(1000);
      synapses = new Ellipsoid[nbrSynapses];
    
      // Calculate the largest semi axis
      maxSemiAxis = max(semiaxisA, max(semiaxisB, semiaxisC));
      int gx, gy, gz, gvalue;
      int gmax = 3;
    
      HashSet<Integer> used = new HashSet<Integer>();
      for (int i = 0; i < synapses.length; i++) {
        synapses[i] = new Ellipsoid(this, 15, 15);
        synapses[i].setRadius(semiaxisA, semiaxisB, semiaxisC);
        synapses[i].fill(#19BF38);
        synapses[i].drawMode(Shape3D.TEXTURE);
        // Get a set of values for gx, gy, gz which have not already 
        // been used. There are 6x6x6 = 216 locations for 10 (nbrSynapses)
        // synapses - plenty of space.
        do {
          gx = round(random(-gmax, gmax));
          gy = round(random(-gmax, gmax));
          gz = round(random(-gmax, gmax));
          gvalue = 1000000 * gz + 1000 * gy + gx;
        } while(used.contains(gvalue));
        // Now found an unused set so add the gvalue so they can't 
        // be picked again
        used.add(gvalue);
        // Use 2.1 (any value slightly greater than 2 will prevent
        // a pair of synapses touching)
        float x = gx * 2.05 * maxSemiAxis;
        float y = gy * 2.05 * maxSemiAxis;
        float z = gz * 2.05 * maxSemiAxis;
        synapses[i].moveTo(x, y, z);
        synapses[i].rotateBy(random(0, TWO_PI), random(0, TWO_PI), random(0, TWO_PI));
      }
      // Defines position and maximum/minimum distance of camera
      cam = new PeasyCam(this, 500);
      cam.setMinimumDistance(50);
      cam.setMaximumDistance(500);
    }
    
    void draw() {
      background(0);
      // Creates directional light
      lights();
      directionalLight(255, 255, 255, -1, 0, 0);
      for (int i = 0; i < synapses.length; i++) {
        synapses[i].draw();
      }
    }
    
  • edited March 2014

    Hi @quark

    I sincerely appreciate you sticking with me. I'm sure you've got a lot of other stuff going on so thank you for your time.

    Regarding your new code, I worked through it and was wondering about two things:

    First, are we taking into account the possibility that gx, gy, or gz could equal zero? That would mean for example that gx could be -3, -2, -1, 0, 1, 2, 3 amounting to 7 not 6 possible values.

    In total, there would then be 7x7x7 = 343 possible positions rather than 216 possible positions, correct?

    Second, would it make sense to better ensure absolute uniqueness of each synapse position by calculating:

    gvalue = 1000000 * gx + 1000 * gy + 10*gz;
    

    Instead of:

    gvalue = 1000000 * gz + 1000 * gx;
    

    Or is such a modification superfluous?

    Thanks so much once again for your help :)

  • edited March 2014

    Update:

    I realized that

    gvalue = 1000000 * gx + 1000 * gy + 10*gz;
    

    Does not work but I can't figure out why.

  • maybe because gx etc. can be negative

    you could add gmax to a copy of them before your formula

  • edited March 2014 Answer ✓

    Sorry there was a mistake in the code line 54 (well spotted) it should have been

    gvalue = 1000000 * gz + 1000 * gy + gx;

    this will guarantee that gvalue is unique for any combination of gx, gy and gz and it doesn't matter if they are negative or positive provided gmax < 1000 (which shouldn't be a problem).

    PS I have corrected the original code posted above.

    Normally random(a, b) will produce a float in the range >=a and <b but I am rounding the output so whether there are 216 or 343 possible values then mmmm... :-/ I will leave that for you to discover.

  • Hi Quark,

    Thanks again for the reply.

    The use of gvalue is pretty cool!

    @Chrisir, thanks for the suggestion. I think it works with negative numbers where say,

    gX = -3
    gY = 1
    gZ = 1
    

    would give you a gValue of 1,000,977. While not explicitly representative of gX, gY, and gZ, the gValue is nonetheless unique to the particular gX, gY, and gZ.

    Also, Quark, I am going to go with the notion that there are 343 possible positions given your definition of rounding.

    Thanks again so much for all your help these past couple of days!

  • I ran the following code

    int gmax  = 3;
    for (int i = 0; i < 100; i++) {
      int n = round(random(-gmax, gmax));
      if (i%10 == 0)
        println();
      print(n + " ");
    }
    

    and the output below shows that the range is -3 to +3 inclusive, 343 possibilities are it

    -3 -3 -3 3 2 2 0 1 1 0 
    1 -2 0 1 -2 0 -3 -1 -1 -1 
    -3 0 3 -3 0 2 1 1 -2 -2 
    0 -1 -2 3 -3 -3 -1 0 -2 -2 
    -2 1 2 -2 2 2 3 3 -2 0 
    -3 2 -1 1 2 2 -1 0 -1 1 
    -2 0 -1 -3 3 2 2 -2 -2 2 
    -1 -3 -1 1 2 1 -3 -1 -1 -1 
    -3 1 2 0 -1 2 -2 -1 -2 3 
    3 -1 2 -1 -1 1 -1 1 0 -1
    
  • edited March 2014

    Hi @quark,

    I ran my code by the graduate student with whom I am working (no surprise here but I'm an undergraduate dabbling in code) and we came upon a roadblock to achieving our goal:

    Our goal is to model the random placement and orientation of synapses in 3D space as accurately as possible.

    The program, however, keeps synapses from touching with the following condition:

    The centers of two synapses cannot be closer than2*maxSemiAxis

    The issue here is that this does not allow for the situation in which the centers of two synapses are CLOSER than2*maxSemiAxisBUT still do not touch.

    An example of this is two synapses nearly stacked upon each other. Their centers are closer than2*maxSemiAxisbut the synapses most definitely do not touch. Such a situation is never drawn by the program. I have made a drawing of this here:

    http://prntscr.com/34h33e

    Would you happen to have any ideas on how to keep synapses from touching in every situation? I know this makes the problem a whole lot more complicated and I am at a loss about where to start.

    Many Thanks,

    Ian

  • Spheres would be easy but you are talking about the intersection of two 'rugby ball shaped' objects and the maths would be horrendous. I did a quick Google and even the maths for two 2D-ellipses was beyond me.

    Effectively you are looking at collision detection and this is a very time consuming process so most techniques work on using spheres and axis-aligned boxes to reduce the level of computation. For more complex shapes the level of computation can be reduced by modeling them with one or more simpler (sphere/box) shapes and in most scenarios this is close enough.

    The problem will get worse as you increase the number of synapses. Consider the situation where you have n synapses then to prevent collisions you have to test every synapse against every other synapse, in other words n(n-1) or ~ n squared. So you double the number of synapses and quadruple the number of collision detections. There are techniques to improve on that e.g. quadtrees for 2D (see it in action here) and octrees for 3D but they are sophisticated techniques.

    Before making a reaching a decision you need to consider

    1) How many ellipsoids are there going to be?

    2) The density of synapses. The greater the density (smaller the distance between them) the more likely to get collisions.

    3) The level of intersection permitted. As you move the viewpoint away from the synapses the less likely you are to notice any intersection.

    The other thing to consider is that in real life synapses are not solid ellipsoids so maybe you are being a little over zealous.

    One other question, because human biology is not my specialist area, are synapses randomly orientated or are they not aligned, perhaps with just minor random deviation, along some axis?

Sign In or Register to comment.