Resource Rotating Particle Effects

Discussion in 'Spigot Plugin Development' started by finnbon, Jul 24, 2016.

  1. Hello fellow spigoteers! (I hope that word didn't make you cringe)

    I was asked by @HeroVerseWizard to write a tutorial about rotating particle effects. I could put a 20 line explanation of what that is, but instead, here is a gif.
    [​IMG]

    Now this is a rather tricky topic, so be prepared if you want to fully understand this.

    As you can see, this "star" effect rotates. To be more specific, it rotates around all three axises, X, Y and Z.

    I'll divide this tutorial in a few parts.

    X, Y & Z - Yaw, Pitch & Roll
    First things you should know about are the 3 axises X, Y & Z and pitch, yaw & roll.

    I'll have to assume you already know what X, Y & Z are. If you don't, how do you even know how to use locations in Minecraft?

    Now for the pitch, yaw & roll. Those are the names of rotations around X, Y & Z. Pitch is the rotation around the X axis, yaw is the rotation around the Y axis, and roll is the rotation around the Z axis. In minecraft, a player has a pitch and a yaw, but not a roll. If they did have a roll value, you would be able to tilt your head to the left or right.

    Let's take this airplane as an example.
    [​IMG]
    Let's say the plane is going to turn to the left, so towards you. Then, it's yaw is going to change (look at the arrow!). And as you can see, it rotates around the Y-axis.

    If the plane is going to lean forward, you can see it's pitch is going to change. (Again look at the arrow!). And again, as you can see it rotates around the X-axis.

    And finally, when the plane is going to tilt to the left or right, it's roll is going to change (You can remember the term 'roll' because it is literally rolling over). And as you can see, it's then rotating around the Z-axis.

    So now you know these things, we can continue to rotating effects.

    Rotating animations
    Now, I'm going to do something I haven't really done before. I am going to hand you a few methods without much explanation of how they work. This is because, you don't need to fully know how these methods work in order to use them properly. You need to know what they are, what they are for and when to use which ones. They can also be found all over the internet, so this isn't that big of a deal.
    Code (Java):
       private Vector rotateAroundAxisX(Vector v, double angle) {
            angle = Math.toRadians(angle);
            double y, z, cos, sin;
            cos = Math.cos(angle);
            sin = Math.sin(angle);
            y = v.getY() * cos - v.getZ() * sin;
            z = v.getY() * sin + v.getZ() * cos;
            return v.setY(y).setZ(z);
        }

        private Vector rotateAroundAxisY(Vector v, double angle) {
            angle = -angle;
            angle = Math.toRadians(angle);
            double x, z, cos, sin;
            cos = Math.cos(angle);
            sin = Math.sin(angle);
            x = v.getX() * cos + v.getZ() * sin;
            z = v.getX() * -sin + v.getZ() * cos;
            return v.setX(x).setZ(z);
        }

        private Vector rotateAroundAxisZ(Vector v, double angle) {
            angle = Math.toRadians(angle);
            double x, y, cos, sin;
            cos = Math.cos(angle);
            sin = Math.sin(angle);
            x = v.getX() * cos - v.getY() * sin;
            y = v.getX() * sin + v.getY() * cos;
            return v.setX(x).setY(y);
        }
    Please note that these methods are somewhat heavy! Try not to overuse them.
    If you are really curious about how these methods work, you should google it. I do not think I am the right person to explain this to you.

    You can grab a piece of paper a pen and a calculator, and write this out on paper. That can really help you understand these things! :)

    As you can see, each method takes in a Vector object, and an angle value. As you can also guess from the names they rotate around an axis. Now, let's say we have the code for a regular, flat circle. Let's turn our x and z values into a vector.
    Code (Java):
    Vector vec = new Vector(x, 0, z);
    Now, we want to make that circle rotate. We could do this in a runnable etc etc. Anyway, to rotate it, we simply call one of the methods, pass our vec variable, and an angle (in degrees). Congratulations, you got your new rotated vector.


    Then simply add the vector to your center, display a particle and subtract the vector from your location!
    Then you can do things like this:
    [​IMG]
    However there is a way to rotate particles which is a bit more efficient and still uses this as a base. Simply click the spoiler below to read about it.
    These methods aren't the friendliest if it comes to efficiency. That's why I'll be giving you alternative methods which are a bit more efficient. You'll have to do some math outside of the methods though.

    First we change the methods to these methods
    Code (Java):
       private Vector rotateAroundAxisX(Vector v, double cos, double sin) {
            double y = v.getY() * cos - v.getZ() * sin;
            double z = v.getY() * sin + v.getZ() * cos;
            return v.setY(y).setZ(z);
        }

        private Vector rotateAroundAxisY(Vector v, double cos, double sin) {
            double x = v.getX() * cos + v.getZ() * sin;
            double z = v.getX() * -sin + v.getZ() * cos;
            return v.setX(x).setZ(z);
        }

        private Vector rotateAroundAxisZ(Vector v, double cos, double sin) {
            double x = v.getX() * cos - v.getY() * sin;
            double y = v.getX() * sin + v.getY() * cos;
            return v.setX(x).setY(y);
        }
    We only have to calculate our cos and sin values once, so we're gonna do this outside our for-loop. (The for-loop which is used for calculating the locations).

    Here's an example of what it would look like:
    Code (Java):

    // the numbers are the angles on which you want to rotate your animation.
    double xangle = Math.toRadians(90); // note that here we do have to convert to radians.
    double xAxisCos = Math.cos(xangle); // getting the cos value for the pitch.
    double xAxisSin = Math.sin(xangle); // getting the sin value for the pitch.

    // DON'T FORGET THE ' - ' IN FRONT OF 'yangle' HERE.
    double yangle = Math.toRadians(60); // note that here we do have to convert to radians.
    double yAxisCos = Math.cos(-yangle); // getting the cos value for the yaw.
    double yAxisSin = Math.sin(-yangle); // getting the sin value for the yaw.

    double zangle = Math.toRadians(30); // note that here we do have to convert to radians.
    double zAxisCos = Math.cos(zangle); // getting the cos value for the roll.
    double zAxisSin = Math.sin(zangle); // getting the sin value for the roll.

    for (double a = 0; a < Math.PI * 2; a += Math.PI / 20) {
      // calculate x and z.[/SIZE]

    [SIZE=4]  Vector vec = new Vector(x, 0, z);
      rotateAroundAxisX(vec, xAxisCos, xAxisSin);
      rotateAroundAxisY(vec, yAxisCos, yAxisSin);
      rotateAroundAxisZ(vec, zAxisCos, zAxisSin);
      // add vec to center, display particle and subtract vec from center.
    }
     
    As you can see we only calculate our sin and cos for each axis once, which is ofcourse more efficient.

    Thanks to @sothatsit for suggesting this!

    Isn't math beautiful? =')

    I apologize for not properly explaining the rotation methods.

    If you have any suggestions on how to make this better, please leave a reply! I hope this was useful and if it was be sure to leave a rating! :)
    Thanks for reading!

    - Finn
     
    #1 finnbon, Jul 24, 2016
    Last edited: Jul 24, 2016
    • Useful x 30
    • Winner x 8
    • Like x 7
    • Informative x 5
    • Agree x 2
    • Creative x 2
  2. Officially counted as "Math god" of SpigotMC.



    To me, atleast <3
     
    • Agree Agree x 23
    • Friendly Friendly x 2
    • Like Like x 1
    • Optimistic Optimistic x 1
  3. sothatsit

    Patron

    Looks great, but could it not be much more efficient if you did not calculate the cos and sin for each particle? Seems like that would be an easy optimisation and I would be interested to see if it would make any significant differences to it's performance.

    Edit: Implementation wise it would be quite simple to just take in an array of vectors and return another array of rotated vectors.
     
  4. Do you mean something like this?
    Code (Text):
    new BukkitRunnable() {

                Location loc = player.getLocation();
                double t = 0;
                double r = 2;

                public void run() {
                    t = t + Math.PI / 16;
                    double x = r * cos(t);
                    double y = 0;
                    double z = r * sin(t);
                    Vector v = new Vector(x, 0, z);
                    v = rotateAroundAxisY(v, 10);
                    loc.add(v.getX(), v.getY(), v.getZ());

                    ParticleEffect.FIREWORKS_SPARK
                            .display(0, 0, 0, 0, 1, loc, 100D);
                    loc.subtract(v.getX(), v.getY(), v.getZ());
                    if (t > Math.PI * 8) {
                        this.cancel();
                    }
                }

            }.runTaskTimer(this, 0, 1);
     
  5. About the efficiency part you are 100% correct. I usually do this for all of my effects. But I don't think it's possible for rotation, since you would have to store all x and z values for each possible angle. Unless you already know which angles you want to use, you can't really do that I'm afraid (An example for when this isn't really do-able is for when you want to rotate something according to a player's yaw and pitch).
     
  6. From a quick look that should work, go ahead and try it. ;)
     
  7. sothatsit

    Patron

    I am not suggesting caching the cos and sin, I am suggesting not calculating it for every particle as the rotation of each particle would be the same.
     
  8. Ahh Doesnt work ;-;
    It doesnt rotate, mind if you looked at the code a bit deeper @finnbon ?
     
  9. It did rotate, but just once. Change the number '10' in your call to a rotation method to a number that increases every tick.
     
  10. I now see what you mean. Yes that would indeed be more efficient, I'll add that! :)
     
  11. This is good, I did the exact same thing when someone asked me to make a method that could rotate a shape on all 3 axis.
    After teaching myself all about 3d rotation matrices, this is what came out:
    Code (Text):
    // The class Point is simply a class that holds x, y, and z values.
    // The list of points should be all the points
    // of the shape relative to the center of rotation.
    // All the points of the shape should be unit points (not sure if this is true, but just to be safe)
    // meaning min point (-1, -1, -1) max point (1, 1, 1)
    private List<Location> transformPoints(Location center, List<Point> points, double yaw, double pitch, double roll, double scale) {
            // Convert to radians
        yaw = Math.toRadians(yaw);
        pitch = Math.toRadians(pitch);
        roll = Math.toRadians(roll);
        List<Location> list = new ArrayList<>();
            // Store the values so we don't have to calculate them again for every single point.
        double cp = Math.cos(pitch);
        double sp = Math.sin(pitch);
        double cy = Math.cos(yaw);
        double sy = Math.sin(yaw);
        double cr = Math.cos(roll);
        double sr = Math.sin(roll);
        double x, bx, y, by, z, bz;
        for (Point point : points) {
            x = point.getX();
            bx = x;
            y = point.getY();
            by = y;
            z = point.getZ();
            bz = z;
            x = ((x*cy-bz*sy)*cr+by*sr)*scale;
            y = ((y*cp+bz*sp)*cr-bx*sr)*scale;
            z = ((z*cp-by*sp)*cy+bx*sy)*scale;
            list.add(new Location(center.getWorld(), (center.getX()+x), (center.getY()+y), (center.getZ()+z)));
        }
        return list;
            // list contains all the locations of the rotated shape at the specified center
    }
    So far that method has solved the problems for 2 people that needed it.
     
    • Like Like x 1
    • Informative Informative x 1
    • Optimistic Optimistic x 1
  12. I guess it would be like taking the plane the particles are in and only rotate that plane. Then get the projection of the particle vektor on the new plane and reset the vektor length.

    That would save you calculating the cos and sin for each particle instead you would rotate the plane once and then use the rotated plane to get your new particle positions.
     
  13. This is actually amazing.. I might use this in my boss plugin which could be casted around the player and then disarms them as long as they are in a star. +1, this is phenomenal.
     
    • Friendly Friendly x 1
  14. Your reply warms my heart (Maybe that's a bit over the top but you get what I mean...) :)
     
  15. I understand what you mean, don't worry. :p
     
  16. You are simply amazing. You never cease to surprise the spigot community :)

    [​IMG]
     
    #16 avighnash, Jul 24, 2016
    Last edited: Jul 24, 2016
    • Winner Winner x 5
  17. You're a god.
     
    • Agree Agree x 2
    • Friendly Friendly x 1
  18. Choco

    Moderator

    Genius... just an absolute genius. Every resource thread you make blows my mind even more. When will you stop being such a bad-ass? Lol. Keep up the great resources, man. I'll be sure to keep this one handy. It looks beautiful
     
    • Winner Winner x 2
  19. Serializator

    Supporter

    I start to love @finnbon more every day...
    I might even switch sides! (jk!!!! what do you think of me, geesh o_O)
     
    • Like Like x 1
    • Agree Agree x 1
  20. [​IMG]
     
    • Winner Winner x 6