Does PVector.array() make a shallow or a deep copy?

edited January 2018 in Using Processing

Normally I would assume that it makes a shallow copy, I can think of no good reason to make a deep copy, but I have this programming bug and it is making me question my intuition.

(I actually have versions of this code in a few different threads on this forum as I am just picking up this language.)

I have a box and I want to make it fly around the screen in 3 dimensions and rotate while it does it.

(assuming we can get it to work) ignore the counter-intuitive rotations and the fact that the transition between the initial rotation and the addition of the movement is not smooth what so ever, please just focus on the code relevant to lines 48-66.

I want to let the box fly off of the screen, but I don't want to let it go to far, which is why, before I increment its position, I run it through the regulate_boundary_intervention() function.

The problem is that the x and y properties of the location vector aren't being changed, even though you can clearly see that their increments are being calculated by the function correctly. For some reason, each run of the function seems to be resetting the l[] array, which I can only fathom would be the case if PVector is making a deep copy on me.

I also understand that I need to increase the size of my boundary if I want to allow the box to go off-screen. Right now I just want it to move correctly.

int w = 1500;
int h = 800;

PVector location = new PVector(w/2,h/2,0);
PVector last_location=location.get();
float move_scale=50;

PVector rotation = new PVector(0,0,0);
float rotation_scale=0.25;

PVector noises = new PVector(0,0,0);

PVector final_min = new PVector(0,0,-h);
PVector final_max = new PVector(w,h,h/2);

int count=0;
int dance_queue = 60*10;
color box_color = color(40,40,40);
float box_size = ((w+h)/2)/5;
boolean axes=false;
void setup()
{
  size(1500,800,P3D);
  background(255,255,255);
  fill(box_color);
  noStroke();
  seed_noises();
  axes=true;
}

void draw()
{
  background(255);
  directionalLight(100, 100, 100, 0.5, 0.65, -0.5);
  directionalLight(100, 100, 100, 0.5, 0, -1);

  float a = 0.01;
  noises.add(a,a,a);
  if(count<dance_queue)
  {  
    float s = count * rotation_scale*2;
    float scale = radians(s);
    rotation.x=map(newNoise(noises.x,0,0),-1,1,-scale, scale);
    rotation.y=map(newNoise(noises.y,0,0),-1,1,-scale, scale);
    count++;       
  }
  else
  {
    last_location=location.get();
    float[] l = location.array();
    float[] n = noises.array();
    for(int i=0;i<3;i++)
    {
      float natural_movement = map(newNoise(n[i], 0, 0),-1,1,-move_scale,move_scale);
      l[i] = regulate_boundary_intervention(natural_movement,i,a); //point the cube back in the right direction if necessary.
      if(i==0)
      {
        println(l[i]);
      }
    } 
    println(l[0]);
    location=new PVector(l[0],l[1],l[2]);   
    rotation.x=map(location.x-last_location.x,  location.x-move_scale, location.x+move_scale,  -rotation_scale, rotation_scale);
    rotation.y=map(location.y-last_location.y,  location.y-move_scale, location.y+move_scale,  -rotation_scale, rotation_scale);    
  }

  translate(location.x,location.y,location.z);
  rotateX(rotation.y);
  rotateY(rotation.x);
  noStroke();
  box(box_size);
  if(axes) draw_axes(); 
}

void seed_noises()
{
  int seed=(int) random(6,30);
  noises.x=random(1000)*seed-4;
  noises.y=random(1000)*seed-2;
  noises.z=random(1000)*seed;  
}

float regulate_boundary_intervention(float desired_movement, int i, float noise_incr)
{ 
  float[] loc = location.array();
  float[] fMin = final_min.array();
  float[] fMax = final_max.array();
  float new_location = loc[i]+desired_movement;
  boolean location_approved = ((new_location>fMin[i]) && (new_location<fMax[i]));
  if (location_approved)
  {
    return new_location;
  }

  float[] ns = noises.array();
  while(!location_approved)
  {
    ns[i]+=noise_incr;
    desired_movement = map(newNoise(ns[i], 0, 0),-1,1,-move_scale,move_scale);
    new_location=loc[i]+desired_movement;
    location_approved = ((new_location>fMin[i]) && (new_location<fMax[i]));
  }  
  noises=new PVector(ns[0],ns[1],ns[2]);
  return new_location;
}

void draw_axes()
{
  stroke(0,255,0);
  line(-w/2,0,w/2,0);
  stroke(255,0,0);
  line(0,-h/2,0,h/2);
}



//================================================================
//Chinchbug's Implementation of Ken Perlin's Improved Noise Algorithm
//See <a href="http://mrl.nyu.edu/~perlin/paper445.pdf" target="_blank" rel="nofollow">http://mrl.nyu.edu/~perlin/paper445.pdf</a>;
//================================================================

int p[];

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

float newNoise(float x, float y, float z) {
  //returns value between -1 and +1
  if (p == null) setupPermutationTable();
  int X = floor(x) & 255;
  int Y = floor(y) & 255;
  int Z = floor(z) & 255;
  x -= floor(x);
  y -= floor(y);
  z -= floor(z);
  float u = fade(x);
  float v = fade(y);
  float w = fade(z);    
  int A = p[X]+Y;
  int AA = p[A]+Z;
  int AB = p[A+1]+Z;
  int B = p[X+1]+Y;
  int BA = p[B]+Z;
  int BB = p[B+1]+Z;

  return lerp2(w, lerp2(v, lerp2(u, grad(p[AA], x, y, z), grad(p[BA], x-1, y, z)),
       lerp2(u, grad(p[AB], x, y-1, z), grad(p[BB], x-1, y-1, z))),
       lerp2(v, lerp2(u, grad(p[AA+1], x, y, z-1), grad(p[BA+1], x-1, y, z-1)),
       lerp2(u, grad(p[AB+1], x, y-1, z-1), grad(p[BB+1], x-1, y-1, z-1))));
 }

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

 float fade(float t) { 
   return ((t*6 - 15)*t + 10)*t*t*t; 
 }

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

 float lerp2(float t, float a, float b) { 
   return (b - a)*t + a;
 }

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

 float grad(int hash, float x, float y, float z) {
   int h = hash & 15;
   float u = h<8 ? x : y;
   float v = h<4 ? y : h==12||h==14 ? x : z;
   return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
 }

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

void setupPermutationTable() {
  int permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 
      233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 
      6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 
      11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 
      68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 
      229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 
      143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 
      169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 
      3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 
      85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 
      170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 
      43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 
      185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 
      191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 
      31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 
      254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 
      66, 215, 61, 156, 180 };

  p = new int[512];
  for (int i=0; i < 256 ; i++)
    p[i] = p[i+256] = permutation[i];
}
Tagged:

Answers

  • edited December 2014 Answer ✓

    tldr

    https://github.com/processing/processing/blob/master/core/src/processing/core/PVector.java#L1004

    Return a representation of this vector as a float array. This is only for temporary use. If used in any other fashion, the contents should be copied by using the PVector.get() method to copy into your own array.

    I really don't get why they waste the memory of having an array in the PVector class...

  • I read that, but it doesn't answer my question although, now that I read it again it definitely implies deep copy.

  • edited December 2014

    changed line 85 from location.array() to location.get().array() and it worked.

    and my response to your statement is that its very convenient when you want to perform similar operations on all 3 vector dimensions, so you can just loop through an array.

  • Yeah but I think it would be better to have it like:

    location.array(arrayToFill);

    If you have a lot of PVectors to deal with then it's better to avoid get() on runtime. Creating a lot of new objects on runtime can slow things down.

  • "I really don't get why they waste the memory of having an array in the PVector class"
    If array() isn't called, it is only a null reference. Still some memory, but not much. But indeed, providing an array to fill is another option, often used in the JRE libraries.

  • edited December 2014

    This is how we calculate how much memory an object uses up. Let's use PVector class:

    • All objects uses 12 bytes for internal data.
    • PVector got 3 float fields. So it's 3 * 4 bytes = 12 bytes.
    • Unfortunately, PVector got a useless extra float[] field, which is null 99,9% of times.
    • So it increases 4 more bytes. Totaling now 12 + 12 + 4 = 28.
    • However, since objects are aligned to 8 bytes, 28 bytes becomes 32 bytes.
    • Final result is thus 32 bytes for each PVector.
    • If that imbecil float[] field would be removed, a PVector would come down to 24 bytes each!
    • That is 8 bytes less!!! :-@
  • If that imbecil float[] field would be removed, a PVector would come down to 24 bytes each!

    I questioned the need for this array back in 2011, see here

    I think I also raised it as an issue and was informed that Processing core used internally for speed reasons. Don't know whether that is still the case with Processing 2 & 3

  • edited December 2014

    ... used internally for speed reasons.

    That'd be true if method array() would be commonly used. This post is the 1st time I see some1 even knowing about its existence! @-)

    That attempt optimization is simply instantiate a float[] the 1st time array() is called.
    Then reuse that same instance for further array() calls.

    That is an excellent idea for data structure classes like IntList, Table, etc.
    But not for something as basic as a PVector which is supposed to be instantiated lotsa times. >:P

    As I've explained above, it's 32 bytes down to 24 bytes for each PVector if that float[] is finally extirpated! :-q

    @clankill3r's idea to pass an existing float[] to method array() is the best optimization for class PVector!

  • Something like this won't even break things?

    public float[] array() {
      return array(new float[3]);
    }
    
    public void array(float[] dest) {
      dest[0] = x;
      dest[1] = y;
      dest[2] = z;
    }
    
  • edited January 2015

    Yea, that's more like it. Though both overloaded array() methods should return float[]:

    // forum.processing.org/two/discussion/8832/
    // does-pvector-array-make-a-shallow-or-a-deep-copy
    
    public float[] array() {
      return array(null);
    }
    
    public float[] array(float[] target) {
      if (target == null)  target = new float[3];
      int len = target.length;
    
      if (len > 0)  target[0] = x;
      if (len > 1)  target[1] = y;
      if (len > 2)  target[2] = z;
    
      return target;
    }
    
    float x = 10, y = 20, z = 30;
    
    void setup() {
      float[] q = array();
      println(q);
      println();
    
      q = array(new float[2]);
      println(q);
      println();
    
      q = array(new float[] { -1.e3, EPSILON, 1/3., PI, THIRD_PI });
      println(q);
    
      exit();
    }
    
  • Isn't it a waste of cpu cycles to check if the array has the correct length? Shouldn't it be up to the user to ensure the length... hmmm

    I think this makes more sense:

    target[0] = x;
    target[1] = y;
    if (len > 2)  target[2] = z;
    
  • edited January 2015

    Yes it is. And I cringe when I do it. However, array()'s usage is so rare and transferring x, y, z to it isn't needed all the time, that the added checks won't decrease the performance that much! In the end, It's irrelevant whether checks are present or not, as long as they remove that useless float[] field! [-O<

Sign In or Register to comment.