Solved Particle location question re: add vs clone

Discussion in 'Spigot Plugin Development' started by ZizzyZizzy, Jan 13, 2020.

  1. After digging around, I thought I understood when to use clone vs add/subtract. I was wrong.

    I'm creating a sphere using vector points in space based on a given radius. I want to add those vectors to a specific location in order to spawn a particle sphere.

    This code works perfectly:
    Code (Text):

    ArrayList<Location> locations = new ArrayList<>();
    ArrayList<Vector> sphereVectors = getSphere(radius,points);
    Location originalLoc = player.getLocation().clone().add(0.0, 1.0, 0.0); // Center is middle of player
    for (Vector thisVector : sphereVectors) {
        locations.add(originalLoc.clone().add(thisVector));
    }
    for(Location thisLocation : locations) {
        world.spawnParticle(Particle.FLAME, thisLocation, 1, 0, 0, 0,0);
    }
     
    I was reading around here that clone() is probably slower than an add, then subtract, so I tried this instead:
    Code (Text):
    for (Vector thisVector : sphereVectors) {
        originalLoc.add(thisVector);
        locations.add(originalLoc));
        originalLoc.subtract(thisVector);
    }
    That code does NOT work. It spawns all the particles in the exact same location. Color me confused!

    I also tried this:
    Code (Text):
    for (Vector thisVector : sphereVectors) {
        locations.add(originalLoc.add(thisVector));
    }
    I expected that to cause all kinds of weirdness, but it still spawns them all in the exact same location.

    Clearly I am missing or misunderstanding something. Poking around the javadocs didn't shed any light on this either.
     
  2. You are not wrong at all. However, you are always adding the same original vector to the list. What is being stored inside the list is a reference to the object. Since that reference is always the same, the same reference is stored N times (with N being the number of elements inside sphereVectors). Even thou you change the original location, this will implicitly change every element inside the list as well. Why?
    Well let's look at what happens when you get that element here:
    Code (Text):
    for (Location thisLocation : locations) {
        // world.spawn...
    }
    What thisLocation is behind the scenes is a pointer to an object. You can imagine this as the address of your house. You are always storing that same address, even thou you are changing stuff, you might be building stuff at your house, but your address never changes (hope this made it clearer, not even more confusing).
    When you get the location, it simply looks where the object is (the actual house) and performs stuff with it (like spawning something).

    Now if you want to make this more efficient, here's what you can do:
    You currently have 3 loops; one creating the sphere, one copying it into a List of locations and one sending out the particles. You can omit 2 of these loops and spawning your particles in-time, meaning when you create the sphere
    (also, if you want to know more about how to efficiently perform sin/cos operations, I have heard that there is a guy that provided a resource about just that :giggle:)
     
    • Like Like x 1
  3. You lost me there. The spawning loop is the same in the working and non-working configuration, how I'm adding to the locations ArrayList is what changes. Are you saying I'm adding the same location to the locations ArrayList each time?

    From the way I read the code, location.add not only returns the modified location it also adds the vector to the original location, correct? Thus the need to clone so you don't keep adding to the modified result.



    Regarding the getSphere function. I wanted to do it this way so I only have to call it once for a given radius and save the resulting vectors only. If I include it in the particle spawning loop, getSphere gets called again, even if the same radius is used, since the center location would be different.

    I found a GREAT sphere function in a stackoverflow post linked from here. Some serious math wizards on that one. This sphere calculation ensures all the points are equidistant from each other so it looks like an actual sphere instead of just different sized stacked circles. Check it out:

    (rnd can be used to randomize the distance between points a little. I opted to neuter that part of the function.)

    Code (Text):
     public ArrayList<Vector> getSphere(double radius,double samples) {
            ArrayList<Vector> spherePoints = new ArrayList<>();
            double rnd = 1.0;
            double offset = 2/samples;
            double increment = Math.PI * (3.0 - Math.sqrt(5.0));
            double y;
            double x;
            double z;
            double r;
            double phi;
            for(int i=0;i<samples;i++){
                y = ((i * offset) - 1) + (offset / 2);
                r = Math.sqrt(1 - Math.pow(y,2));

                phi = ((i + rnd) % samples) * increment;

                x = Math.cos(phi) * r;
                z = Math.sin(phi) * r;
                //System.out.println(x*radius + "," + y*radius + "," + z*radius);
                spherePoints.add(new Vector(x * radius,y * radius,z * radius));
            }
            return spherePoints;
        }
    Animated action GIF:
    https://gifyu.com/image/mYP3
    [​IMG]
     
  4. Yes, you are adding the same Location object to the locations ArrayList each time. When you modify the Location, you're actually modifying all the Locations in the ArrayList too. The Location#add method modifies the location then returns itself, not a copy.
     
  5. precisely.

    It adds the Vector every time, that is correct, but it adds it to the same location each time. Let me try to show what I mean. This is your (tbh dramatically simplified) memory, basically a long list of data (represented as binary integers), each with a unique address:

    Adress Data
    0000: 123456789
    0001: 482018573
    ...

    Somewhere in that memory, a little location lives (again, massively simplified):
    Adress Data
    1234 Location
    1235 x = 234
    1236 y = 76
    1237 z = 23

    Now, suppose you would write a function that takes this location and does something with it, say we have a function add:

    Code (Java):
    public void add(Location loc, int x, int y, int z) {
        loc.setX(loc.getX() + x);
        loc.setY(loc.getY() + y);
        loc.setZ(loc.getZ() + z);
    }
    How would you "give" that function the location?
    You could simply "give" it every bit of data, or, you give it a "pointer". This is the Adress to the location.
    What this code does get translated to is something like this:
    Code (Java):
    public void add(Location loc, int x, int y, int z) {
        // get x
        a = load what is at adress 1235; a = 234
        a = a + x
        store to adress 1235 the contents of a; 1235 = a
        // same with y and z
    }
    On the other hand, if you clone the location, you will get a new Object that "lives" somewhere else in the memory, with a unique pointer.
    The same principle applies to a List. You are not inserting the "whole" object, you are inserting a pointer to that location. If you then insert the same pointer twice, the List will not mind.
    If you still have trouble, try to replace the List with a Set (and one of its implementations: HashSet). A Set may only have one value of a kind. Don't change your code otherwise and print out the contents of that set. You will probably see that it contains only one value.

    Again, hope to reduce the confusion, not increase it :p
     
  6. Ahhh, OK, I follow you guys now. It's not actually saving different x,y,z,etc each time. pffft! Misunderstanding indeed.

    Thanks!