# Advanced Particles -- determining y-axis rotation based on Vector for a helix effect

Discussion in 'Spigot Plugin Development' started by CrypticCabub, Jun 28, 2018.

1. ### CrypticCabub

I am currently a little stuck on the math for the following problem.

below is the code for a helix trail that I am currently working on:
Code (Java):
package com.gmail.crypticcabub.benderbattle.game.projectiles.particles;

import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.util.Vector;

/**
* A Helix particle trail that orbits around the projectile as it flies
* <p>
*     direction of orbit is statically set upon creation and is not recalculated. For constantly recalculating
*     direction of orbit (such as when the projectile has gravity) use {@link HelixTrailDynamic}
* </p>
*/

public class HelixTrail extends ParticleTrail {
/*
sin & cos for circles:
https://www.spigotmc.org/threads/tutorial-lets-learn-a-thing-about-math-first-steps-in-animations.111238/

rotating around axes:
https://www.spigotmc.org/threads/rotating-particle-effects.166854/
*/

Particle particle;
private double radius;
private int degressPerIncrement;
private int theta = 0; //number of degrees to use at next spawn

private int helixCount;
private int helixDegreeSeperation;

//particle orbits around the Y axis and then is rotated around X and Z axes to point perpendicular to provided direction
private double cosX, sinX, cosZ, sinZ;

/**
* a circular trail that forms a helix as it travels in a straight line
* @param direction directional vector to orbit around
* @param radius the radius of orbit
* @param rotationalSpeed the number of particle spawns required to complete a full orbit (aproximated)
*/

public HelixTrail(double spawnIncrement, Particle particle, int helixCount, Vector direction, double radius, int rotationalSpeed) {
super(spawnIncrement);
this.particle = particle;
this.radius = radius;
degressPerIncrement = 360 / rotationalSpeed;
this.helixCount = helixCount;
this.helixDegreeSeperation = 360 / helixCount;
calculateDeviations(direction);
}

protected void calculateDeviations(Vector direction) {
//get the deviations from vertical
Vector zDeviationComponent = new Vector(direction.getX(), direction.getY(), 0);
Vector xDeviationComponent = new Vector(0, direction.getY(), direction.getZ());

Vector yAxis = new Vector(0, 1, 0); //equivalence with this axis means pointing straight up

float xDeviation = yAxis.angle(xDeviationComponent);
float zDeviation = yAxis.angle(zDeviationComponent);

cosX = Math.cos(xDeviation);
sinX = Math.sin(xDeviation);
cosZ = Math.cos(zDeviation);
sinZ = Math.sin(zDeviation);

}

@Override
public void spawn(Location location, Vector direction) {
for(int i = 0; i < helixCount; i++) {
Location spawnLoc = location.clone().add(offsetFromCenter(theta + (i * helixDegreeSeperation)));
spawnLoc.getWorld().spawnParticle(particle, spawnLoc, 1, 0, 0, 0, 0);
}

theta += degressPerIncrement;
}

protected Vector offsetFromCenter(int degrees) {
double radians = Math.toRadians(degrees);
double x = Math.cos(radians);
double z = Math.sin(radians);
Vector v = new Vector(x, 0, z);
v.multiply(radius);
rotateAroundAxisX(v, cosX, sinX);
rotateAroundAxisZ(v, cosZ, sinZ);
return v;
}

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 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);
}
}

note: spawn is called at set distance intervals along the projectile's path of travel

it works great for most directions producing a nice helix perpendicular to the vector of travel, however in some directions the helix appears flattened as if it is not properly rotated around the y-axis (yaw).

The theory behind my current code was that the helix was created facing directly upward and then I simply need to calculate the deviation along the X and Z axes and roll/pitch the particles to get the correct bearing.

My question is if I have simply misunderstood the problem and if there is a better way to rotate my helix so that it is always perpendicular to the angle of travel. I have tried simply adding yaw and a y rotation to the problem and all that does is make every diagonal direction break instead of only some of them

2. ### RedstoneTek

Just a note, you can get yaw and pitch from the projectile's location. I faced the exact same problem a few weeks ago and got it to work with it. I also had a flattening problem but I can't help you on that as in far away from my computer. If you still need help in a week or so I'll be able to help.

3. ### CrypticCabub

Thanks @RedstoneTek

Now I have 2 different ways to orient based on direction. However, like you I am still stuck on this flattening problem. Any information anybody can provide is much appreciated. If this isn't solved by the time you return to your computer I would appreciate your solution, though a quicker solution would be better if possible 4. ### RedstoneTek

Okay I don't have my code in front of me (still away from pc) but you calculated the cos and sin of the pitch and yaw right ? If so, try inverting the sin of pitch. That's what worked for me.

Example: double psin = -psin;

Also don't like to ask like this but if this worked for you, consider leaving a rating and mark the thread as solved

6. ### OutOfTime

I'll be honest with you, I can't really tell what you're talking about here. Is the second picture it not working correctly, and if so, how?

If not, could you post a clear picture of it not working?

(I've got lots of experience with particles and complicated math, my plugin ParticlePictures uses rotation of 3D objects made of particles, so I can probably help you if I can figure out what's going wrong )

• Agree x 1
7. ### RedstoneTek

@OutOfTime Cool, I actually made a similar thing with png and jpeg particle displays

Anyways, try inverting some values, I can't really tell but debugging and messing about often helps.

8. ### CrypticCabub

@OutOfTime the second picture is side on slightly because coming from behind was blinded by the particles. the problem I am having is imperfect rotation at certain angles that flattens the helix into 3 linear sine waves rather than a helix that actually orbits perpendicular to the direction of travel.

I have tried inverting various values and seen some success but there is always a range of angles (usually diagonals in either pitch or yaw) when the helix becomes flattened. the impression I get is one of imperfect rotation such that I'm orbiting at an odd angle to the velocity vector (or even parallel with it as seen when I end up with a sine wave).

9. ### OutOfTime

Firstly, beautiful profile picture, don't know how I didn't notice it before. Rare to find a fellow fan of SupCom these days Second, this is rather odd. Have you tried actually doing the trig to figure this out rather than just playing with the numbers? That's what I did for my particle plugin, and it ended up working out, but then again figuring out the trig took the better part of an hour :| Also, do you have a jar of this you could shoot me so I can see what you're talking about in-game?

10. ### CrypticCabub

FAForever! maybe it's just the sort of game programmer types tend to like I don't have a jar I can send your way all that easily unfortunately but do have some more pictures that might be useful:

Normal Behavior: Flattened Clearer View: And this is the current math being done:
Code (Java):
protected void calculateDeviations(Vector direction) {
//get the deviations from vertical
/*
Vector zDeviationComponent = new Vector(direction.getX(), direction.getY(), 0);
Vector xDeviationComponent = new Vector(0, direction.getY(), direction.getZ());

Vector yAxis = new Vector(0, 1, 0); //equivalence with this axis means pointing straight up

float xDeviation = yAxis.angle(xDeviationComponent);
float zDeviation = yAxis.angle(zDeviationComponent);

cosX = Math.cos(xDeviation);
sinX = Math.sin(xDeviation);
cosZ = Math.cos(zDeviation);
sinZ = Math.sin(zDeviation);
*/

Location directionalLoc = direction.toLocation(null).setDirection(direction);
double yaw = Math.toRadians(directionalLoc.getYaw());
double pitch = Math.toRadians(directionalLoc.getPitch());
cosX = -Math.cos(pitch);
sinX = -Math.sin(pitch);
cosY = -Math.cos(yaw);
sinY = -Math.sin(yaw);

//Bukkit.broadcastMessage("xDeviation: " + Math.toDegrees(xDeviation));
//Bukkit.broadcastMessage("zDeviation: " + Math.toDegrees(zDeviation));
//Bukkit.broadcastMessage("yaw: " + yaw);
}

protected Vector offsetFromCenter(int degrees) {
double radians = Math.toRadians(degrees);
double x = Math.cos(radians);
double z = Math.sin(radians);
//Vector v = new Vector(x, 0, z);
Vector v = new Vector(x, z, 0);
v.multiply(radius);
rotateAroundAxisY(v, cosY, sinY);
rotateAroundAxisX(v, cosX, sinX);
//rotateAroundAxisZ(v, cosZ, sinZ);

return v;
}

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 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);
}

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);
}
If I knew the trig well enough to do it by hand I would love to be able to figure it out, however I have yet to take Calculus/Linear Algebra within my current degree studies so I know only what I have been able to self-teach myself through google and logic -- a difficult task when you aren't exactly sure of the technical names for what you are trying to do.

11. ### OutOfTime

Indeed, it's one of those strategy games that requires some effort to learn, since it works so differently with the economy. Brilliant game, and I still play it every now and again for nostalgia Interestingly enough, I never used any Vector stuff for when I did this... mine was all done by hand with the trig, perhaps that's why I'm having trouble figuring this out. I'll look at it more closely, perhaps I can see why what you're feeding the Vector isn't doing what you want it to.

12. ### RedstoneTek

I know I suggested this before but in your case the flattening is on the other axis so this might help, have you tried ONLY inverting yawsin ? I know it sounds like I know nothing about trig or math in general here but I resolved my problem this way and you seem to be facing the exact same problem but in the other axis. I don't see anything wrong with your calculations. I also saw at some point that there was a 90 degree offset implemented by notch for some reason (I'd have to look further into that)

13. ### CrypticCabub

I can play with it some more and may even make a proper test jar to fully test rotational physics, but I did try inverting most of the values (inverting all sin and cos seemed to fix the horizontal plane but still falls apart on certain diagonals, especially down)

14. ### MCGamer00000

Ok, I'm not an expert in this type of math as I also have yet to take a class, but I have made a directional helix before and I'm going off my working code.

I would recommend reading through this before doing anything (you probably will, but just wanted to say)

Assuming you still have this code, there seem to be changes you should make.
Code (Java):

protected void calculateDeviations(Vector direction) {
//get the deviations from vertical
Location directionalLoc = direction.toLocation(null).setDirection(direction);
double yaw = Math.toRadians(directionalLoc.getYaw());
double pitch = Math.toRadians(directionalLoc.getPitch());
cosX = -Math.cos(pitch);
sinX = -Math.sin(pitch);
cosY = -Math.cos(yaw);
sinY = -Math.sin(yaw);

//Bukkit.broadcastMessage("xDeviation: " + Math.toDegrees(xDeviation));
//Bukkit.broadcastMessage("zDeviation: " + Math.toDegrees(zDeviation));
//Bukkit.broadcastMessage("yaw: " + yaw);
}

protected Vector offsetFromCenter(int degrees) {
double radians = Math.toRadians(degrees);
double x = Math.cos(radians);
double z = Math.sin(radians);
//Vector v = new Vector(x, 0, z);
Vector v = new Vector(x, z, 0);
v.multiply(radius);
rotateAroundAxisY(v, cosY, sinY);
rotateAroundAxisX(v, cosX, sinX);
//rotateAroundAxisZ(v, cosZ, sinZ);

return v;
}

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 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);
}

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);
}

The way I, myself, made my directional helix was I made a normal helix that goes up, and rotated it based on pitch and yaw.
So, let's try to make yours work similarly.

1. The Y should be the only axis that the angle is inverted, as seen in these rotational methods below.
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; // As seen here <-----------------------------
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);
}
So, you should only invert the angle of the cosY and sinY
like this
Code (Java):
cosX = Math.cos(pitch);
sinX = Math.sin(pitch);
cosY = Math.cos(-yaw);
sinY = Math.sin(-yaw);
2. When looking at MY code, I see that the direction's pitch value has 90 degrees (PI/2 radians) added to it. (Don't ask me why, it just works it seems)
so you should add 90 degrees to the pitch
Code (Java):
double pitch = Math.toRadians(directionalLoc.getPitch() +90 );
(If the helix rotates in a weird direction, you could mess around with this, this is just what I see in my code)

3. Before we get into editing the rotations, you can remove this line.
Code (Java):
v.multiple(radius);
and simply edit the x and z variables to
Code (Java):
double x = Math.cos(radians)*radius;
double z = Math.sin(radians)*radius;
(This is required to use the next 2 step's method of rotating)

4. I don't see how your helix is going further away from you. When looking at the code, it seems it should just be spinning in place.
So, I'll recommend adding a variable called "time" and increment "time" when theta is incremented (the increment of "time" would determine how fast the particle will fly away)
This will lead into #5 where we will edit the vector and rotation.

5. This is the long one. You will need to edit the instantiation of the vector before its rotated.
Code (Java):
Vector v = new Vector(x, z, 0);
This will use the "time" variable you created in #4.
And, it will put the z variable in what should be the right place.
Code (Java):
Vector v = new Vector(x, time, z);
If you were to not rotate this, and just run it by now, it should just be a vertical helix.

Now, because of the fact that it goes up in the y axis, you need to first rotate about the X axis before the Y axis. Otherwise, it wouldn't rotate around the Y axis properly.
So swap the X and Y rotation methods for X to be first.

Code (Java):
rotateAroundAxisX(v, cosX, sinX);
rotateAroundAxisY(v, cosY, sinY);
So, assuming you took all of the edits, it should now look similar to this.
Code (Java):

protected void calculateDeviations(Vector direction) {
Location directionalLoc = direction.toLocation(null).setDirection(direction);

double yaw = Math.toRadians(directionalLoc.getYaw());
double pitch = Math.toRadians(directionalLoc.getPitch()+90);

cosX = Math.cos(pitch);
sinX = Math.sin(pitch);
cosY = Math.cos(-yaw);
sinY = Math.sin(-yaw);
}

@Override
public void spawn(Location location, Vector direction) {
for(int i = 0; i < helixCount; i++) {
Location spawnLoc = location.clone().add(offsetFromCenter(theta + (i * helixDegreeSeperation)));
spawnLoc.getWorld().spawnParticle(particle, spawnLoc, 1, 0, 0, 0, 0);
}
time += PI/32.0;
theta += degressPerIncrement;
}

protected Vector offsetFromCenter(int degrees) {
double radians = Math.toRadians(degrees);
double x = Math.cos(radians)*radius;
double z = Math.sin(radians)*radius;

Vector v = new Vector(x, time, z);

rotateAroundAxisX(v, cosX, sinX);
rotateAroundAxisY(v, cosY, sinY);

return v;
}

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);
}

(You shouldn't just copy this, just make the edits yourself so you can navigate your own code.)

If you have any questions or something isn't working be sure to reply. I haven't tested the code and I am not an expert, but I tried to explain it and fix it as much as I could.

#14
Last edited: Jul 1, 2018
15. ### Mareckoo01

So you want to do something like this?

16. ### CrypticCabub

thanks @MCGamer00000. I apologize for it not being quite so clear on the code, but the location provided by spawn() moves with the projectile the helix is supposed to be the trail for.

Your solution sounds very similar to my original idea as well, and for a linear helix it would probably work quite well. I suppose I could possibly use the distanceTraveled double as a replacement for time as you suggest to achieve this effect, but it would be preferable to avoid this if possible due to the polymorphic nature of the code I've been working on. The catch I foresee is that projectiles can have gravity, so their velocity vectors changes in an arc over time. the math you supplied relies on a constant origin location with the helix being a set distance from it. the code I provided does assume a static helix for efficiency's sake but I was trying to support the child class HelixTrailDynamic which would simply call calculateDeviations() each particle spawn tick to update the helix angle (understandably expensive, thus why it is not the default behavior).

Perhaps a simpler way to understand my problem is that I have a circle that needs to be oriented perpendicular to a direction vector. the final helix is achieved by moving the center-point of the rotated circle based on a set of calculations that come out of a black box elsewhere in the code. During rotation, the origin around which to rotate is the center-point of the circle