I'm given dynamic values that plot a triangle wave t between -1 and 1. The starting value is 0. I would like to manipulate these values to the following:
oscillate between 0 and 1
start at .5 (shift its phase)
increase its frequency by a factor of 3 or more (by 2 is still too slow)
Using the Wikipedia and Wolfram entries on Triangle Waves, I'm able to partially accomplish this using formulas similar to this:
But having control over freq in particular has got me stuck.
For example, (t+1)/2 takes care of 1 and 2 above, but #3 (freq) is only doubled... How would I triple or quadruple the frequency?
Let me use the JS syntax since we don't have MathJAX here on SO.
I assume you are familiar with this equation
y = abs( 2*x % 2 - 1)
The values are not causal:
The modulus is necessary to create a periodic pattern. A value of 2 is necessary so that the maximum value the function reaches before starting a new cycle is 2 (actually 2 is a Least Upper Bound, but that's fine).
A maximum amplitude of 2 is necessary so that after subtracting 1, the function image ranges in [-1, 1).
Subtracting 1 is necessary so that with an absolute value the function get flipped for negative value, assuming the triangular shape and ranging in [0, 1]
Multiplying by 2 is necessary so that x runs twice as faster, making the function complete an emi-cycle at 0.5 (thus a cycle completes in 1 unit).
This function than has amplitude one and frequency one, assuming the x-axis is time (if it's space then it has wave-length of 1 unit of space).
Scaling and offsetting the function is easy (thanks to the amplitude of unitary value):
y = abs( 2*x % 2 - 1) * scale - offset
For example y = abs( 2*x % 2 - 1) * 3 - 1.5
Note that this function still has frequency 1.
To change the frequency it suffices to make x run faster by multiplying it
y = abs( 2*freq*x % 2 - 1) * scale - offset
to add a phase we just need to add an offset to x
y = abs( (2*freq*x + phase) % 2 - 1) * scale - offset
The phase is in the range [0, 2] where 0 is 0% and 2 is 100%.
Here an example of a wave with amplitude 1, offset 1.5, frequency 1/3 and phase 1 (50%):
For visual reference, a better typed version of the final formula is
Related
This question is for a deeper understanding of my previous question about large size Canvas animation. The question is here: Repeat HTML canvas element (box) to fill whole viewport
I am trying to understand the logic behind the TypedArray - Uint8ClampedArray. I will first start with my research and get to the question itself later.
So, ImageData represents the pixel data of the HTML5 Canvas. It allows for much faster performance and is good for heavy animations. After we have our ImageData object, we create a buffer space for it. Because we can not read & write directly from / to buffer, we pass this buffer to a TypedArray. In this case Uint8ClampedArray which is like a normal array and allows to access data inside it.
Each pixel on our canvas is represented by 4 integer values that stand for red, green, blue, alfa - as in RGBA - ranging from 0 to 255. Each of these value is assigned to an Uint8ClampedArray index starting from 0 and the array is divided into chunks of 4. So first 4 values are very first pixel, second 4 values are 2nd pixel and so on. The Cavnas pixels are being read from left to right, row by row.
So if for example we want to get array index of red value of the pixel at xCoord = 3; yCoord = 1; canvasWidth = 10;. The formula from MDN: Pixel manipulation with canvas suggests that we do the following math:
var red = y * (width * 4) + x * 4; = 1 * 10 * 4 + 3 * 4 = 52;
But if we try to do the same manually, and just calculate ourselves pixel by pixel, we don't get the same value. It's always a little off. How would we calculate manually? In this picture we start from 0 X 0 to 0 X 9 and to 1 X 3. Because we start from top left and move toward right, it's inverted and Y is our first coordinate and X is our second coordinate. From 0 X 0 to 0 X 9 we record 40 values in total (4 values on each pixel, 10 pixels in total width); From 1 X 0 to 1 X 3 we record 16 values in total. We get 56th index at the end, instead of 52 as we had calculated using the formula.
So, please help me understand the whole logic in Uint8ClampedArray and how it's calcualted.
From 1 X 0 to 1 X 3 we record 16 values in total
The last 4 bytes of these 16 do represent the pixel at (3, 1). The red channel is the first of these, preceded by 12 bytes for the pixels to the left and 40 bytes for the pixels in the first row. It is sitting at index 52 in the overall array.
Remember that arrays are indexed as
0 1 2
+---+---+--
| | |
+---+---+--
not as
+---+---+--
| 0 | 1 | 2
+---+---+--
I'm using this equation to calculate a series of points along a quadratic curve:
// Returns a point on a quadratic bezier curve with Robert Penner's optimization of the standard equation
result.x = sx + t * (2 * (1 - t) * (cx - sx) + t * (ex - sx));
result.y = sy + t * (2 * (1 - t) * (cy - sy) + t * (ey - sy));
Sadly the points are unevenly distributed, as you can see in the dashed-line rendering below. The points are denser in the middle of the curve, and are further spaced apart near the edges. How can I calculate a evenly distributed set of points along a quadratic bezier curve?
Please note that I'm using this for rendering a dashed line, so a slow solution in MATLAB or something will not do. I need a fast solution that will fit inside a renderer. This is not for research or a one-off calculation!
Edit: I'm not asking how to accomplish the above. The above is MY RENDERING! I already know how to estimate the length of a bezier, calculate the number of points, etc, etc. What I need is a better bezier point interpolation algorithm since the one I have calculates points unevenly distributed along the curve!
You want to generate equidistant (by arc length) subdivision of quadratic Bezier curves.
So you need subdivision procedure and function for calculation of curve length.
Find length of the whole curve (L), estimate desired number of segments (N), then generate subdivision points, adjusting t parameters to get Bezier segments with length about L/N
Example: you find L=100 and want N=4 segments. Get t=1/2, subdivide curve by two parts and get length of the first part. If length > 50, diminish t and subdivide curve again. Repeat (use binary search) until length value becomes near 50. Remember t value and do the same procedure to get segments with length=25 for the first and for the second halves of the curve.
This approach uses the THREE.js library, which is not in the OP's question, but may be useful if only to look at how they approach it:
var curve = new THREE.QuadraticBezierCurve(
new THREE.Vector2( -10, 0 ),
new THREE.Vector2( 20, 15 ),
new THREE.Vector2( 10, 0 )
);
var points = curve.getSpacedPoints(numPoints);
I have a healthbar that i want to use a min/max and actual number.
6 is min
8 is max
the actual number will float between them with 7 as the beginning number.
tried ((max_n + min_n) / act_n)*100, but of course that doesn't work.
i think i was sick that day when they went over that in school (eyeroll)
If you have a floating point value that is (6, 8) and you want the distance between the value and minimum expressed as a different between the maximum -
var min = 6,
max = 8,
current = 7,
difference = max - min,
percent = (current - min) / difference; // <-- is the value you're after
// it is 50% between (6,8)
I'm not sure I understand what you're doing, but I believe you're calculating the average the wrong way.
Shouldn't you have the actual value be divided by the total possible?
Currently, your logic looks like this:
(8 + 6 (14) / Actual (7) )*100, which would be 14/7 = 2 * 100 = 200.
I think you need to reverse these two arguments, the min+max and the actual value.
If it were me, instead of doing it that way, I would actually subtract the minimum amount from both, and then divide it by the difference between the two.
If the 6 and 8 are dynamic, this may look like:
( (act_n - min_n) / (max_n - min_n) )*100
Or, with values, that would be:
( (7.2 - 6) / (8 - 6)*100
(1.2 / 2)*100 = 60, so your percentage would be 60%
I don't use Javascript, so please ignore any spacing errors I made. I don't know how strict the language is.
This calculates vertex coordinates on ellipse:
function calculateEllipse(a, b, angle)
{
var alpha = angle * (Math.PI / 180) ;
var sinalpha = Math.sin(alpha);
var cosalpha = Math.cos(alpha);
var X = a * cosalpha - b * sinalpha;
var Y = a * cosalpha + b * sinalpha;
}
But how can I calculate the "angle" to get equal or roughly equal circumference segments?
So from what Jozi's said in the OP's comments, what's needed isn't how to subdivide an ellipse into equal segments (which would require a whole bunch of horrible integrals), it's to construct an ellipse from line segments of roughly equal length.
There are a whole pile of ways to do that, but I think the best suited for the OP's purposes would be the concentric circle method, listed on the page as 'the draftman's method'. If you don't mind installing the Mathematica player, there's a neat lil' app here which illustrates it interactively.
The problem with those methods is that the segment lengths are only roughly equal at low eccentricities. If you're dealing in extreme eccentricities, things get a lot more complicated. The simplest solution I can think of is to linearly approximate the length of a line segment within each quadrant, and then solve for the positions of the endpoints in that quadrant exactly.
In detail: this is an ellipse quadrant with parameters a = 5, b = 1:
And this is a plot of the length of the arc subtended by an infinitesimal change in the angle, at each angle:
The x axis is the angle, in radians, and the y axis is the length of the arc that would be subtended by a change in angle of 1 radian. The formula, which can be derived using the equations in the Wikipedia article I just linked, is y = Sqrt(a^2 Sin^2(x) + b^2 Cos^2(x)). The important thing to note though is that the integral of this function - the area under this curve - is the length of the arc in the whole quadrant.
Now, we can approximate it by a straight line:
which has gradient m = (a-b) / (Pi/2) and y intercept c = b. Using simple geometry, we can deduce that the area under the red curve is A = (a+b)*Pi/4.
Using this knowledge, and the knowledge that the area under the curve is the total length of the curve, the problem of constructing an approximation to the ellipse reduces to finding say a midpoint-rule quadrature (other quadratures would work too, but this is the simplest) of the red line such that each rectangle has equal area.
Converting that sentence to an equation, and representing the position of a rectangle in a quadrature by it's left hand boundary x and its width w, we get that:
(v*m)*w^2 + (m*x+c)*w - A/k == 0
where k is the number of pieces we want to use to approximate the quadrant, and v is a weighting function I'll come to shortly. This can be used to construct the quadrature by first setting x0 = 0 and solving for w0, which is then used to set x1 = w0 and solve for w1. Then set x2 = w1, etc etc until you've got all k left-hand boundary points. The k+1th boundary point is obviously Pi/2.
The weighting function v effectively represents where the rectangle crosses the red line. A constant v = 0.5 is equivalent to it crossing in the middle, and gets you this with 10 points:
but you can play around with it to see what better balances the points. Ideally it should stay in the range [0, 1] and the sum of the values you use should be k/2.
If you want an even better approximation without messing around with weighting functions, you could try least-squares fitting a line rather than just fitting it to the endpoints, or you could try fitting a cubic polynomial to the blue curve instead of a linear polynomial. It'll entail solving quartics but if you've a maths package on hand that shouldn't be a problem.
Too long for a comment, so I suppose this has to be an answer ...
Here's a mathematically simple approach to forming a first order approximation. Pick one quadrant. You can generate the data for the other quadrants by reflection in the X and Y axis. Calculate (x,y) for the angle = 0 degrees, 1 degree, ... 90 degrees. Now you want the little lengths joining consecutive points. If (x_n, y_n) are the coordinates at angle = n, then Pythagoras tells us the distance D between points (x_n, y_n) and (x_n+1, y_n+1) is D = sqrt((x_n+1 - x_n)^2 + (y_n+1 - y_n)^2). Use this formula to produce a table of cumulative distances around the ellipse for angles from 0 degrees to 90 degrees. This is the inverse of the function you seek. Of course, you don't have to pick a stepsize of 1 degree; you could use any angle which exactly divides 90 degrees.
If you want to find the angle which corresponds to a perimeter step size of x, find the largest angle n in your table producing a partial perimeter less than or equal to x. The partial perimeter of angle n+1 will be larger than x. Use linear interpolation to find the fractional angle which corresponds to x.
All we are doing is approximating the ellipse with straight line segments and using them instead of the original curve; its a first order approximation. You could do somewhat better by using Simpson's rule or similar instead of linear interpolation.
Yes, you have to calculate the table in advance. But once you have the table, the calculations are easy. If you don't need too much accuracy, this is pretty simple both mathematically and coding-wise.
This question already has answers here:
Generating random whole numbers in JavaScript in a specific range
(39 answers)
Closed 9 years ago.
This maybe more mathematical question than programming. In JS I wanted to a function that returns a random integer number in an interval lets say 1-6 and this is what I found:
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
I feel guilty if I copy and paste this in my code. I don't understand this :
Why we subtract min from max, add 1, multiply the answer by Math.random() and then add the min. I tired with several numbers manually on paper and it work just fine ! But I don't understand why !
Assuming you already understand the behaviour of Math.floor and Math.random, here's the rest step by step:
Math.random() ↝ a random number between 0 (inclusive) and 1 (exclusive)
Math.random() * max ↝ a random number between 0 (inclusive) and max (exclusive)
Math.floor(Math.random() * max) ↝ a random integer between 0 (incl.) and max (excl.)
Math.floor(Math.random() * (max - min)) + min ↝ a random integer between min (incl.) and max (excl.)
Math.floor(Math.random() * ((max + 1) - min)) + min ↝ a random integer between min (incl.) and max+1 (excl.) (OR between min and max both inclusive)
Math.random() will give you a "real" number from 0 to 1 (not including 1.0).
That's cool and all, but what if I want a "real" number from 1 to 2?
The answer: "transform" your [0,1) into [1,2).
In practical terms, it means adding 1 to your result.
Try it out -- Math.random()+1 will give you a number from 1 to 2.
In mathematics this is known as a "mapping". That is -- for every possible real number in [0,1), find a way to "map" that real number to another real number in [1,2). That is, if I give you any real number between [0,1), you should be able to map that number -- apply that number to a function that will return a number between [1,2).
In our case, that function f(x) = x+1.
Do you see how this gives us random numbers between [1,2)? Visualize the two intervals next to each other and imagine a line going from every point in [0,1) to its corresponding map in [1,2). Now, pick a random point on [0,1) ... and follow the line. You'll follow the line to a random point in [1,2)!
Now, all complete one-to-one maps from [0,1) to [1,2) will turn a random number between [0,1) to a random number between [1,2)...but not all of them will give you an evenly distributed random number between [1,2). The mathematics behind what maps give you evenly distributed results is a bit complicated but in short, if your map only involves adding, subtracting, multiplying, and dividing by constants, it's "legal" in the sense that the results will also be evenly distributed.
So, now we know how to transform [0,1) into [1,2).
What if I want to map [0,1) onto [0,2)? I can't just add numbers anymore ...
How about I multiply everything by two?
This should work -- the function f(x) = x*2 does indeed map every point on [0,1) to a point on [0,2) --- and because it only involves multiplication by constants (2), it is a distribution-preserving map.
This works! Math.random()*2 will give you a random number between 0 and 2.
Okay, now something a bit more complicated ... transforming [0,1) into [1,3).
Multiplying by two doesn't work ... 0*2 = 0, and that's not in your target range.
Adding one doesn't work... even though 0+1 is in your target range and 1+1 is, as well, there is no way you can ever reach 3.
If we can't transform [0,1) into [1,3), let's try and see if we can transform something else into [1,3).
How about [0,2)? Yes, we can do this ... the function f(x) = x+1 perfectly maps [0,2) to [1,3). You can think of + as "shifting" the range up.
And so the solution here is clear -- first, turn [0,1) into [0,2), then turn [0,2) into [1,3).
We already know the first (f(x) = x*2), and we figured out the second (f(x) = x+1). So the "combined" transformation/map is f(x) = (x*2)+1.
That is, Math.random()*2 + 1 will give you a number from 0 to 3.
Now for the final trick...mapping [0,1) to an arbitrary range [min,max).
The secret here is to re-write this as [min,min+range), where range = max-min.
Here you can see that it's simple to transform the range [0,range) to [min,min+range) -- you just add "min" to it. So if I had the range [0,range), and I wanted to get [min,min+range), i would use f(x) = x+min.
So how do we get from [0,1) to [0,range) ?
Multiply by range!
f(x) = (x*range) + min
Now writing things back to original terms, using range = max-min
f(x) = (x*(max-min)) + min
will transform a real number from [0,1) to a real number from [min,max)
I'll leave the rest (turning it into a useful integer) to you
0 <= Math.random() < 1 =>
0 <= Math.random() * 6 < 6 =>
0 <= Math.floor( Math.random() * 6 ) <= 5
then you add 'min' so it would look like this:
min <= Math.floor( Math.random() * 6 ) <= 5 + min
in your exemple, for min = 1 you will have all the numbers in 1-6.
I hope now is much clear.
Here's an explanation of your code:
Math.random() generates a random number between 0 and 1 (not including 1).
You need to scale that value based on the range of numbers you want. Your range is how far from your min desired number to your max desired number which is max - min.
If you want to include the max value in the range of numbers generated, then use max - min + 1
You then need to make sure the random number starts at the right base rather than 0 so you add min to it.
Then, if you want it to be an integer you call Math.floor() to truncate it to the next lowest integer.
So, if you just had this:
Math.floor(Math.random())
You would always get zero. Because Math.floor() of a float value between 0 and 1 (not including one) will always truncate down to 0.
Then, if you expand the range with:
Math.floor(Math.random() * (max - min + 1))
You would now get a random number between 0 and max - min including the larger value.
So, to then get it to start at the right base, you add in min like this:
Math.floor(Math.random() * (max - min + 1)) + min