So I have a variable containing rotation in degrees, and I have an ideal rotation, and what I want is the percentage of accuracy within 20 degrees in either direction.
var actualRotation = 215
var idealRotation = 225
var accuracy = magicFunction(actualRotation, idealRotation)
In this case, the actualRotation is 10 degrees off from idealRotation, so with a 20 degree threshold in either direction, that's a 50% accuracy. So the value of accuracy would be 0.5.
var accuracy = magicFunction(225, 225) // 1.0
var accuracy = magicFunction(225, 210) // 0.25
var accuracy = magicFunction(245, 225) // 0.0
var accuracy = magicFunction(90, 225) // 0.0
How can I achieve this?
var actualRotation = 215
var idealRotation = 225
var diff = abs(actualRotation - idealRotation);
if (diff > 20)
console.log(0);
else{
accuracy = 1 - (diff/ 20);
console.log(accuracy);
}
Try this (just run code snippet):
function magicFunction(actualRotation , idealRotation ) {
var diff = Math.abs(actualRotation - idealRotation);
var accurrancy = 1 - (diff / 20);
accurrancy = accurrancy < 0 ? 0 : accurrancy;
return accurrancy;
}
console.log("225, 225: ", magicFunction(225, 225));
console.log("225, 210: ", magicFunction(225, 210));
console.log("245, 225: ", magicFunction(245, 225));
console.log("90, 225: ", magicFunction(90, 225));
The previous answers were good, but they don't handle the case where the difference crosses the zero-singularity.
E.g. when the angles are 5 and 355, you expect a difference of 10, but a simple subtraction gives 350. To rectify this, subtract the angle from 360 if it is bigger than 180.
For the above to work, you also need the angles to be in the range [0, 360). However this is a simple modulo calculation, as below.
Code:
function normalize(angle) {
if (angle < 0)
return angle - Math.round((angle - 360) / 360) * 360;
else if (angle >= 360)
return angle - Math.round(angle / 360) * 360;
else
return angle;
}
function difference(angle1, angle2) {
var diff = Math.abs(normalize(angle1) - normalize(angle2));
return diff > 180 ? 360 - diff : diff;
}
function magicFunction(actualRotation, idealRotation, limit) {
var diff = difference(actualRotation, idealRotation);
return diff < limit ? 1.0 - (diff / limit) : 0.0;
}
// tests
console.log(difference(10, 255)); // 115 (instead of the incorrect answer 245)
console.log(magicFunction(5, 355, 20)); // 0.5 (instead of 0 as would be returned originally)
EDIT: a graphical illustration of why the previous method would be insufficient:
Related
I'm creating a birds-eye view, 2D game in JS using no engine/library (more for the learning challenge). I've got the character movement working with WASD where the character will either go forward (W) or back (S) depending on their current direction (A turns left, D turns right), but when I output the angles (direction) during play-time I'm getting unexpected results.
For example, when a player is facing "up" and I press "W", the player moves up, and the direction that gets output is 90° - as expected. When facing "down" and I press "S", the player moves down, and the direction is 270° - as expected.
But, when facing left, and I press "W", the character does move left but the output direction is 0°, and when facing + moving right, it is 180° - the exact opposite of what I expect.
These are the functions that move my players:
// Turning
_resetAngle(angle) {
if (angle >= 360) { return 0 }
if (angle < 0) { return 359 }
return angle
}
updateDirection(player, keyboardInput) {
let currentDirection = player.direction
const turnedLeft = keyboardInput['a']
const turnedRight = keyboardInput['d']
if (turnedLeft) { currentDirection -= this.turnAngle }
if (turnedRight) { currentDirection += this.turnAngle }
player.setDirection(this._resetAngle(currentDirection))
}
//Moving
_calculateNewCoords(movingForward, entity) {
let newX
let newY
// please ignore the code duplication, just testing for now
if (movingForward) {
newX = entity.getX() - entity.speed * Math.cos(entity.direction * (Math.PI / 180))
newY = entity.getY() - entity.speed * Math.sin(entity.direction * (Math.PI / 180))
}
else {
newX = entity.getX() + entity.speed * Math.cos(entity.direction * (Math.PI / 180))
newY = entity.getY() + entity.speed * Math.sin(entity.direction * (Math.PI / 180))
}
return { newX, newY }
}
updateCoordinatesByKeyboard(entity, keyboardInput) {
const movingForward = keyboardInput['w']
const movingBackwards = keyboardInput['s']
if ((movingForward && movingBackwards) || !(movingForward || movingBackwards)) { return }
const { newX, newY } = this._calculateNewCoords(movingForward, entity)
if (this._canMove(entity, newX, newY)) { return entity.setXY(newX, newY) }
}
And this is the part that renders the players:
drawCharacter(character, image) {
const scale = this._getScale(character) // for a 'breathing' effect, makes character grow and shrink
this.context.setTransform(scale, 0, 0, scale, this.windowDimensions.width / 2, this.windowDimensions.height / 2)
this.context.rotate(character.direction * Math.PI / 180)
this.context.drawImage(image, -image.width / 2, -image.height / 2)
}
Results when printing player.direction:
Output: 90 (as expected)
Output: 180 (expect it to be 0)
Output: 270 (as expected)
Output: 0 (expect it to be 180)
Output: 135 (expect it to be 45)
Output: 315 (expect it to be 225)
Again, just to reiterate - the players move as expected (i.e. pressing WASD makes the player turn and move correctly) - but the output directions are unexpected, and I'd like to fix that because in the future I'd like to set NPCs at certain angles (i.e. facing 45°) and expect them to face that direction without having to calculate the 'mirror' direction.
Thanks in advance for any help!
I wouldn't say the values you get don't make sense - it's quite the reverse - as it's what I would expect. So it's more of a 'cosmetical' problem as it would be the same for your other game objects.
Nevertheless you can easily recalculate the output to your desired value by:
output = Math.abs((input + 90) % 360 - 270)
This will make:
90 -> 90
180 -> 0
270 -> 270
0 -> 180
135 -> 45
315 -> 225
Trying to calculate TDOA (Time Difference Of Arrival) but I can’t get my head around it. Can someone please help me with an clear and simple example.
I have 3 microphone located at:
X0 = 0 cm
Y0 = 0 cm
-
X1 = 0 cm
Y1 = 15 cm
-
X2 = 0 cm
Y2 = 30 cm
Time different of sound arrive to the 3 microphone:
A0 = 0 sec
A1 = 0.002 sec
A2 = 0.004 sec
Speed of sound:
S = 340 m/sec
Updated after response from wessel
I am trying to make a script calc TDOA
let ? = (Y1 - Y0) / (S); // = 0.00044
let ? = (Y2 - Y0) / (S); // = 0.00088
let VX = (X1 - X0) / A0; // = NaN
let VY = (Y1 - Y0) / A0; // = Infinity
let V = Math.sqrt(VX^2 + VY^2) // = 1.4142
let alpha = atan(VY / VX) // = NaN
console.log(alpha+' deg with the x-axis.');
If you have a distance and a speed (distance / time), you get a time by doing distance / speed = distance / (distance / time ) = distance / distance * time = time.
What you omit in your question, is the direction and the shape of the sound wave. If the sound is traveling along the x-axis, the shape does not matter, and the time difference would be:
(A1 - A0) = (X1 - X0) / (340 m/s) = 0.15 / 340 s = 0.00044 s
(A2 - A0) = (X2 - X0) / (340 m/s) = 0.3 / 340 s = 0.00088 s
If the shape is a straight line, e.g. from an infinitely far point source or from a particularly shaped non-point source, the different of arrival time is given by the x-component of the direction of the sound wave:
VX = (X1 - X0) / (A1 - A0) = 37.5 m/s.
The total sound speed, which you give as 340 m/s, should equal
V = Sqrt(VX^2 + VY^2)
such that you can work out that
VY = Sqrt(V^2 - VX^2) = 338 m/s
or
VY = -Sqrt(V^2 - VX^2) = -338 m/s
such that your sound wave must in incoming under an angle alpha:
alpha = atan(VY / VX) = 83 deg
with the x-axis.
I don't see why javascript is relevant here, though. Just in case: use Math.sqrt, Math.pow or V*V, and Math.atan or Math.atan2 (beware of the order of the arguments there!).
I need to map a rotation angle scale in degrees to a light intensity scale (a rotating sun) that goes from 0.0 to 0.9. This is the mapping function I'm using:
function map (num, in_min, in_max, out_min, out_max)
{
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
//(degree, degree in min, degree in max, light intensity out min, light intensity out max
var fade = map(30, 180, 360, 0.0, 0.9);
However, the fade in and out is too slow leaving only a small amount of time when the light intensity is at it's max. I need more control over how the scale mapping is done - how do I achieve this?
The answer by #Manuel Otto above is correct if you need a scale that runs up from a number to a higher number (and that's what I asked for). However, I needed more flexibility as I'm adjusting my sun position adhoc at the moment while testing and my final function below accommodates running from a number to a lower number as well (since it's degrees it gets reset at 360 to 0) - so for example 180 -> 60 (180 to 360 and then 0 to 60 = 240 degrees):
//input degree, minimum degree possible, maximum degree possible, scale minimum, scale maximum, speed of scale fades (0.0 - 1.0 = fast to slow)
function map(num, in_min, in_max, out_min, out_max, factor)
{
//if input degree is greater than minimum degree possible
if (num >= in_min)
{
//simple subtraction
var delta = Math.max(0, num - in_min);
} else {
//subtract from 360 and add the input degree
var delta = Math.max(0, ((360 - in_min) + num));
}
//if the maximum degree possible is greater than the minimum degree possible
if (in_max >= in_min)
{
//simple subtraction
var scale = delta / (in_max - in_min);
} else {
//subtract from 360 and add the maximum degree possible
var scale = delta / ((360 - in_min) + in_max);
}
return Math.pow(Math.min(1, scale * 2) - Math.max(0, scale * 2 - 1), factor);
}
Posting it here in case it's useful to anybody else.
As said already you can achieve a initial rapid change and a long period of low change with Math.pow(x,<1).
To have it fade in and then fade out again, you can scale the value by 2 and subtract what's above 1 with the value.
function map(num, in_min, in_max, out_min, out_max, factor)
{
var delta = Math.max(0,num-in_min)
var scale = delta/(in_max-in_min)
return Math.pow(Math.min(1,scale*2)-Math.max(0,scale*2-1),factor)
}
When factor is 1 it's linear. When it's smaller than 1 it's polynomial.
See illustration:
function map(num, in_min, in_max, out_min, out_max, factor)
{
var delta = Math.max(0,num-in_min)
var scale = delta/(in_max-in_min)
return Math.pow(Math.min(1,scale*2)-Math.max(0,scale*2-1),factor)
}
// UI
var degrees_slider = document.getElementById('degrees')
var factor_slider = document.getElementById('factor')
var degrees_disp = document.getElementById('degrees_disp')
var factor_disp = document.getElementById('factor_disp')
var intensity_disp = document.getElementById('intensity_disp')
degrees_slider.oninput = factor_slider.oninput = update
function update(){
var num = degrees_slider.value
var fact = factor_slider.value
degrees_disp.innerHTML = num
factor_disp.innerHTML = fact
intensity_disp.innerHTML = toPercent(map(num, 180, 360, 0.0, 0.9, fact))
}
function toPercent(val){
return Math.round(val*100)+'%'
}
update()
#intensity_disp{
font-size: 32pt;
margin-top: 25px;
}
<label for="degrees">Degrees</label>
<br>
<input type="range" id="degrees" min="0" max="360" step="1"/>
<span id="degrees_disp"></span>
<br><br>
<label for="degrees">Linearity</label>
<br>
<input type="range" id="factor" min="0" max="1" step="0.01"/>
<span id="factor_disp"></span>
<div id="intensity_disp"></div>
I am working on rotating an object at 160 degrees/second, and having it slow down to a complete stop at a pre-specified angle. For example, if the angle chosen was 30 degrees, it would spin very fast and slow down, eventually halting at 30 degrees. I am having trouble coming up with the algorithm to do it, which is what I'm asking for.
For the time being, assume that all you need to do to set the rotation is object.Rotation = 30 (degrees). Feel free to write this in Java/Lua/C++/JavaScript.
What I have so far (basically nothing):
//Assume that wait(1) waits 1 second
int angle = 70;//Fast rotations at first but slow down as time goes on
for (int i = 140; i > .1; i = i - 5)//Must work for every angle
{
for (int a = 0; a < i; a = a + 10)
{
object.Rotation = a;
wait(.05);
}
}
pseudocode:
int max_speed = 10
int target_angle = 30
while (target_angle != object.Rotation) do
int delta = target_angle - object.Rotation
delta = max(min(delta / 5, max_speed), -max_speed) + max(min(delta, 1), -1)
object.Rotation = object.Rotation + delta
wait(.05)
end while
Preamble: there's an issue logged with the Google Maps API, requesting the ability to correct the roll angle of street view tiles to compensate for hills. I've come up with a client-side workaround involving some css sorcery on the tile container. Here's my rotate function:
rotate: function() {
var tilesLoaded = setInterval(function() {
var tiles = $('map-canvas').getElementsByTagName('img');
for (var i=0; i<tiles.length; i++) {
if (tiles[i].src.indexOf(maps.panorama.getPano()) > -1) {
if (typeof maps.panorama.getPhotographerPov != 'undefined') {
var pov = maps.panorama.getPhotographerPov(),
pitch = pov.pitch,
cameraHeading = pov.heading;
/**************************
// I need help with my logic here.
**************************/
var yaw = pov.heading - 90;
if (yaw < 0) yaw += 360;
var scale = ((Math.abs(maps.heading - yaw) / 90) - 1) * -1;
pitch = pov.pitch * scale;
tiles[i].parentNode.parentNode.style.transform = 'rotate(' + pitch + 'deg)';
clearInterval(tilesLoaded);
return;
}
}
}
}, 20);
}
A full (and more thoroughly commented) proof-of-concept is at this JSFiddle. Oddly, the horizon is just about perfectly level if I do no calculation at all on the example in the JSFiddle, but that result isn't consistent for every Lat/Lng. That's just a coincidence.
So, I need to calculate the roll at the client's heading, given the client heading, photographer's heading, and photographer's pitch. Assume the photographer is either facing uphill or downhill, and pov.pitch is superlative (at the min or max limit). How can I calculate the desired pitch facing the side at a certain degree?
Edit: I found an equation that seems to work pretty well. I updated the code and the fiddle. While it seems to be pretty close to the answer, my algorithm is linear. I believe the correct equation should be logarithmic, resulting in subtler adjustments closer to the camera heading and opposite, while to the camera's left and right adjustments are larger.
I found the answer I was looking for. The calculation involves spherical trigonometry, which I didn't even know existed before researching this issue. If anyone notices any problems, please comment. Or if you have a better solution than the one I found, feel free to add your answer and I'll probably accept it if it's more reliable or significantly more efficient than my own.
Anyway, if the tile canvas is a sphere, 0 pitch (horizon) is a plane, and camera pitch is another plane intersecting at the photographer, the two planes project a spherical lune onto the canvas. This lune can be used to calculate a spherical triangle where:
polar angle = Math.abs(camera pitch)
base = camera heading - client heading
one angle = 90° (for flat horizon)
With two angles and a side available, other properties of a spherical triangle can be calculated using the spherical law of sines. The entire triangle isn't needed -- only the side opposite the polar angle. Because this is math beyond my skills, I had to borrow the logic from this spherical triangle calculator. Special thanks to emfril!
The jsfiddle has been updated. My production roll getter has been updated as follows:
function $(what) { return document.getElementById(what); }
var maps = {
get roll() {
function acos(what) {
return (Math.abs(Math.abs(what) - 1) < 0.0000000001)
? Math.round(Math.acos(what)) : Math.acos(what);
}
function sin(what) { return Math.sin(what); }
function cos(what) { return Math.cos(what); }
function abs(what) { return Math.abs(what); }
function deg2rad(what) { return what * Math.PI / 180; }
function rad2deg(what) { return what * 180 / Math.PI; }
var roll=0;
if (typeof maps.panorama.getPhotographerPov() != 'undefined') {
var pov = maps.panorama.getPhotographerPov(),
clientHeading = maps.panorama.getPov().heading;
while (clientHeading < 0) clientHeading += 360;
while (clientHeading > 360) clientHeading -= 360;
// Spherical trigonometry method
a1 = deg2rad(abs(pov.pitch));
a2 = deg2rad(90);
yaw = deg2rad((pov.heading < 0 ? pov.heading + 360 : pov.heading) - clientHeading);
b1 = acos((cos(a1) * cos(a2)) + (sin(a1) * sin(a2) * cos(yaw)));
if (sin(a1) * sin(a2) * sin(b1) !== 0) {
roll = acos((cos(a1) - (cos(a2) * cos(b1))) / (sin(a2) * sin(b1)));
direction = pov.heading - clientHeading;
if (direction < 0) direction += 360;
if (pov.pitch < 0)
roll = (direction < 180) ? rad2deg(roll) * -1 : rad2deg(roll);
else
roll = (direction > 180) ? rad2deg(roll) * -1 : rad2deg(roll);
} else {
// Fall back to algebraic estimate to avoid divide-by-zero
var yaw = pov.heading - 90;
if (yaw < 0) yaw += 360;
var scale = ((abs(clientHeading - yaw) / 90) - 1) * -1;
roll = pov.pitch * scale;
if (abs(roll) > abs(pov.pitch)) {
var diff = (abs(roll) - abs(pov.pitch)) * 2;
roll = (roll < 0) ? roll + diff : roll - diff;
}
}
}
return roll;
}, // end maps.roll getter
// ... rest of maps object...
} // end maps{}
After rotating the panorama tile container, the container also needs to be expanded to hide the blank corners. I was originally using the 2D law of sines for this, but I found a more efficient shortcut. Thanks Mr. Tan!
function deg2rad(what) { return what * Math.PI / 180; }
function cos(what) { return Math.cos(deg2rad(what)); }
function sin(what) { return Math.sin(deg2rad(what)); }
var W = $('map-canvas').clientWidth,
H = $('map-canvas').clientHeight,
Rot = Math.abs(maps.originPitch);
// pixels per side
maps.growX = Math.round(((W * cos(Rot) + H * cos(90 - Rot)) - W) / 2);
maps.growY = Math.round(((W * sin(Rot) + H * sin(90 - Rot)) - H) / 2);
There will be no more edits to this answer, as I don't wish to have it converted to a community wiki answer yet. As updates occur to me, they will be applied to the fiddle.