# Item height based on angle

I have a little maths conundrum I can’t quite figure out, and was wondering if maybe anyone had an equation up their sleeve.

I guess an image would better explain what I mean:

Lets assume these are squares. Equal length in height and width. For simplification we will assume that the distance of a side is 1f.

The first square has a line at an angle of 45 degrees. To calculate the length from A to B in the first square, you would use the math formula: 1f * FastMath.sqrt(2);

The 4 squares on the right hand side have variable angles. I’m sure there is a formula to calculate the length of the lines at these angles (which for all intent and purpose could be anything from 0 to 90), but does anyone know it?

I think you would have to seperate the problem in two cases.
1.: angle <= 45 degrees:
dist = sidelength / cos(angle)
2.: angle > 45 degrees:
dist = sidelength / cos(90 - angle)

To use cos you need a triangle, and in case 1 this is in the lower half of the rectangle. In the second case it’s in the upper half, so the angle has to be treated the other way around.

1 Like

Do you know the end points of the line? What information do you know already?

Also, can you back up and explain what this is for? Maybe there is a better way before you even get here.

I think @Toboi was almost there. I think so anyway, I’m closer than I was before. I know that it is a true square (equal on all sides) and I know the angle of rotation.

I’m this far so far, but it doesnt work in all angles:

[java]
// get the steps of rotation that this item allows, and rotate it.
// this could be anything from one degree steps to 45 degree steps or even 90 degree steps.
float rotation = item.getRotationXStep();
placingSpatial.rotate(new Quaternion().fromAngleAxis(rotation, new Vector3f(1, 0, 0)));

// get the angle of the new rotation
float[] angles = placingSpatial.getLocalRotation().toAngles(null);
int angle = (int)FastMath.abs((angles[0] * FastMath.RAD_TO_DEG));

// reduce the angle to 90 degrees
if (angle >= 135)
baseAngle = angle - 90;
else if (angle == 89)
baseAngle = 0;
else if (angle > 90)
baseAngle = angle - 90;
else baseAngle = angle;

// the angle will be flat, so we know the distance is equal to its original size.
if (angle == 0 | angle == 90)
{
stretchedSize = item.getMinScale();
}
else
{
float distance = (baseAngle <= 45)
? placingSpatial.getLocalScale().getX() / FastMath.cos(baseAngle)
: placingSpatial.getLocalScale().getX() / FastMath.cos(90 - angle);

``````stretchedSize = (baseAngle &gt; 0)
? item.getMinScale() * FastMath.sqrt(distance)
: item.getMinScale();
``````

}

stretchedSize = Math.abs(stretchedSize);

placingSpatial.setLocalScale(placingSpatial.getLocalScale().getX(), stretchedSize, item.getMinScale());

[/java]

This image basically shows what I’m trying to do. Some items rotate at 45 degree steps, some at 1 degree steps, some at 90… They are all different, so the best solution is to find a formula that will calculate any degree of rotation and stretch it accordingly.

[java]
// reduce the angle to 90 degrees
if (angle >= 135)
baseAngle = angle - 90;
else if (angle == 89)
baseAngle = 0;
else if (angle > 90)
baseAngle = angle - 90;
else baseAngle = angle;
[/java]
I think this could be replaced:
[java]
baseAngle = angle % 90;
[/java]
Besides that, I would not use the additional variable baseAngle, just change angle itself and use it from here on; As far as I can see, the first value of angle is not needed.
[java]
float distance = (baseAngle <= 45)
? placingSpatial.getLocalScale().getX() / FastMath.cos(baseAngle)
: placingSpatial.getLocalScale().getX() / FastMath.cos(90 - angle);
[/java]
First, why use once baseAngle and then angle? (See above).
Second: I am not absolutely sure whether FastMath.cos needs radians, but I think so…

1 Like

Yeah thanks. I was being verbose intentionally so I could breakpoint exactly what was going on and understand it, but your first question was a code error.

It’s almost 1am here in the UK, i’ll post the code when I figure it out tomorrow morning. As far as I can tell, though, cosine is happy with degrees. It works on angles like 44 degrees, but not for 1 degree or 45 degrees. I’ll look at it again tomorrow with some fresh eyes.

EDIT: There’s actually more to this than just that. If I add FastMath.PI / 8 to each other (rotate it by 45 degrees a few times) it loses precision and I end up with numbers like 44 degrees or 189 degrees, and it feels wrong to correct the sum. It just seems a lot more elegant to calculate it based on the resulting degree.

Probably 44 degrees divides to pi in a way that you get about the same value for cos in both radians and degrees.
But I just looked it up, FastMath.cos definitively needs radians.
It passes the call to Math.cos, and there stands
@param a an angle, in radians.

Always use radians. Always. Unless some doc says many times that it takes degrees then it will take radians.

I’m opting for an extra trig call to greatly simplify the logic… which seems warranted in this thread. Also, it presumes the side lengths are 1.
[java]
float x = FastMath.cos(angle);
float y = FastMath.sin(angle);
float h;
if( x == y ) {
h = FastMath.sqrt(2); // could be a constant
} else if( x > y ) {
h = 1 / y;
y = 1;
x = x * h;
} else if( y > x ) {
h = 1 / x;
x = 1;
y = y * h;
}
[/java]

Essentially, it creates a right triangle x, y, h where h is the hypotenuse (distance of the long side). When x and y are regular cos(angle) and sin(angle) then h = 1 but we don’t care about that triangle. We want to know how much we need to scale it.

So if sin = cos then we know it’s a 45 degree angle and Pythagoras tells us h = sqrt(xx + yy)… or sqrt(2). I left this one in as an illustration of what we’re doing. The math would still work right without it as long as you turn the last else if into just an else.

If sin is more than cos then the angle is steep and we use sin as the basis to calculate h (which is also the scale of the right trangle). So we can scale the other parts appropriately (and y * 1 / y = 1, so…)

If cos is more than sin then the angle is shallow and we use cos as the basis to calculate h.

Edit: note that this works with all angles. x and y will be negative for some quadrants as one would expect. I see this as desirable to your end goal, really.

1 Like

Well… Almost. Seems the three different approaches all share a common bug as shown in the video below. Also at times the height becomes a NaN and vanishes, probably related.

Below are the angles produced by one full rotation, rotating at 45 degree angles (45f / 180f * FastMath.PI). The same kind of stretching occurs regardless of the step size, it just reduces the data to analyze.

1) 45.000004 2) 90.00001 3) 135.0 4) -179.99997 5) -134.99997 6) -89.99996 7) -44.99996 8 ) 4.0981133E-5

Heading to the 2nd and 8th rotation is what appears to be causing the issue. I think its related to the value of x because if I just use the code below it works fine except for near 0 and 180 degrees:

[java]
float[] angles = placingSpatial.getLocalRotation().toAngles(null);
float angle = angles[0];

float x = FastMath.cos(angle);
float y = FastMath.sin(angle);
float h;

if( x == y )
{
h = FastMath.sqrt(2); // could be a constant
}
else
{
h = 1 / y;
}

[/java]

Ok well I got it to work but it still feels like a bit of a hack. I guess I’ll know how bullet proof it is as time goes by:

[java]
float angle = placingSpatial.getLocalRotation().toAngles(null)[0];

float x = FastMath.cos(angle);
float y = FastMath.sin(angle);

float h = (x > y) ? 1f / y : 1f / x;

h = FastMath.abs(h);

// irregularity
if (h > 2f) h = 1f;

[/java]

edit: ugh. doesnt work.

I would suggest that instead of getting the angle back, store it separately, add your new angle to that, and then create a new quaternion using that (if possible). There are a few issues going from angle <-> quaternions. One of them being that Quaternion.toAngles() has singularity problems at the poles (angles near 90 start “snapping” to 90), not sure this is the problem, just pointing it out.

I didn’t look too closely at the logic mentioned here, but for a different approach, you can define the rectangle as 4 parametric vectors (vec = startPos + dir) and then do 4 parametric vector intersection tests. This will give you back the position of the intersection with any of the vectors, which you can then get the length of, from the center of your square. Just make sure to always take the closest point if there are multiple intersections, and also be careful of the edge case where the vectors are parallel.

I guess another advantage of this way is that it doesn’t need to be a square, can be a rectangle or any convex shape actually and you can use it on slopes as well. And if you want to be able to have it at different orientations inside your virtual cube, you can define the sides as planes, instead of lines.

I dont know anymore. It doesnt work. I’ll just hard-code it. I tried 10 hours worth of different methods and none of them work. Something’s broken.

@jayfella said: [java] float[] angles = placingSpatial.getLocalRotation().toAngles(null); float angle = angles[0];

float x = FastMath.cos(angle);
float y = FastMath.sin(angle);
float h;

if( x == y )
{
h = FastMath.sqrt(2); // could be a constant
}
else
{
h = 1 / y;
}

[/java]

Try:
[java]
if( x == y )
{
h = FastMath.sqrt(2); // could be a constant
}
else if( y == 0 )
{
h = 1;
}
else
{
h = 1 / y;
}
[/java]

The issue would be the divide by 0 I guess.

Also, for the record, this kind of thing is also going to cause you no end of grief:
float[] angles = placingSpatial.getLocalRotation().toAngles(null);

A quaternion will spit out all kinds of angles. If you want to track the angle of your surface then you will need to do is separately. A quaternion may spit out unexpected angles because it is keeping track of rotation in a “magic” way and is reconstructing the “most efficient” angles from whatever rotation provided. So for example, if you have a 180 degree rotation around x-axis then in theory that could also be a 90 degree rotation around y and a 90 degree rotation around z.

If you need to keep angles then keep angles and generate rotation from them when needed.

And do yourself a favor, keep your angles in radians and only convert them to degrees if you need display them. All of the converting back and forth will introduce rounding errors over time. To help, there is a reason that FastMath has all of those HALF_PI (90 degrees) and QUARTER_PI (45 degrees) constants.

Well I just hard-coded it as I said and it works fine. I just don’t like the way it’s done. It looks amateur, it feels wrong, and I don’t feel comfortable having it there, but it works.

If you see the video below, you can see that items are moved around in a grid-like fashion to make building easy and quick - being able to knit items together without minute movements. The roof and floor creation is the area where the scaling technique comes in. It would have been nicer to have alternative angles to choose from. A 45 degree slope is pretty steep. Ideally you would want to use use 2 x 22.5 degree slopes - which is where the requirement for “one formula for all” became an idea. I’ll come back and have another stab at it when I go through the many “final checks” of the code.

1 Like

My formula should work if you account for divide by zero. But good luck with your game!