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>
Related
A typical random walk does not care about direction changes. Each iteration generates a new direction. But if you imagine a point animated on a random walk, it will mostly jump around. So, the goal is to have a smoother curve depending on the previously calculated points.
How to adjust a random walk function to have smoother directional changes?
My main idea is to have a method that generates a new point with x and y coordinates, but looks after the previous step and decreases the size of the next step (const radius), if the rotation (directional change) comes closer to 180°.
Therefore, I am using D3js to randomly take a new step in any x and y direction. At the end I'll get an array of all past steps limited by the maximum amount of steps. The radius gives an orientation how long an average step should be taking on the x and y axis'.
const history = [];
const steps = 10;
const radius = 1;
let point = {
x: 0,
y: 0,
radians: null
};
for (let i = 0; i < steps; i++) {
console.log(point);
history.push(point);
const previousPoint = Object.assign({}, point);
point.x += radius * d3.randomNormal(0, 1)();
point.y += radius * d3.randomNormal(0, 1)();
point.radians = Math.atan2(
point.y - previousPoint.y,
point.x - previousPoint.x
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.js"></script>
Instead of using a coordinates based random walk, I decided to randomly generate each iteration a new radians. So the new and previous radians can be compared to each others to decide with velocity the new point will get. Depending on the minimum range between these radians' the volicity will be set. Afterwards a simple sine and cosine calculation have to be down to generate the coordinates of the new point.
At least I've achieved my final goal: https://beta.observablehq.com/#nextlevelshit/gentlemans-random-walk-part-3
const steps = 10;
const stepSize = 10;
let point = {
x: 0,
y: 0,
radians: randomRadians(),
velocity: 0
};
for (let i = 0; i < steps; i++) {
console.log(point);
const radians = randomRadians();
const velocity = 1 - minimumDifference(radians, point.radians) / Math.PI;
point = {
// Coordinates calculated depending on random radians and velocity
x: Math.sin(radians * Math.PI) * stepSize * velocity + point.x,
y: Math.cos(radians * Math.PI) * stepSize * velocity + point.y,
radians: radians, // Randomly generated radians
velocity: velocity // Velocity in comparison to previous point
};
}
function randomRadians() {
return randomFloat(- Math.PI, Math.PI);
}
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
function minimumDifference(x, y) {
return Math.min((2 * Math.PI) - Math.abs(x - y), Math.abs(x - y));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.8.0/d3.js"></script>
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:
I have a system of vertices with lines connecting them. I measure the angle at each vertex by comparing itself and it's "next" point (the vertices are a doubly linked list).
var next = this.get("next"),
dX = next.get("x") - this.get("x"),
dY = next.get("y") - this.get("y"),
radians = Math.atan2(dY, dX);
When this angle between them hits some threshold, like +/- 2 degrees from a 45 degree... so like 47 degrees and we want to call it 45... I need to move this point to the x,y that would be dictated should it have been 45 degrees. This same thing applies to 135, 90, 180, etc.
I can detect the angle and whether we're within the snap-to-45 zone easy enough, and I know which angle we ought to set it to. What I don't know how to find is the x,y given that new angle.
if(CLOSE_ENOUGH_TO_SNAP) {
newAngle = Math.round(angle / 45) * 45;
this.set({
x: something,
y: something
});
}
So in the below image, this angle ought to snap to 90 and so I ought to be able to calculate a new x,y given that it's 90, not 92.
in psuedocode:
point dif = currentPt - previousPt
float distance = sqrt(dif.x * dif.x + dif.y * dif.y)
float newCurrentX = previousPt.x + distance * cos(newAngle)
floar newCurrentY = previousPt.y + distance * sin(newAngle)
However, if all the new angles are multiples of 45, you could avoid using sin and cos.
For a multiple of 90 degress (or zero degrees),
if (newAngle is 90) newCurrentY = previousPt.y + distance
else if (newAngle is 0) newCurentX = previousPt.x + distance,
etc.
for multiples of 45 degress:
else if (newAngle is 135) {
shift = distance * CONST_SIN_OF_45;
newCurrentX = previousPt.x - shift;
newCurrentY = previousPt.y + shift;
}
This is all about mathematics. It's a shame that I'v forgotten those I learned in scool.
OK, I'm trying to get the image dimension after rotation (using canvas) with a certain angle in Javascript.
Since I don't have any tools other than MSPaint here, I'll re-use your image:
Say your original rectangle's size is R(ectangle)W(idth) * RH(eight),
in this case RW=200, RH=80;
After rotating a certain angle A, counterclockwise,
where 0deg <= A <= 90deg in degrees (or 0 <= A <= Math.PI/2 in radians),
in this case A=30deg or A=Math.PI/6,
In the new "outer" rectangle, each side is divided by two parts (for the convenience of describing; corresponding to the image).
On the left side, let's say the upper (purple) part is called N(ew)H(eight)U(p), and the lower (red) part is called NHL(ow);
Same rule on the bottom side, we have NW(idth)L(eft) (blue) and NWR(ight) (orange).
So the size (area) of new rectangle would be (NHU + NHL) * (NWL + NWR)
According to the definition of sin and cos:
NWL = RW * Math.cos(A); //where A is in radians
NHL = RW * Math.sin(A);
NHU = RH * Math.cos(A);
NWR = RH * Math.sin(A);
(if you're using A in degrees, replace A to Math.PI*A/180).
So the new "outer" width would be NWL + NWR, and new "outer" height would be NHU + NHL, and now you can calculate everything.
Here's a drop-in function that implements #Passerby's solution + a couple other safeguards:
function imageSizeAfterRotation(size, degrees) {
degrees = degrees % 180;
if (degrees < 0) {
degrees = 180 + degrees;
}
if (degrees >= 90) {
size = [ size[1], size[0] ];
degrees = degrees - 90;
}
if (degrees === 0) {
return size;
}
const radians = degrees * Math.PI / 180;
const width = (size[0] * Math.cos(radians)) + (size[1] * Math.sin(radians));
const height = (size[0] * Math.sin(radians)) + (size[1] * Math.cos(radians));
return [ width, height ];
}
// USAGE:
imageSizeAfterRotation([ 200, 80 ], 30) // [ 213.20508075688775, 169.28203230275508 ]
I'm making a web page that includes a clock with an arrow in the center. When the user clicks on an hour, the arrow rotates to point to what he/she has clicked.
I'm using a jQuery image rotate plugin (jQueryRotate) to rotate the arrow.
Here is the current code to compute the number of degrees to rotate:
var numTiles = $("ul li").size(); // Number of tiles is however many are listed in the UL, which is 12
var sel = 0; // Default hour selection
var rot = 0; // Default rotation is at the top (0 degrees)
var gap = 360 / numTiles; // Degrees between each tile
function rotateArrow(num) {
rot = num * gap;
$("#arrow").rotateAnimation(rot);
sel = num;
}
When the user clicks one of the hours, it passes num as a value of 1 through 12.
It works fine, but the problem is that if the arrow is pointing to 1 o'clock, and the user clicks 11 o'clock, the arrow rotates clockwise 300 degrees, when it would make more sense to rotate 60 degrees counterclockwise.
So, how can I write an equation to take the current hour (num) and the hour clicked (sel), and output a value as a positive or negative number, which equals the number of degrees to rotate that is most efficient, rather than just rotate only in one direction?
Any advice is appreciated. Let me know if you have any questions. Thanks!
Basically the closest rotation will always be less than 180 degrees, so if your angle is greater than 180, just subtract 360 from it to get the negative angle. Taking your own example, if you end up with 300 degrees, subtract 360 to get -60 degrees.
So to add to your current line of code:
rot = num * gap;
all you need is:
if (rot > 180)
rot -= 360;
This does the rather boring job:
function diff(x, y) {
var a = (x * Math.PI / 180) - Math.PI;
var b = (y * Math.PI / 180) - Math.PI;
return Math.atan2(Math.sin(b - a), Math.cos(b - a)) * (180 / Math.PI);
}
It returns -180 to 180, depending on which rotation will be the shortest.
diff(360, 20)
> 19.999999999999993
diff(20, 360)
> -19.999999999999993
diff(0, 160)
> 160
diff(0, 190)
> -170