Make Z axis alternate between 'growing' and 'shrinking'...?

Hi all!

I’m new to Processing and, in short, I’d like the Z axis of an object to keep ‘growing’ and ‘shrinking’ but I don’t know the best way to do this.

I thought maybe having two alternating timers might work, but that seems like several unnecessary steps after I spotted this post about growing and shrinking a 2D circle.

However, I can’t quite figure out if this idea can be moved over into 3D, since I have a z variable that uses fixed min and max values.

I also like frameCount/2 being used a variable/as a modifier at the end of the ‘float z’ line, as it adds a nice ‘explode’ effect but as far as I understand it, frameCount can’t be “reversed”, can it?

Here’s my code that currently rasterizes an image and rotates it on the Y axis; I’ll add the specific image at the bottom in case this helps with testing…!

PImage img;

void setup() {
  size(900,900, P3D);
  img = loadImage("SKULL FRONT.png");
  img.resize(900,0);
}

void draw() {
  background(#000000);
  fill(#FFFFFF);
  noStroke();
  sphereDetail(1);
 
  
  float tiles = 100;
  float tileSize = width/tiles;
  
  push();
  translate(width/2, height/2);
  rotateY(radians(frameCount /2));
  for (int x = 0; x < tiles; x++) {
   for(int y = 0; y < tiles; y++) {
    
    color c = img.get(int(x*tileSize), int(y*tileSize));
    float b = map(brightness(c),0,255,0,1);
    float z = map(b,0,1,-300, 300);
    
    push();
    translate(x*tileSize - width/2, y*tileSize - height/2, z);
    sphere(tileSize*b*0.8);
    pop();
     
   }
  }
  
  pop();
}

Any help greatly appreciated!

Hello @RevAustin ,

Give this a try:

 push();
  translate(width/2, height/2, 0);
  //rotateY(radians(frameCount /2));
  //rotateY(TAU/4);
  
  // Focus on this:
  float zw = 200*sin(frameCount%360*(TAU/360));
  println(zw);

  for (int x = 0; x < tiles; x++) {
   for(int y = 0; y < tiles; y++) {
    
    color c = img.get(int(x*tileSize), int(y*tileSize));
    float b = map(brightness(c),0,255,0,1);
    float z = map(b,0,1, 0, zw);
    
   push();
    translate(x*tileSize - width/2, y*tileSize - height/2, z);
    sphere(tileSize*b*0.8);
   pop();     
   }
  }
 pop()

:)

Amazing! Thanks a bunch @glv !

I’m experimenting with generative art in Processing so I get the use of sin, and I have some understanding that % is modulo, and this can help keep values neatly constrained - is that an okay way of looking at it?

I’ve never seen TAU before, though - looking at the ref for this, am I right in thinking it’s also for keeping something (in this case, sin) constrained but to a set ratio? Trying to get my head around it haha

Thanks again!

1 Like
  • TAU is just 2xPI, so TAU is a full circle (using radians instead of degrees)

see TAU / Reference / Processing.org

  • (TAU/360) is one radians (1 degree measured in radians)

  • frameCount%360 is using modulo (the REST) so this results in a number from 0…360 no matter how big frameCount gets

  • it looks better when you use lights() - nice but slow


// Img
PImage img;
// list of spheres
ArrayList <OneSphere> list = new ArrayList();

// -----------------------------------------------------------------

void setup() {
  size(900, 900, P3D);
  img = loadImage("SKULL FRONT.png");
  img.resize(900, 0);
  // make list of spheres
  analyzImage();
  // set some parameters
  sphereDetail(9);
  noStroke();
  // fill(#FFFFFF);
}//func setup

void draw() {
  background(#000000);
  lights(); // nice but slow

  translate(width/2, height/2);
  //rotateY(radians(frameCount / 1));
  rotateY(radians(1 / 3));
  //rotateY(TAU/4);

  // Focus on this:
  float zw = 200*sin(frameCount%180*(TAU/360));

  // loop over the list
  for (OneSphere oneSphere : list) {
    oneSphere.display( zw );
  }
  //
}//func draw

//-------------------------------------------------------------------

void analyzImage() {
  // runs only once
  float tiles    = 100;
  float tileSize = width/tiles;

  for (int x = 0; x < tiles; x++) {
    for (int y = 0; y < tiles; y++) {

      color c = img.get(int(x*tileSize), int(y*tileSize));
      if (brightness(c)>250)
        continue;
      float b = map(brightness(c), 0, 255, 0, 1);
      float z = 0; // dummy   for   map(b, 0, 1, 0, zw);

      list.add (new OneSphere( x*tileSize - width/2, y*tileSize - height/2, z,
        c,
        tileSize*b*0.8));
    }//for
  }//for
}//func

// ==============================================================================

class OneSphere {

  float x, y, z;
  color col;
  float sizeSphere;

  float b;

  //---------

  // constr
  OneSphere(float x_, float y_, float z_,
    color c_, float size_) {
    x=x_;
    y=y_;
    z=z_;

    col=c_;
    b = map(brightness(c_), 0, 255, 0, 1);

    sizeSphere=size_;
  }// constr

  //---------

  void display(  float zw  ) {
    pushMatrix();
    float z = map(b, 0, 1, 0, zw);
    translate(x, y, z);
    fill(col);
    sphere(sizeSphere);
    popMatrix();
  }//method
  //
} // class
//


1 Like

frameCount%360 will count from 0 to 359 and repeat.
TAU = TWO_PI which is in radians.
TAU/360 divides TAU into 360 parts (in radians) which is equal to 1 degree.
Or you could have used 0.01745329251 but the fraction provides clarity on the angle increment.

sin() requires radians:

Example here (they are using degrees and converting to radians):

And you will need to know some trigonometry:

Have fun!

:)

1 Like

That should be 0…359

:)

2 Likes

I think this is what you’re after. It’s my go-to function for such a thing (although sin()-based approach works alright too).

public static double triangleWaveSmooth(double min, double max, double time, double timePeriod) {
	double amplitude = max - min;
	double phase = (time / timePeriod) % 1.0;
	return amplitude * smoothStep3(Math.abs(2.0 * phase - 1.0)) + min;
}

public static double smoothStep3(double x) {
	return x * x * (3 - 2 * x);
}

I tend to call it like this:

// smoothly oscillate back and forth between 0 and 1, taking 5s to complete one cycle.
double x = triangleWaveSmooth(0, 1, seconds(), 5);

Where seconds() is:

long nanoOffset = System.nanoTime(); // declare at program start

public double seconds() {
	return (System.nanoTime() - nanoOffset) / 1e9;
}

Using seconds() is preferred to frameCount because it’s framerate-independent.

2 Likes

frameCount() (or a counter) is preferred when capturing frames for animations such as animated GIFs.

:)