Solved How to rotate points in 3D space to be aligned with direction vector?

Discussion in 'Spigot Plugin Development' started by Nuubles, Oct 20, 2021.

  1. I have a collection of Vectors and a direction vector, how would I rotate the 3D points in the vector collection to be directed in the same direction as the direction vector?
    I have been attempting to use euler rotations, using rotate methods in multiple different ways and searching for information but everything I try seems to rotate the points incorrectly

    Basically:
    Code (Java):
            double pi = 6.283185307179586D;
            double radius = 1;
            Vector[] c = new Vector[this.ringPointCount];
            double inc = pi / c.length;
            int counter = 0;
            for (counter = 0; counter < c.length; counter++) {
                double x = radius * Math.sin(inc);
                double y = radius * Math.cos(inc) - radius * 0.5;
                double z = radius * Math.sin(inc);
                inc += pi / c.length;
                c[counter] = new Vector(x,y,z);
            }
            for (int i = 0; i < c.length; i++) {
                // how do i rotate all of these points correctly???
                c[i].rotateAroundX(playerLocation.getDirection().getX());
                c[i].rotateAroundY(playerLocation.getDirection().getY());
                c[i].rotateAroundZ(playerLocation.getDirection().getZ());
            }
     
  2. I don't get what you're trying to do.

    You've got several arbitrary vectors facing any direction?
    Now you want all of them to face the same direction as a specific single vector direction?

    If the vector length doesn't matter.... you're asking for making all your vectors simply equal.
    So i think you didn't explain correctly what you're trying to do.

    Are you like trying to rotate a 3D shape given by a array of points to face a specific direction, but keeping the relative shape form?
    If so, that's not what you asked for...
     
  3. The list of 'vectors' in this case means only points in 3D space, and the one single vector is the direction to which the list of 'vectors' aka. points should be rotated around their origin

    Basically you could image that this ring is a ring of multiple 3D points in space, and as they are just points their "direction" could be imagined to be (0,0,0) (the line is a bit difficult to see in this pic). The lighter orange line is the direction to which those points should be rotated towards
    [​IMG]

    so that the end result looks like this
    [​IMG]

    But yes, "
    Are you like trying to rotate a 3D shape given by a array of points to face a specific direction, but keeping the relative shape form?
    If so, that's not what you asked for...
    " is the method i'm needing here, my question is probably quite a lot different to this as I'm very lost with this stuff and english is not my first language. also by the phrase "list of Vectors" i mean a list of objects which classes are Vector, rather than "list of vectors"
     
  4. Do I understand right that you want to rotate around two vectors?
    Try to use the scalar product < a, b > = |a| * |b| * cos(alpha) and build a function with that layer...

    There exists a basic calculation for this:
    The vector b as a copy of a vector a rotated by the angle alpha is
    b = a + sin(alpha) * c, c is orthogonal to a and constructs a triangle with a, b, c.

    c must be a tangent at a and your circle, also c needs to go in the same direction your circle goes.

    After you get b you only need to adjust b to the length of a and you are done.


    If you want to use matrices there also exists rotation matrices that solves your problem perfectly:
     
  5. I attempted to rotate using the rotation matrices, but the rotation still isn't quite correct as small differences lead to wildly different outputs. Basically I just want to specify x, y, z (or pitch/yaw, whichever is easier) rotate points in 3d space according to those values, kinda like how the players head rotates around the players neck

    Here's what I attempted:
    Code (Java):
    double roll = 0, yaw = location.getYaw(), pitch = location.getPitch();
                double x = -Math.cos(yaw) * Math.sin(pitch) * Math.sin(roll) * -Math.sin(yaw) * Math.cos(roll);
                double y = -Math.sin(yaw) * Math.sin(pitch) * Math.sin(roll) * +Math.cos(yaw) * Math.cos(roll);
                double z =  Math.cos(pitch) * Math.sin(roll);
                c[i].rotateAroundX(x);
                c[i].rotateAroundY(y);
                c[i].rotateAroundZ(z);

    Edit: I've also added a Point3D class which has the basic shift, add, subtract, etc. methods so it doesn't get mixed up with actual Vectors. Basically I want to have a method similar to Point3D+rotate(x, y, z). The rotation should be get from the Location class, either through location.getDirection() or location.getPitch(), location.getYaw()
     
  6. I found a script from stackoverflow ( https://stackoverflow.com/questions/34050929/3d-point-rotation-algorithm/34060479 ) but it also seems to produce incorrect results for rotation:
    [​IMG]

    Here's a very rough implementation:
    Code (Java):
    @Override
        public void draw(Location location, int timestemp) {
            double pi = 6.283185307179586D;
            double radius = 1;
            Vector[] c = new Vector[this.ringPointCount];
            double inc = pi / c.length;
            int counter = 0;
            for (counter = 0; counter < c.length; counter++) {
                double x = radius * Math.sin(inc);
                double y = radius * Math.cos(inc) - radius * 0.5;
                double z = radius * Math.sin(inc);
                inc += pi / c.length;
                c[counter] = new Vector(x,y,z);
            }
           
            double roll = 0, yaw = location.getYaw(), pitch = location.getPitch();
            double cosa = Math.cos(yaw);
            double sina = Math.sin(yaw);
           
            double cosb = Math.cos(pitch);
            double sinb = Math.sin(pitch);
           
            double cosc = Math.cos(roll);
            double sinc = Math.sin(roll);
           
            double Axx = cosa*cosb;
            double Axy = cosa*sinb*sinc - sina*cosc;
            double Axz = cosa*sinb*cosc + sina*sinc;
           
            double Ayx = sina*cosb;
            double Ayy = sina*sinb*sinc + cosa*cosc;
            double Ayz = sina*sinb*cosc - cosa*sinc;
           
            double Azx = -sinb;
            double Azy = cosb*sinc;
            double Azz = cosb*cosc;

            for (int i = 0; i < c.length; i++) {  
                double px = c[i].getX();
                double py = c[i].getY();
                double pz = c[i].getZ();
               
                c[i].setX(Axx*px + Axy*py + Axz*pz);
                c[i].setY(Ayx*px + Ayy*py + Ayz*pz);
                c[i].setZ(Azx*px + Azy*py + Azz*pz);
            }
           
            for(int i = 0; i < c.length; ++i) {
                Location loc = c[i].toLocation(location.getWorld());
                loc.add(location);
                location.getWorld().spawnParticle(particle, loc, 1, 0, 0, 0, 0);
            }
        }
     
  7. The problem with Minecraft and 3d is that Y and Z axis are swapped. But that should not make the matrix wrong...

    Do you want to have multiple particles form a sphere around something? Or just a ring?
    Or does the particles also move in another direction?
     
  8. Ah, I'll try swapping the Y and Z axis today if it makes any changes. Basically in the picture I'm flying with an elytra and the code should generate particle rings as the player flies, and the rings should align with the players direction it's currently flying

    Edit: no, the particles are stationary and they aren't currently moving in any direction
    Edit2: nope, swapping getY getZ and setY setZ methods still results in similar outcome
     
    #9 Nuubles, Oct 22, 2021
    Last edited: Oct 22, 2021
  9. Well, if you just want to do that, try the following instruction:

    You have the vector of the player's view, right? This is the axis you want to rotate around.
    Create an orthogonal vector of the first particle (in my case for example (-pz, 0, px) px and pz are x and z coord of the viewing vector), add it to the player's location and you have the location of the first particle. Now, use this method and rotate the point around the axis of the player view (player's location + looking direction). Then you have it
     
  10. I see, so you really are searching for a 3D shape rotation...
    Yes, rotation matrices is the way to go.
    I did that earlier, so i can confirm it works.
    I would need to look up the source code if you need some spoonfeed...
     
    #11 Michel_0, Oct 23, 2021
    Last edited: Oct 23, 2021
  11. I attempted to use this matrix with the following code:
    Code (Java):
    double px = c[i].getX();
    double py = c[i].getZ();
    double pz = c[i].getY();
    double cry = Math.cos(ry), sry = Math.sin(ry);
    double crz = Math.cos(rz), srz = Math.sin(rz);
    double crx = Math.cos(rx), srx = Math.sin(rx);
    double y = py * (cry * crz) + pz * (sry * srx - cry * crx * srz) + px * (cry * sry + cry * srz * srx);
    double z = py * (srz) + pz * (crz * crx) + px * (- crz * srx);
    double x = py * (- crz * sry) + pz * (cry * srx + crx * sry * srz) + px * (cry * crx - sry * srz * srx);
    c[i].setX(x);
    c[i].setZ(z);
    c[i].setY(y);
    This code makes the object rotate smoothly but in wrong directions. I have also tried to make the matrix [x,y,z] instead of [y,z,x] etc. but I can't seem to get it working. The object also becomes squished from the invalid rotations.
    [​IMG]
     
    • Like Like x 1
  12. I'm not quite sure about that... too many abbreviation named variables, assigned somewhere you didn't share.

    I'm almost sure i've found the project where i implemented it in 2017 (damn time runs fast...):
    Code (Java):
    /**
     * Intrinsic matrix rotation for MC coordinate system
     * @author Michel_0
     * @param direction The directional vector to be transformed
     * @param yaw The desired yaw angle for rotation
     * @param pitch The desired pitch angle for rotation
     * @param roll The desired roll angle for rotation
     * @return The transformed directional vector
     * @see https://en.wikipedia.org/wiki/Euler_angles
     */

    public static Vector transform(Vector direction, double yaw, double pitch, double roll) {
        double[] vec = new double[] { direction.getX(), direction.getY(), direction.getZ() };
        direction.setX(
                vec[0] * (Math.cos(-yaw) * Math.cos(roll) + Math.sin(-yaw) * Math.sin(pitch) * Math.sin(roll))
                + vec[1] * (Math.cos(roll) * Math.sin(-yaw) * Math.sin(pitch) - Math.cos(-yaw) * Math.sin(roll))
                + vec[2] * Math.cos(pitch) * Math.sin(-yaw));
        direction.setY(
                vec[0] * Math.cos(pitch) * Math.sin(roll)
                + vec[1] * Math.cos(pitch) * Math.cos(roll)
                - vec[2] * Math.sin(pitch));
        direction.setZ(
                vec[0] * (Math.cos(-yaw) * Math.sin(pitch) * Math.sin(roll) - Math.cos(roll) * Math.sin(-yaw))
                + vec[1] * (Math.cos(-yaw) * Math.cos(roll) * Math.sin(pitch) + Math.sin(-yaw) * Math.sin(roll))
                + vec[2] * Math.cos(-yaw) * Math.cos(pitch));
        return direction;
    }
    However i'm not sure if it matches the referenced matrix from wiki above... i would need another hour to re-engineer what i did back then in 2017 - i guess just try it.

    Pass each of your shape point as direction, rotate it by the desired yaw, pitch, roll. Once you've done that for all points the whole shape should be rotated.
    Be aware the origin [0|0|0] must be relative to your points. It gets rotated around [0|0|0].
    If you need it to be moved anywhere in the world, rotate it first, then move it by adding an offset to each point.

    EDIT: If you want circles (like in your screenshot), i would not recomment to calculate a circle, then rotate each point of it. Instead you can write a small method, passing circle center and radius, then draw a circle using the roll parameter of my posted method above...
     
    #13 Michel_0, Oct 24, 2021
    Last edited: Oct 24, 2021
  13. Big thanks for the code! Unfortunately this also suffers from the same problems, I have no idea how...
    I'll paste the whole drawing section here and display also the way the direction/vector is taken from the player.

    Yes, I know the code can be made much more efficient like you explained but I'm first just trying to get this to work before making it more efficient.

    Code (Java):
        @Override
        public void runTimer() {
            for(Entry<UUID,ElytraParticles> entry : particles.entrySet()) {
                Player player = Bukkit.getPlayer(entry.getKey());
                // I've also attempted the snippet below instead of plain player.getLocation() in order to align the particles with movement
                // player.getLocation().setDirection(player.getVelocity().normalize())
                entry.getValue().getDrawer().draw(player.getLocation(), ++timeStep);
            }
        }

    Code (Java):

        @Override
        public void draw(Location location, int timestep) {
            double pi = 6.283185307179586D;
            double radius = 1;
     
            // generate the ring
            Vector[] ring = new Vector[this.ringPointCount];
            double increment = 0;
            for (int i = 0; i < ring.length; ++i) {
               double x = 0;
               double y = radius * Math.cos(increment);
               double z = radius * Math.sin(increment);
                increment += pi / ring.length;
                ring[i] = new Vector(x,y,z);
            }

            // rotate the ring
            for (int i = 0; i < ring.length; i++) {
               // this uses your method copy pasted, no alterations done to it
               transform(ring[i], location.getYaw()/Math.PI, location.getPitch()/Math.PI, 0/Math.PI);
            }
     
            // draw the ring
            for(int i = 0; i < ring.length; ++i) {
                Location loc = ring[i].toLocation(location.getWorld());
                loc.add(location);
                location.getWorld().spawnParticle(particle, loc, 1, 0, 0, 0, 0);
            }
        }
     

    Using yaw and pitch gives the wildly chaotic rotations as in the screenshot below.
    [​IMG]

    Replacing getYaw() and getPitch() with getDirection().getX/Y/Z() results in the same behavior as in my previous posts -> making the rotation incorrect but smooth. The below image displays the results using X,Y,Z (or Y,Z,X). (smooth, but incorrect). I've attempted all of the different variations: ((x y z), (x z y), (y z x), (y x z), (z x y), (z y x)). Basically the function you gave outputs the exact same results as in the code snippet I had given which probably means the mistake is somewhere else
    [​IMG]

    The unknown variables in the previous post I made (rx, ry, rz) are variables taken from location.getDirection().getX() etc.. The c is an array of 3D points.

    I've also attempted to divide the rotation by PI and 2*PI in order to see if it had any effect on the rotations but it worked on neither of them, the rotations are still chaotic even if they're divided. As the ring is generated without any references to the initial position, the ring is around the origin 0,0,0 to which the actual location is then added when drawing the ring, as can be seen from the last lines of the code snippets.

    Edit: found something interesting when testing the ring generation without rotation... I'll edit the results after a bit of testing
    Edit2: Ok it seems that the ring itself was a bit skewed, I've updated the ring generation in this post to generate the ring correctly. Unfortunately this doesn't seem to fix the rotation issue which happens with the code from SO, the one I made according to the matrix and your implementation. I'm so lost at this point to what could be the issue.

    Also as a note I have 3D graphs (objects) and other shapes as well which require rotation so a general 'ring specific' rotation wouldn't work in the long run as the graphs also require some rotation, which is why I'm trying to look for a general rotation matrix solution
     
    #14 Nuubles, Oct 24, 2021
    Last edited: Oct 24, 2021
  14. I wrote a short testing plugin for the method i posted.
    I'm spawning at a specific location a yellow particle cube (2x2x2), a red x axis, green y axis, blue z axis (each 4 blocks long).
    I'm rotating this shape according to the players yaw & pitch.
    Result:
    2021-10-25_22.35.32.png

    Looks correct to me.
    Red is basically always on XZ plane, parallel to my hotbar (rotates based on my yaw), green always faces up my screen field of view (according to my pitch) so blue will always point directly where i'm looking.
    Sounds correct to me...
    All rotations are smooth, all shapes are always correctly aligned to each other...

    I did not yet invest enough time to understand what you are trying to do with your source code.
    Maybe explain what you actually trying to achieve and i can try to implement it and see if it works.
    I didn't use roll yet. Roll makes the whole thing more complicated to validate by one dimension...
     
  15. All of the previous methods got working now! The only place where I had to change stuff was changing location.getYaw() to Math.toRadians(location.getYaw()) and the same for pitch