Calculate correct sprite direction image in bird's view game? (Math here might be speed vector to degrees angle?) - math

Background: I have 8 images for every sprite in my bird's view JavaScript game, representing top, top-right, right, right-bottom etc., depending on the player's space ship speed.
Question: Given the values sprite.speed.x and sprite.speed.y (which could be something like 4 and -2.5, or 2 and 0 for instance), how do I get the correct angle in degrees? Given that angle, I could then have a lookup for which degrees value represents which sprite image. Or perhaps there's an even easier way. (Currently I'm just using something like "if x below zero use left image" etc. which will result in diagonal images used almost all of the time.)
Searching around, I found ...
angle = Math.atan2(speed.y, speed.x);
... but somehow I'm still missing something.
PS: Zero speed can be ignored, these sprites will just use whatever was the last valid direction image.
Thanks so much for any help!

Good question! I liked tom10's answer (on the mark, +1), but wondered if it can be done without much trigonometry. Here's a solution in short, followed by an explanation.
// slope is a constant, 0.414...; calculate it just once
var slope = Math.tan(Math.PI/8);
// do this for each x,y point
var s1 = x * slope + y > 0 ? 0 : 1;
var s2 = y * slope + x > 0 ? 0 : 1;
var s3 = y * slope - x < 0 ? 0 : 1;
var s4 = x * slope - y > 0 ? 0 : 1;
var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);
This sets the value of segment between 0 and 7. Here's an example with 2000 random points (full source code at the end of the answer). Using the x,y values of the sprite's speed, you can use the segment value to pick up the appropriate sprite image.
Tadaa!
So how does this work? Our segment expression does look a bit cryptic.
Observation one: we want to split the circle around the point into 8 segments of equal angular dimension. 360/8 = 45 degrees per segment. Four of the 8 segments are centered on one of the two sides of the x and y axes, sliced at 45/2 = 22.5 degrees each.
Observation two: The equation of a line on a plane, a*x + b*y + c = 0, when turned into an inequality, a*x + b*y + c > 0 can be used to test on which side of the line a point is located. All our four lines cross the origin (x=0, y=0), and hence force c=0. Further, they are all at a 22.5 degrees angle from either the x or the y axis. This gets us the four line equations:
y = x * tan(22.5); y = -x * tan(22.5);
x = y * tan(22.5); x = -y * tan(22.5)
Turned into inequalities we get:
x * tan(22.5) - y > 0;
x * tan(22.5) + y > 0;
y * tan(22.5) - x > 0;
y * tan(22.5) + x > 0
Testing the inequalities for a given point lets us know on each side of each line it lies:
Observation three: we can combine the test results to obtain the segment number pattern we want. Here's a visual breakdown:
In sequence: 4 * s4, 2 * (s2 ^ s4) and the sum 4 * s4 + 2 * (s2 ^ s4)
(The ^ symbol is the Javascript XOR operator.)
And here is s1 ^ s2 ^ s3 ^ s4, first on its own, and then added to 4 * s4 + 2 * (s2 ^ s4)
Extra credit: can we tweak the calculation to use only integer arithmetic? Yes -- if x and y are known to be integers, we could multiply both sides of the inequalities by some constant (and round off), resulting in completely integer math. (This would be lost, however, on Javascript, whose numbers are always double precision floating point.):
var s1 = x * 414 + y * 1000 > 0 ? 0 : 1;
var s2 = y * 414 + x * 1000 > 0 ? 0 : 1;
var s3 = y * 414 - x * 1000 < 0 ? 0 : 1;
var s4 = x * 414 - y * 1000 > 0 ? 0 : 1;
Full source code for our sample above: (just drop it in a new html file, and open in any browser)
(see as a live demo on jsbin)
<html>
<head>
<style type="text/css">
.dot { position: absolute; font: 10px Arial }
.d0 { color: #FF0000; }
.d1 { color: #FFBF00; }
.d2 { color: #7fcc00; }
.d3 { color: #00FF7F; }
.d4 { color: #00FFFF; }
.d5 { color: #5555FF; }
.d6 { color: #aF00FF; }
.d7 { color: #FF00BF; }
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var $canvas = $("#canvas");
var canvasSize = 300;
var count = 2000;
var slope = Math.tan(Math.PI/8);
$canvas.css({ width: canvasSize, height: canvasSize });
for (var i = 0; i < count; ++i) {
// generate a random point
var x = Math.random() - 0.5;
var y = Math.random() - 0.5;
// draw our point
var $point = $("<div class='dot'></div>")
.css({
left: Math.floor((x + 0.5) * canvasSize) - 3,
top: Math.floor((y + 0.5) * canvasSize) - 6 })
.appendTo($canvas);
// figure out in what segment our point lies
var s1 = x * slope + y > 0 ? 0 : 1;
var s2 = y * slope + x > 0 ? 0 : 1;
var s3 = y * slope - x < 0 ? 0 : 1;
var s4 = x * slope - y > 0 ? 0 : 1;
var segment = 4 * s4 + 2 * (s2 ^ s4) + (s1 ^ s2 ^ s3 ^ s4);
// modify the point's html content and color
// (via its CSS class) to indicate its segment
$point
.text(segment)
.addClass("d" + segment);
}
});
</script>
</head>
<body>
<div id="canvas" style="position: absolute; border: 1px solid blue">
</div>
</body>
</html>

What you suggest is exactly right! Note that the result of Math.atan2 is in radians, and you're probably more familiar with degrees; you can convert using angle_degrees = angle*(180./pi).
(Note also that you don't need to normalize as RCIX suggested, though you can if you want to. What you have, angle = Math.atan2(speed.y, speed.x);, should work just fine.)

You were on the right track. Normalize your speed vector (check for both components being 0 first) , call atan2 on it, and then convert the radians value you get to some sort of friendly direction enum or something that you can use to pick the right sprite.

Related

Perlin noise for terrain generation

I'm trying to implement 2D Perlin noise to create Minecraft-like terrain (Minecraft doesn't actually use 2D Perlin noise) without overhangs or caves and stuff.
The way I'm doing it, is by creating a [50][20][50] array of cubes, where [20] will be the maximum height of the array, and its values will be determined with Perlin noise. I will then fill that array with arrays of cube.
I've been reading from this article and I don't understand, how do I compute the 4 gradient vector and use it in my code? Does every adjacent 2D array such as [2][3] and [2][4] have a different 4 gradient vector?
Also, I've read that the general Perlin noise function also takes a numeric value that will be used as seed, where do I put that in this case?
I'm going to explain Perlin noise using working code, and without relying on other explanations. First you need a way to generate a pseudo-random float at a 2D point. Each point should look random relative to the others, but the trick is that the same coordinates should always produce the same float. We can use any hash function to do that - not just the one that Ken Perlin used in his code. Here's one:
static float noise2(int x, int y) {
int n = x + y * 57;
n = (n << 13) ^ n;
return (float) (1.0-((n*(n*n*15731+789221)+1376312589)&0x7fffffff)/1073741824.0);
}
I use this to generate a "landscape" landscape[i][j] = noise2(i,j); (which I then convert to an image) and it always produces the same thing:
...
But that looks too random - like the hills and valleys are too densely packed. We need a way of "stretching" each random point over, say, 5 points. And for the values between those "key" points, you want a smooth gradient:
static float stretchedNoise2(float x_float, float y_float, float stretch) {
// stretch
x_float /= stretch;
y_float /= stretch;
// the whole part of the coordinates
int x = (int) Math.floor(x_float);
int y = (int) Math.floor(y_float);
// the decimal part - how far between the two points yours is
float fractional_X = x_float - x;
float fractional_Y = y_float - y;
// we need to grab the 4x4 nearest points to do cubic interpolation
double[] p = new double[4];
for (int j = 0; j < 4; j++) {
double[] p2 = new double[4];
for (int i = 0; i < 4; i++) {
p2[i] = noise2(x + i - 1, y + j - 1);
}
// interpolate each row
p[j] = cubicInterp(p2, fractional_X);
}
// and interpolate the results each row's interpolation
return (float) cubicInterp(p, fractional_Y);
}
public static double cubicInterp(double[] p, double x) {
return cubicInterp(p[0],p[1],p[2],p[3], x);
}
public static double cubicInterp(double v0, double v1, double v2, double v3, double x) {
double P = (v3 - v2) - (v0 - v1);
double Q = (v0 - v1) - P;
double R = v2 - v0;
double S = v1;
return P * x * x * x + Q * x * x + R * x + S;
}
If you don't understand the details, that's ok - I don't know how Math.cos() is implemented, but I still know what it does. And this function gives us stretched, smooth noise.
->
The stretchedNoise2 function generates a "landscape" at a certain scale (big or small) - a landscape of random points with smooth slopes between them. Now we can generate a sequence of landscapes on top of each other:
public static double perlin2(float xx, float yy) {
double noise = 0;
noise += stretchedNoise2(xx, yy, 5) * 1; // sample 1
noise += stretchedNoise2(xx, yy, 13) * 2; // twice as influential
// you can keep repeating different variants of the above lines
// some interesting variants are included below.
return noise / (1+2); // make sure you sum the multipliers above
}
To put it more accurately, we get the weighed average of the points from each sample.
( + 2 * ) / 3 =
When you stack a bunch of smooth noise together, usually about 5 samples of increasing "stretch", you get Perlin noise. (If you understand the last sentence, you understand Perlin noise.)
There are other implementations that are faster because they do the same thing in different ways, but because it is no longer 1983 and because you are getting started with writing a landscape generator, you don't need to know about all the special tricks and terminology they use to understand Perlin noise or do fun things with it. For example:
1) 2) 3)
// 1
float smearX = interpolatedNoise2(xx, yy, 99) * 99;
float smearY = interpolatedNoise2(xx, yy, 99) * 99;
ret += interpolatedNoise2(xx + smearX, yy + smearY, 13)*1;
// 2
float smearX2 = interpolatedNoise2(xx, yy, 9) * 19;
float smearY2 = interpolatedNoise2(xx, yy, 9) * 19;
ret += interpolatedNoise2(xx + smearX2, yy + smearY2, 13)*1;
// 3
ret += Math.cos( interpolatedNoise2(xx , yy , 5)*4) *1;
About perlin noise
Perlin noise was developed to generate a random continuous surfaces (actually, procedural textures). Its main feature is that the noise is always continuous over space.
From the article:
Perlin noise is function for generating coherent noise over a space. Coherent noise means that for any two points in the space, the value of the noise function changes smoothly as you move from one point to the other -- that is, there are no discontinuities.
Simply, a perlin noise looks like this:
_ _ __
\ __/ \__/ \__
\__/
But this certainly is not a perlin noise, because there are gaps:
_ _
\_ __/
___/ __/
Calculating the noise (or crushing gradients!)
As #markspace said, perlin noise is mathematically hard. Lets simplify by generating 1D noise.
Imagine the following 1D space:
________________
Firstly, we define a grid (or points in 1D space):
1 2 3 4
________________
Then, we randomly chose a noise value to each grid point (This value is equivalent to the gradient in the 2D noise):
1 2 3 4
________________
-1 0 0.5 1 // random noise value
Now, calculating the noise value for a grid point it is easy, just pick the value:
noise(3) => 0.5
But the noise value for a arbitrary point p needs to be calculated based in the closest grid points p1 and p2 using their value and influence:
// in 1D the influence is just the distance between the points
noise(p) => noise(p1) * influence(p1) + noise(p2) * influence(p2)
noise(2.5) => noise(2) * influence(2, 2.5) + noise(3) * influence(3, 2.5)
=> 0 * 0.5 + 0.5 * 0.5 => 0.25
The end! Now we are able to calculate 1D noise, just add one dimension for 2D. :-)
Hope it helps you understand! Now read #mk.'s answer for working code and have happy noises!
Edit:
Follow up question in the comments:
I read in wikipedia article that the gradient vector in 2d perlin should be length of 1 (unit circle) and random direction. since vector has X and Y, how do I do that exactly?
This could be easily lifted and adapted from the original perlin noise code. Find bellow a pseudocode.
gradient.x = random()*2 - 1;
gradient.y = random()*2 - 1;
normalize_2d( gradient );
Where normalize_2d is:
// normalizes a 2d vector
function normalize_2d(v)
size = square_root( v.x * v.x + v.y * v.y );
v.x = v.x / size;
v.y = v.y / size;
Compute Perlin noise at coordinates x, y
function perlin(float x, float y) {
// Determine grid cell coordinates
int x0 = (x > 0.0 ? (int)x : (int)x - 1);
int x1 = x0 + 1;
int y0 = (y > 0.0 ? (int)y : (int)y - 1);
int y1 = y0 + 1;
// Determine interpolation weights
// Could also use higher order polynomial/s-curve here
float sx = x - (double)x0;
float sy = y - (double)y0;
// Interpolate between grid point gradients
float n0, n1, ix0, ix1, value;
n0 = dotGridGradient(x0, y0, x, y);
n1 = dotGridGradient(x1, y0, x, y);
ix0 = lerp(n0, n1, sx);
n0 = dotGridGradient(x0, y1, x, y);
n1 = dotGridGradient(x1, y1, x, y);
ix1 = lerp(n0, n1, sx);
value = lerp(ix0, ix1, sy);
return value;
}

Matrix.rotateM android API result in wrong rotating Matrix

I´m doing my own collada parser. When animating joints I have a list of ordered transformations to apply. When all them are applied, I get a matrix, the joint node matrix. I think is this way how it works.
The way this system works as far as I know is, in the first transformation, applying it to the identity matrix, and successively applying to the result matrix the next transformation until the last one.
Applying a translate transformation to a matrix looks to works ok, but when rotating it something strange is happening:
For example: the two first transformations are a translation and a rotation arround the z axis. This looks like this:
<translate> -0.6289 63.7555 0.008499979 </translate>
<rotate>0 0 1 -5.00881</rotate>
Using the Matrix.translateM and Matrix.rotateM android api functions I get this matrix:
storedMatrix =
0.9961813 0.0873089 0. - 0.6289
- 0.0873089 0.9961813 0. 63.7555
0. 0. 1. 0.0085000
0. 0. 0. 1.
Using SciLAB (it´s a free software to replace Matlab) I programmed a script to calculate a translate and a rotate matrix. This is the code I put into the script:
function [storedMatrix] = rotateStoredMat(x_axis,y_axis,z_axis,angle_amount)
// t is angle amount, x is x_axis, and...
// ESTA ES LA FÓRMULA (NO SACADA DE WIKIPEDIA PERO ALLI ESTA IGUAL) PARA MATRICES ORGANIZADAS POR COLUMNAS
// FROM WIKIPEDIA:
// x*x * (cos(t) - 1) + cos(t) x*y * (cos(t) - 1) + (z*sin(t)) x*z * (cos(t) - 1) - (y*sin(t))
// x*y * (cos(t) - 1) - z*sin(t) y*y * (cos(t) - 1) + cos(t) y*z * (cos(t) - 1) + (x*sin(t))
// x*z * (cos(t) - 1) + y*sin(t) y*z * (cos(t) - 1) - (x*sin(t)) z*z * (cos(t) - 1) + cos(t)
global storedMatrix;
xx = x_axis * x_axis;
xy = x_axis * y_axis;
xz = x_axis * z_axis;
yy = y_axis * y_axis;
yz = y_axis * z_axis;
zz = z_axis * z_axis;
cost = cos(angle_amount);
sint = sin(angle_amount);
mat = [
xx*(cost -1)+cost xy*(cost -1)+(z_axis*sint) xz*(cost -1)-(y_axis*sint) 0;
xy*(cost -1)-z_axis*sint yy*(cost -1)+cost yz*(cost -1)+(x_axis*sint) 0;
xz*(cost -1)+y_axis*sint yz*(cost -1)-(x_axis*sint) zz*(cost -1)+cost 0;
0 0 0 1;
];
storedMatrix = mat * storedMatrix;
endfunction
function [storedMatrix] = translateStoredMat(x,y,z)
global storedMatrix;
mat = [
1 0 0 x;
0 1 0 y;
0 0 1 z;
0 0 0 1;
];
storedMatrix = mat * storedMatrix;
endfunction
function [storedMatrix] = loadIdentity()
global storedMatrix;
mat = [
1 0 0 0;
0 1 0 0;
0 0 1 0;
0 0 0 1;
];
storedMatrix = mat;
endfunction
Using this "helper" script this is the matrix i get:
ans =
0.2920992 0.9563880 0. 60.791296
- 0.9563880 0.2920992 0. 19.224402
0. 0. - 0.4158016 - 0.0035343
0. 0. 0. 1.
So, is my scilab script wrong? Am I applying the rotateM function in the wrong way ?
All I can say is that the bones and the joints are drawn in wrong positions, so I supouse I´m not calculating the them fine and that my scilab is giving me the right matrix to use.
Can you help me? Do you need some information more to evaluate this and give an answer? Please feel free to ask.
There are a few reasons for the difference:
rotateM() takes an angle in degrees, while the script you wrote treats it as an angle in radians.
Your rotation matrix calculation does not match the formula I see on Wikipedia (http://en.wikipedia.org/wiki/Rotation_matrix). All the terms look somewhat different. For example, you use (cost - 1) everywhere the Wikipedia page has (1 - cost).
Your rotation matrix looks transposed compared to what rotateM() produces.
When you use the Matrix functions, you're multiplying the two matrices in the reverse order compared to what you do with your script.

Determining the average angle

I'm developing an application that involves getting the camera angle in a game. The angle can be anywhere from 0-359. 0 is North, 90 is East, 180 is South, etc. I'm using an API, which has a getAngle() method in Camera class.
How would I find the average between different camera angles. The real average of 0 and 359 is 179.5. As a camera angle, that would be South, but obviously 0 and 359 are both very close to North.
You can think of it in terms of vectors. Let θ1 and θ2 be your two angles expressed in radians. Then we can determine the x and y components of the unit vectors that are at these angles:
x1 = sin(θ1)
y1 = cos(θ1)
x2 = sin(θ2)
y2 = cos(θ2)
You can then add these two vectors, and determine the x and y components of the result:
x* = x1 + x2
y* = y1 + y2
Finally, you can determine the angle of this resulting vector:
θavg = tan-1(y*/x*)
or, even better, use atan2 (a function supported by many languages):
θavg = atan2(y*, x*)
You will probably have to separately handle the cases where y* = 0 and x* = 0, since this means the two vectors are pointing in exactly opposite directions (so what should the 'average' be?).
It depends what you mean by "average". But the normal definition is the bisector of the included acute angle. You must put both within 180 degrees of each other. There are many ways to do this, but a simple one is to increment or decrement one of the angles. If the angles are a and b, then this will do it:
if (a < b)
while (abs(a - b) > 180) a = a + 360
else
while (abs(a - b) > 180) a = a - 360
Now you can compute the simple average:
avg = (a + b) / 2
Of course you may want to normalize one more time:
while (avg < 0) avg = avg + 360
while (avg >= 360) avg = avg - 360
On your example, you'd have a=0, b=359. The first loop would increment a to 360. The average would be 359.5. Of course you could round that to an integer if you like. If you round up to 360, then the final set of loops will decrement to 0.
Note that if your angles are always normalized to [0..360) none of these loops ever execute more than once. But they're probably good practice so that a wild argument doesn't cause your code to fail.
You want to bisect the angles not average them. First get the distance between them, taking the shortest way around, then divide that in half and add to one of the angles. Eg:
A = 355
B = 5
if (abs(A - B) < 180) {
Distance = abs(A - B)
if (A < B) {
Bisect = A + Distance / 2
}
else {
Bisect = B + Distance / 2
}
}
else {
Distance = 360 - abs(A - B)
if (A < B) {
Bisect = A - Distance / 2
}
else {
Bisect = B - Distance / 2
}
}
Or something like that -- "Bisect" should come out to zero for the given inputs. There are probably clever ways to make the arithmetic come out with fewer if and abs operations.
In a comment, you mentioned that all "angles" to be averaged are within 90 degrees to each other. I am guessing that there is really only one camera, but it moves around a lot, and you are creating some sort of picture stability mechanism for the camera POV.
In any case, there is only the special case where the camera may be in the 270-359 quadrant and the 0-89 quadrant. For all other cases, you can just take a simple average. So, you just need to detect that special case, and when it happens, treat the angles in the 270-359 quadrant as -90 to -1 instead. Then, after computing the simple average, adjust it back into the 270-359 quadrant if necessary.
In C code:
int quadrant (int a) {
assert(0 <= a && a < 360);
return a/90;
}
double avg_rays (int rays[], int num) {
int i;
int quads[4] = { 0, 0, 0, 0 };
double sum = 0;
/* trivial case */
if (num == 1) return rays[0];
for (i = 0; i < num; ++i) ++quads[quadrant(rays[i])];
if (quads[0] == 0 || quads[3] == 0) {
/* simple case */
for (i = 0; i < num; ++i) sum += rays[i];
return sum/num;
}
/* special case */
for (i = 0; i < num; ++i) {
if (quadrant(rays[i]) == 3) rays[i] -= 360;
sum += rays[i];
}
return sum/num + (sum < 0) * 360;
}
This code can be optimized at the expense of clarity of purpose. When you detect the special case condition, you can fix up the sum after the fact. So, you can compute sum and figure out the special case and do the fix up in a single pass.
double avg_rays_opt (int rays[], int num) {
int i;
int quads[4] = { 0, 0, 0, 0 };
double sum = 0;
/* trivial case */
if (num == 1) return rays[0];
for (i = 0; i < num; ++i) {
++quads[quadrant(rays[i])];
sum += rays[i];
}
if (quads[0] == 0 || quads[3] == 0) {
/* simple case */
return sum/num;
}
/* special case */
sum -= quads[3]*360;
return sum/num + (sum < 0) * 360;
}
I am sure it can be further optimized, but it should give you a start.

Finding "equivalent" color with opacity

Say I have a background color with a "ribbon" running over it in another solid color. Now, I want the ribbon to be partially transparent to let some details blend through, but still keep the ribbon the "same color" over the background.
Is there a way to (easily) determine, for a given opacity/alpha < 100% of the ribbon color, what RGB values it should have to be identical to its color with 100% opacity over the background?
Here's a picture. Background is rgb(72, 28, 97), ribbon rgb(45, 34, 70). I want a rgba(r, g, b, a) for the ribbon so that it appears identical to this solid color.
Color blending is just a linear interpolation per channel, right? So the math is pretty simple. If you have RGBA1 over RGB2, the effective visual result RGB3 will be:
r3 = r2 + (r1-r2)*a1
g3 = g2 + (g1-g2)*a1
b3 = b2 + (b1-b2)*a1
…where the alpha channel is from 0.0 to 1.0.
Sanity check: if the alpha is 0, is RGB3 the same as RGB2? Yes. If the alpha is 1, is RGB3 the same as RGB1? Yes.
If you locked down only the background color and final color, there are a large number of RGBA colors (infinite, in floating-point space) that could satisfy the requirements. So you have to pick either the color of the bar or the opacity level you want, and find out the value of the other.
Picking the Color Based on Alpha
If you know RGB3 (the final desired color), RGB2 (the background color), and A1 (how much opacity you want), and you are just looking for RGB1, then we can re-arrange the equations thusly:
r1 = (r3 - r2 + r2*a1)/a1
g1 = (g3 - g2 + g2*a1)/a1
b1 = (b3 - b2 + b2*a1)/a1
There are some color combinations which are theoretically possible, but impossible given the standard RGBA range. For example, if the background is pure black, the desired perceived color is pure white, and the desired alpha is 1%, then you would need:
r1 = g1 = b1 = 255/0.01 = 25500
…a super-bright white 100× brighter than any available.
Picking the Alpha Based on Colors
If you know RGB3 (the final desired color), RGB2 (the background color), and RGB1 (the color you have that you want to vary the opacity of), and you are just looking for A1, then we can re-arrange the equations thusly:
a1 = (r3-r2) / (r1-r2)
a1 = (g3-g2) / (g1-g2)
a1 = (b3-b2) / (b1-b2)
If these give different values, then you can't make it match exactly, but you can average the alphas to get as close as possible. For example, there's no opacity in the world that will let you put green over red to get blue.
i made a LESS mixin using Phrogz' answer. you input:
how the colour should look
with a certain alpha
on a given background (default being white)
Here's the code:
.bg_alpha_calc (#desired_colour, #desired_alpha, #background_colour: white) {
#r3: red(#desired_colour);
#g3: green(#desired_colour);
#b3: blue(#desired_colour);
#r2: red(#background_colour);
#g2: green(#background_colour);
#b2: blue(#background_colour);
// r1 = (r3 - r2 + r2 * a1) / a1
#r1: ( #r3 - #r2 + (#r2 * #desired_alpha) ) / #desired_alpha;
#g1: ( #g3 - #g2 + (#g2 * #desired_alpha) ) / #desired_alpha;
#b1: ( #b3 - #b2 + (#b2 * #desired_alpha) ) / #desired_alpha;
background-color: #desired_colour;
background-color: rgba(#r1, #g1, #b1, #desired_alpha);
}
Usage like so:
#mycolour: #abc;
#another_colour: blue;
.box_overlay {
// example:
.bg_alpha_calc (#mycolour, 0.97, #another_colour);
// or (for white bg) just:
.bg_alpha_calc (#mycolour, 0.97);
}
Obviously doesn't work for impossible combinations (as mentioned by Phrogz), that means only mild levels of transparency are supported. See how you go with it.
Thanks to Phrogz's and ephemer's great answers, here is a SASS function that automagically computes the best equivalent RGBA color.
You call it with the desired color and the existing background, and it will compute the best (meaning most transparent) equivalent RGBA color that gives the desired result within ±1/256 of each RGB component (due to rounding errors):
#function alphaize($desired-color, $background-color) {
$r1: red($background-color);
$g1: green($background-color);
$b1: blue($background-color);
$r2: red($desired-color);
$g2: green($desired-color);
$b2: blue($desired-color);
$alpha: 0;
$r: -1;
$g: -1;
$b: -1;
#while $alpha < 1 and ($r < 0 or $g < 0 or $b < 0
or $r > 255 or $g > 255 or $b > 255) {
$alpha: $alpha + 1/256;
$inv: 1 / $alpha;
$r: $r2 * $inv + $r1 * (1 - $inv);
$g: $g2 * $inv + $g1 * (1 - $inv);
$b: $b2 * $inv + $b1 * (1 - $inv);
}
#return rgba($r, $g, $b, $alpha);
}
I just tested it against a number of combinations (all the Bootswatch themes) and it works a treat, both for dark-on-light and light-on-dark results.
PS: If you need better than ±1/256 precision in the resulting color, you will need to know what kind of rounding algorithm browsers apply when blending rgba colors (I don't know if that is standardized or not) and add a suitable condition to the existing #while, so that it keeps increasing $alpha until it achieves the desired precision.
From #Phrogz' answer:
r3 = r2 + (r1-r2)*a1
g3 = g2 + (g1-g2)*a1
b3 = b2 + (b1-b2)*a1
So:
r3 - r2 = (r1-r2)*a1
g3 - g2 = (g1-g2)*a1
b3 - b2 = (b1-b2)*a1
So:
r1 = (r3 - r2) / a1 + r2
g1 = (g3 - g2) / a1 + g2
b1 = (b3 - b2) / a1 + b2
Note you can pick any value of a1, and this will find the corresponding values of r1, g1, and b1 required. For example, picking an alpha of 1 tells you that you need RGB1 = RGB3, but picking an alpha of 0 gives no solution (obviously).
The most practical solution I found so far: just measure the resulting color with a color picker tool and then use the measured color for overlay. This method gives a perfect color match.
I've tried using different mixins but due to rounding error, I was still able to see the difference with a naked eye
To expand on #Tobia's answer and allow for specifying the opacity more like transparentify:
#function rgbaMorph($desired-color, $background-color: rgb(255,255,255), $desired-alpha: 0) {
$r1: red($desired-color);
$g1: green($desired-color);
$b1: blue($desired-color);
$r2: red($background-color);
$g2: green($background-color);
$b2: blue($background-color);
$r: -1;
$g: -1;
$b: -1;
#if ($desired-alpha != 0) {
$r: ( $r1 - $r2 + ($r2 * $desired-alpha) ) / $desired-alpha;
$g: ( $g1 - $g2 + ($g2 * $desired-alpha) ) / $desired-alpha;
$b: ( $b1 - $b2 + ($b2 * $desired-alpha) ) / $desired-alpha;
}
#if (($desired-alpha == 0) or ($r < 0 or $g < 0 or $b < 0
or $r > 255 or $g > 255 or $b > 255)) {
//if alpha not attainable, this will find lowest alpha that is
$alpha: $desired-alpha;
#while $alpha < 1 and ($r < 0 or $g < 0 or $b < 0
or $r > 255 or $g > 255 or $b > 255) {
$alpha: $alpha + 1/256;
$inv: 1 / $alpha;
$r: $r1 * $inv + $r2 * (1 - $inv);
$g: $g1 * $inv + $g2 * (1 - $inv);
$b: $b1 * $inv + $b2 * (1 - $inv);
}
#debug "Color not attainable at opacity using alpha: " $alpha " instead of: " $desired-alpha;
$desired-alpha: $alpha;
}
#return rgba($r, $g, $b, $desired-alpha);
}
I wrote a util that I can use from JSS to help me make a color transparent. It uses utility methods from material-ui which can be found here.
Here is the code for my utility class (in Typescript). Note that it is not possible to make some colors transparent while keeping their color the same as the target color.
Excuse my lack of codepen or runnable code. I hope someone finds this and gets some use out of it!
import {
decomposeColor,
recomposeColor,
ColorObject,
} from '#material-ui/core/styles/colorManipulator';
/**
* Take a non transparent color and create a transparent color that looks identical visually.
* Using formula from this SO post https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
* Assuming a white background
* #param origColor The color you want to match.
* #param value Transparency value between 0 and 1. Cannot be too low because it is mathematically not possible (usually <0.2 but depends on the color)
*/
export const makeTransparent = (origColor: string, value: number): string => {
const origColorObj: ColorObject = decomposeColor(origColor);
if (value >= 1 || value <= 0)
throw new Error('makeTransparent: invalid value provided');
if (origColorObj.values[3] != null && origColorObj.values[3] !== 1)
throw new Error('makeTransparent: origColor cannot be transparent');
const newColorObj: ColorObject = {
type: 'rgba',
values: [0, 0, 0, value],
};
for (let i = 0; i < 3; i++) {
const rgbNum: number = (255 * value + origColorObj.values[i] - 255) / value;
if (rgbNum < 0 || rgbNum > 255)
throw new Error('makeTransparent: Transparency value too low');
newColorObj.values[i] = rgbNum;
}
return recomposeColor(newColorObj);
};

How to map atan2() to degrees 0-360

atan2(y, x) has that discontinuity at 180° where it switches to -180°..0° going clockwise.
How do I map the range of values to 0°..360°?
here is my code:
CGSize deltaPoint = CGSizeMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
float swipeBearing = atan2f(deltaPoint.height, deltaPoint.width);
I'm calculating the direction of a swiping touch event given the startPoint and endPoint, both XY point structs. The code is for the iPhone but any language that supports atan2f() will do.
Solution using Modulo
A simple solution that catches all cases.
degrees = (degrees + 360) % 360; // +360 for implementations where mod returns negative numbers
Explanation
Positive: 1 to 180
If you mod any positive number between 1 and 180 by 360, you will get the exact same number you put in. Mod here just ensures these positive numbers are returned as the same value.
Negative: -180 to -1
Using mod here will return values in the range of 180 and 359 degrees.
Special cases: 0 and 360
Using mod means that 0 is returned, making this a safe 0-359 degrees solution.
(x > 0 ? x : (2*PI + x)) * 360 / (2*PI)
Add 360° if the answer from atan2 is less than 0°.
Or if you don't like branching, negate the two parameters and add 180° to the answer.
(Adding 180° to the return value puts it nicely in the 0-360 range, but flips the angle. Negating both input parameters flips it back.)
#erikkallen is close but not quite right.
theta_rad = atan2(y,x);
theta_deg = (theta_rad/M_PI*180) + (theta_rad > 0 ? 0 : 360);
This should work in C++: (depending on how fmod is implemented, it may be faster or slower than the conditional expression)
theta_deg = fmod(atan2(y,x)/M_PI*180,360);
Alternatively you could do this:
theta_deg = atan2(-y,-x)/M_PI*180 + 180;
since (x,y) and (-x,-y) differ in angles by 180 degrees.
I have 2 solutions that seem to work for all combinations of positive and negative x and y.
1) Abuse atan2()
According to the docs atan2 takes parameters y and x in that order. However if you reverse them you can do the following:
double radians = std::atan2(x, y);
double degrees = radians * 180 / M_PI;
if (radians < 0)
{
degrees += 360;
}
2) Use atan2() correctly and convert afterwards
double degrees = std::atan2(y, x) * 180 / M_PI;
if (degrees > 90)
{
degrees = 450 - degrees;
}
else
{
degrees = 90 - degrees;
}
#Jason S: your "fmod" variant will not work on a standards-compliant implementation. The C standard is explicit and clear (7.12.10.1, "the fmod functions"):
if y is nonzero, the result has the same sign as x
thus,
fmod(atan2(y,x)/M_PI*180,360)
is actually just a verbose rewriting of:
atan2(y,x)/M_PI*180
Your third suggestion, however, is spot on.
Here's some javascript. Just input x and y values.
var angle = (Math.atan2(x,y) * (180/Math.PI) + 360) % 360;
This is what I normally do:
float rads = atan2(y, x);
if (y < 0) rads = M_PI*2.f + rads;
float degrees = rads*180.f/M_PI;
An alternative solution is to use the mod () function defined as:
function mod(a, b) {return a - Math.floor (a / b) * b;}
Then, with the following function, the angle between ini(x,y) and end(x,y) points is obtained. The angle is expressed in degrees normalized to [0, 360] deg. and North referencing 360 deg.
function angleInDegrees(ini, end) {
var radian = Math.atan2((end.y - ini.y), (end.x - ini.x));//radian [-PI,PI]
return mod(radian * 180 / Math.PI + 90, 360);
}
angle = Math.atan2(x,y)*180/Math.PI;
I have made a Formula for orienting angle into 0 to 360
angle + Math.ceil( -angle / 360 ) * 360;
double degree = fmodf((atan2(x, y) * (180.0 / M_PI)) + 360, 360);
This will return degree from 0°-360° counter-clockwise, 0° is at 3 o'clock.
A formula to have the range of values from 0 to 360 degrees.
f(x,y)=180-90*(1+sign(x))* (1-sign(y^2))-45*(2+sign(x))*sign(y)
-(180/pi())*sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
The R packages geosphere will calculate bearingRhumb, which is a constant bearing line given an origin point and easting/northing. The easting and northing must be in a matrix or vector. The origin point for a wind rose is 0,0. The following code seems to readily resolve the issue:
windE<-wind$uasE
windN<-wind$vasN
wind_matrix<-cbind(windE, windN)
wind$wind_dir<-bearingRhumb(c(0,0), wind_matrix)
wind$wind_dir<-round(wind$wind_dir, 0)
theta_rad = Math.Atan2(y,x);
if(theta_rad < 0)
theta_rad = theta_rad + 2 * Math.PI; //if neg., add 2 PI to it
theta_deg = (theta_rad/M_PI*180) ; //convert from radian to degree
//or
theta_rad = Math.Atan2(y,x);
theta_rad = (theta_rad < 0) ? theta_rad + 2 * Math.PI : theta_rad;
theta_deg = (theta_rad/M_PI*180) ;
-1 deg becomes (-1 + 360) = 359 deg
-179 deg becomes (-179 + 360) = 181 deg
For your application I suspect you don't need exact degrees and would prefer a more approximate compass angle, eg 1 of 16 directions? If so then this code avoids atan issues and indeed avoids floating point altogether. It was written for a video game so uses 8 bit and 16 bit integers:
/*
349.75d 11.25d, tan=0.2034523
\ /
\ Sector /
\ 0 / 22.5d tan = ?2 - 1
15 | 1 33.75
| / 45d, tan = 1
14 | 2 _56.25
| / 67.5d, tan = 1 + ?2
13 | 3
| __ 78.75
|
12---------------+----------------4 90d tan = infty
| __ 101.25
|
11 | 5
|
10 | 6
|
9 | 7
8
*/
// use signs to map sectors:
static const int8_t map[4][5] = { /* +n means n >= 0, -n means n < 0 */
/* 0: +x +y */ {0, 1, 2, 3, 4},
/* 1: +x -y */ {8, 7, 6, 5, 4},
/* 2: -x +y */ {0, 15, 14, 13, 12},
/* 3: -x -y */ {8, 9, 10, 11, 12}
};
int8_t sector(int8_t x, int8_t y) { // x,y signed in range -128:127, result 0:15 from north, clockwise.
int16_t tangent; // 16 bits
int8_t quadrant = 0;
if (x > 0) x = -x; else quadrant |= 2; // make both negative avoids issue with negating -128
if (y > 0) y = -y; else quadrant |= 1;
if (y != 0) {
// The primary cost of this algorithm is five 16-bit multiplies.
tangent = (int16_t)x*32; // worst case y = 1, tangent = 255*32 so fits in 2 bytes.
/*
determine base sector using abs(x)/abs(y).
in segment:
0 if 0 <= x/y < tan 11.25 -- centered around 0 N
1 if tan 11.25 <= x/y < tan 33.75 -- 22.5 NxNE
2 if tan 33.75 <= x/y < tan 56.25 -- 45 NE
3 if tan 56.25 <= x/y < tan 78.75 -- 67.5 ExNE
4 if tan 78.75 <= x/y < tan 90 -- 90 E
*/
if (tangent > y*6 ) return map[quadrant][0]; // tan(11.25)*32
if (tangent > y*21 ) return map[quadrant][1]; // tan(33.75)*32
if (tangent > y*47 ) return map[quadrant][2]; // tan(56.25)*32
if (tangent > y*160) return map[quadrant][3]; // tan(78.75)*32
// last case is the potentially infinite tan(90) but we don't need to check that limit.
}
return map[quadrant][4];
}

Resources