Getting two points on the edges of a rectangle - javascript

I have a rectangle and would like to:
Get a random point on one (any) of the sides.
Get a random point on one (except for the previously picked) side.
My initial approach is to create arrays for each possible side.
var arr:Array = [[{x:0,y:0}, // Top
{x:width,y:0}], //
[{x:width,y:0}, // Right
{x:width,y:height}], //
[{x:width,y:height}, // Bottom
{x:0,y:height}], //
[{x:0,y:height}, // Left
{x:0,y:0}]]; //
Then, I get the sides.
rand is an instance of Rand and has the methods:
.next() which provides a random number between 0 and 1
.between(x,y) which returns a random number between x and y.
var firstSide:Array = arr[rand.next() * arr.length];
var secondSide:Array;
do {
secondSide = arr[rand.next() * arr.length];
} while(secondSide.equals(firstSide));
Finally, I calculate my points.
var pointOnFirstSide:Object = {x:rand.between(firstSide[0].x, firstSide[1].x),
y:rand.between(firstSide[0].y, firstSide[1].y};
var pointOnSecondSide:Object = {x:rand.between(secondSide[0].x, secondSide[1].x),
y:rand.between(secondSide[0].y, secondSide[1].y};
I don't think this is the most efficient way to solve this.
How would you do it?

Assuming we have the following interfaces and types:
interface Rand {
next(): number;
between(x: number, y: number): number;
}
interface Point {
x: number;
y: number;
}
type PointPair = readonly [Point, Point];
and taking you at your word in the comment that the procedure is: first randomly pick two sides, and then pick random points on those sides... first let's see what's involved in picking two sides at random:
const s1 = Math.floor(rand.between(0, arr.length));
const s2 = (Math.floor(rand.between(1, arr.length)) + s1) % arr.length;
s1 and s2 represent the indices of arr that we are choosing. The first one chooses a whole number between 0 and one less than the length of the array. We do this by picking a real number (okay, floating point number, whatever) between 0 and the length of the array, and then taking the floor of that real number. Since the length is 4, what we are doing is picking a real number uniformly between 0 and 4. One quarter of those numbers are between 0 and 1, another quarter between 1 and 2, another quarter between 2 and 3, and the last quarter are between 3 and 4. That means you have a 25% chance of choosing each of 0, 1, 2 and 3. (The chance of choosing 4 is essentially 0, or perhaps exactly 0 if rand is implemented in the normal way which excludes the upper bound).
For s2 we now pick a number uniformly between 1 and the length of the array. In this case, we are picking 1, 2, or 3 with a 33% chance each. We add that number to s1 and then take the remainder when dividing by 4. Think of what we are doing as starting on the first side s1, and then moving either 1, 2, or 3 sides (say) clockwise to pick the next side. This completely eliminates the possibility of choosing the same side twice.
Now let's see what's involved in randomly picking a point on a line segment (which can be defined as a PointPair, corresponding to the two ends p1 and p2 of the line segment) given a Rand instance:
function randomPointOnSide([p1, p2]: PointPair, rand: Rand): Point {
const frac = rand.next(); // between 0 and 1
return { x: (p2.x - p1.x) * frac + p1.x, y: (p2.y - p1.y) * frac + p1.y };
}
Here what we do is pick a single random number frac, representing how far along the way from p1 to p2 we want to go. If frac is 0, we pick p1. If frac is 1, we pick p2. If frac is 0.5, we pick halfway between p1 and p2. The general formula for this is a linear interpolation between p1 and p2 given frac.
Hopefully between the two of those, you can implement the algorithm you're looking for. Good luck!
Link to code

jcalz already gave an excellent answer. Here is an alternate version for the variant I asked about in the comments: When you want your points uniformly chosen over two sides of the perimeter, so that if your w : h ratio was 4 : 1, the first point is four times as likely to lie on a horizontal side as a vertical one. (This means that the chance of hitting two opposite long sides is 24/45; two opposite short side, 1/45; and one of each, 20/45 -- by a simple but slightly tedious calculation.)
const rand = {
next: () => Math. random (),
between: (lo, hi) => lo + (hi - lo) * Math .random (),
}
const vertices = (w, h) => [ {x: 0, y: h}, {x: w, y: h}, {x: w, y: 0}, {x: 0, y: 0} ]
const edges = ([v1, v2, v3, v4]) => [ [v1, v2], [v2, v3], [v3, v4], [v4, v1] ]
const randomPoint = ([v1, v2], rand) => ({
x: v1 .x + rand .next () * (v2 .x - v1 .x),
y: v1 .y + rand .next () * (v2 .y - v1 .y),
})
const getIndex = (w, h, x) => x < w ? 0 : x < w + h ? 1 : x < w + h + w ? 2 : 3
const twoPoints = (w, h, rand) => {
const es = edges (vertices (w, h) )
const perimeter = 2 * w + 2 * h
const r1 = rand .between (0, perimeter)
const idx1 = getIndex (w, h, r1)
const r2 = (
rand. between (0, perimeter - (idx1 % 2 == 0 ? w : h)) +
Math .ceil ((idx1 + 1) / 2) * w + Math .floor ((idx1 + 1) / 2) * h
) % perimeter
const idx2 = getIndex (w, h, r2)
return {p1: randomPoint (es [idx1], rand), p2: randomPoint (es [idx2], rand)}
}
console .log (
// Ten random pairs on rectangle with width 5 and height 2
Array (10) .fill () .map (() => twoPoints (5, 2, rand))
)
The only complicated bit in there is the calculation of r2. We calculate a random number between 0 and the total length of the remaining three sides, by adding all four sides together and subtracting off the length of the current side, width if idx is even, height if it's odd. Then we add it to the total length of the sides up to and including the index (where the ceil and floor calls simply count the number of horizontal and vertical sides, these values multiplied by the width and height, respectively, and added together) and finally take a floating-point modulus of the result with the perimeter. This is the same technique as in jcalz's answer, but made more complex by dealing with side lengths rather than simple counts.
I didn't make rand an instance of any class or interface, and in fact didn't do any Typescript here, but you can add that yourself easily enough.

Related

Figuring out the value for PI

Let's say I have a function called bars()
bars () {
const bars = []
for (let i = 0; i < this.numberOfBars; i++) {
bars.push(Math.sqrt(this.numberOfBars * this.numberOfBars - i * i))
}
return bars
}
If I'm reducing the bars array to approximate PI, what should be on the right side of the arrow function?
PI = bars().reduce((a, b) =>
I tried adding the values and dividing by the number of bars, but I'm not getting anywhere near the approximation of Pi. I feel like there's a simple trick that I'm missing.
Your funcion seems to list lengths of "bars" in a quarter of a circle, so we have to add them all up (to have the area of the quarter of a circle), then multiply by 4 (because there is 4 quarter) and the divide by this.numberOfBars ^ 2 because area = π * r^2, but like we have to know the radius, it is better using a pure function :
// Your function rewritten as a pure one
const bars = numberOfBars => {
const bars = []
for (let i = 0; i < numberOfBars; i++) {
bars.push(Math.sqrt(numberOfBars * numberOfBars - i * i))
}
return bars
}
// Here we take 1000 bars as an example but in your case you replace it by this.numberOfBars
// Sum them all up, multiply by 4, divide by the square of the radius
const PI = bars(1000).reduce((g, c) => g + c) * 4 / Math.pow(1000, 2)
console.log(PI)
/** Approximates PI using geometry
* You get a better approximation using more bars and a smaller step size
*/
function approximatePI(numberOfBars, stepSize) {
const radius = numberOfBars * stepSize;
// Generate bars (areas of points on quarter circle)
let bars = [];
// You can think of i as some point along the x-axis
for (let i = 0; i < radius; i += stepSize) {
let height = Math.sqrt(radius*radius - i*i)
bars.push(height * stepSize);
}
// Add up all the areas of the bars
// (This is approximately the area of a quarter circle if stepSize is small enough)
const quarterArea = bars.reduce((a, b) => a + b);
// Calculate PI using area of circle formula
const PI = 4 * quarterArea / (radius*radius)
return PI;
}
console.log(`PI is approximately ${approximatePI(100_000, 0.001)}`);
There is no reason to push all terms to an array, then to reduce the array by addition. Just use an accumulator variable and add all terms to it.
Notice that the computation becomes less and less accurate the closer you get to the end of the radius. If you sum to half of the radius, you obtain r²(3√3+π)/24, from which you can draw π.
(Though in any case, this is one of the worst methods to evaluate π.)

are Triangles Similar?

You have two triangles a1 b1 c1 and a2 b2 c3 on a plane. Your task is to determine whether they are, i.e. if their corresponding angles have the same measurements.
coordinates is an
array []
let coord = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0];
where a1 is (coord[0],coord[1]), b1 (coord[2],coord[3]) ...
let s = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0]
function areTrianglesSimilar(c) {
let result = null
let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1]))
let line2 = (Math.abs(c[4]) - Math.abs(c[0])) + (Math.abs(c[5]) - Math.abs(c[1]))
let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2)))
console.log(line1, line2, line3)
let angle1 = Math.atan2(line1, line2) * 180 / Math.PI
let angle2 = Math.atan2(line1, line3) * 180 / Math.PI
let angle3 = 180 - (angle1 + angle2)
console.log(angle1, angle2, angle3)
let arr1 = []
arr1.push(angle1, angle2, angle3)
let line4 = (Math.abs(c[8]) - Math.abs(c[6])) + (Math.abs(c[9]) - Math.abs(c[7]))
let line5 = (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1]))
let line6 = Math.abs(Math.sqrt( Math.pow(line4, 2)+ Math.pow(line5, 2)))
console.log(line4, line5, line6)
let angle4 = Math.atan2(line4, line5) * 180 / Math.PI
let angle5 = Math.atan2(line4, line6) * 180 / Math.PI
let angle6 = 180 - (angle4 + angle5)
console.log(angle6, angle5, angle4)
if (arr1.includes(angle4) && arr1.includes(angle5) && arr1.includes(angle6)){
return result = true
} else return result = false
}
console.log(areTrianglesSimilar(s))
this was my try but did not pass all tests, any better idea?
Thanks to Mbo
function areTrianglesSimilar(c) {
let dx1 = c[2] - c[0];
let dy1 = c[3] - c[1];
let dx2 = c[4] - c[0];
let dy2 = c[5] - c[1];
let dx3 = c[4] - c[2];
let dy3 = c[5] - c[3];
let l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
let l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
let l3 = Math.sqrt(dx3 * dx3 + dy3 * dy3);
console.log(l1,l2,l3);
let angle12 = Math.acos((dx1 * dx2 + dy1 * dy2) / (l1 * l2));
let angle13 = Math.acos((dx1 * dx3 + dy1 * dy3) / (l1 * l3));
let angle23 = Math.acos((dx3 * dx2 + dy3 * dy2) / (l3 * l2));
console.log(angle12, angle13, angle23);
let dx4 = c[8] - c[6];
let dy4 = c[9] - c[7];
let dx5 = c[10] - c[6];
let dy5 = c[11] - c[7];
let dx6 = c[10] - c[8];
let dy6 = c[11] - c[9];
let l4 = Math.sqrt(dx4 * dx4 + dy4 * dy4);
let l5 = Math.sqrt(dx5 * dx5 + dy5 * dy5);
let l6 = Math.sqrt(dx6 * dx6 + dy6 * dy6);
console.log(l4,l5,l6);
let angle45 = Math.acos((dx4 * dx5 + dy4 * dy5) / (l4 * l5));
let angle46 = Math.acos((dx4 * dx6 + dy4 * dy6) / (l4 * l6));
let angle56 = Math.acos((dx6 * dx5 + dy6 * dy5) / (l6 * l5));
console.log(angle45, angle46, angle56);
if (angle12 == angle45 && angle13 == angle46){
console.log('result'+':'+ true);
} else console.log("result" + ":" + false);
}
let coordinates = [3, 4, 4, 7, 6, 1, -2, -1, 0, 5, 4, -7];
console.log(areTrianglesSimilar(coordinates))
Your calculation is completely wrong. Dot product approach:
dx1 = c[2] - c[0]
dy1 = c[3] - c[1]
dx2 = c[4] - c[0]
dy2 = c[5] - c[1]
dx3 = c[4] - c[2]
dy3 = c[5] - c[3]
l1 = Math.sqrt(dx1*dx1+dy1*dy1)
l2 = Math.sqrt(dx2*dx2+dy2*dy2)
l3 = Math.sqrt(dx3*dx3+dy3*dy3)
angle12 = Math.acos((dx1*dx2+dy1*dy2)/(l1*l2)
and similar for angle13, and later you need to compare only two angles for equality
if angle12 == angle45 and angle13 == angle46 ...
or use some epsylon value to avoid floating calculation errors
if abs(angle12 -angle45) < 0.0000001 ...
Moreover, you can avoid angles and compare side length ratios
if l1/l4==l2/l5 and l1/l4==l3/l6...
There are quite a number of errors here. Aside from these, you should probably consider refactoring the code into separate functions which encapsulate commonly performed calculations. This will cut down on needless repetition and make copy-paste typos less possible. It will also make the code a little more self-documenting, which allows human beings to understand what you're doing better.
Assuming you want to determine the angles of the triangles and compare them (but you could also use side length ratios as #MBo pointed out), the general approach I would follow is this:
Write a function to convert the coordinates array into a pair of Triangle objects, where a Triangle is a three-tuple of Point objects, defined like this:
type Triangle = [Point, Point, Point];
interface Point { x: number, y: number };
function toTrianges(coords: number[]): [Triangle, Triangle] {
// implement this
}
Write a function that takes three Points, A, B, and C, and returns the (absolute value of the) measure of angle ∡ABC (with B as the vertex) in, say, degrees:
function measureAngleABC(a: Point, b: Point, c: Point): number {
// implement this
}
In order to do that, you might want to write functions that turn two Points A and B and produces the Vector from A to B, and that manipulate vectors:
type Vector = Point;
function vector(a: Point, b: Point): Vector { /* impl */ }
function vectorLength(v: Vector): number { /* impl */ }
function dotProduct(v1: Vector, v2: Vector): number { /* impl */ }
Note that the (unsigned) angle between two vectors can be determined by examining their lengths and their dot product.
Once you have these, you should be able to turn a Triangle into a (sorted) triplet of its (unsigned) angles:
type TriangleAngles = [number, number, number];
function angles(triangle: Triangle): TriangleAngles { /* impl * }
And finally, write a function that compares two TriangleAngles for near-equality. Not actual equality using ===, which is fraught with troubles. Since floating-point numbers do not have infinite precision, two different calculations that should yield the same quantity might actually produce two different floating-point results. The famous example is that 0.1 + 0.2 === 0.3 is false. When you compare two TriangleAngles, you need to decide how close is "close enough" to call two triangles similar:
function areNearlyEqual(ta1: TriangleAngles, ta2: TriangleAngles): boolean {
// impl here
}
I'm not going to write out how to implement these, since this looks like an exercise that benefits you most if you actually do it, not if someone does it for you.
In any case, here are the errors I see in your code:
The line (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1])) looks like a typo with indices, as you are seemingly comparing a point from one triangle with a point on a different triangle. This sort of typo would be much less likely if you refactor so as to move from an array of numbers to something like a pair of Triangles.
All code of the form Math.abs(c[k]) for some index k is highly suspect. This treats c[k] === 100 identically to c[k] === -100. If you take a triangle and flip the sign of the x or y coordinate of one of its vertices, you are almost certainly going to change the shape of the triangle by reflecting that vertex across the x or y axis:
If your code can't tell the difference between those two triangles, it's not going to be able to accurately determine if two triangles are similar or not.
The line let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1])) and its brethren seem to looking at one of the sides of one of the triangles and adding the x component of its length to the y component of its length to get a single number. This doesn't represent much of anything that I can think of. The vector of x-component-of-length and y-component-of-length are important, but when you just add the components together you are throwing away information you need. You can verify this for yourself by coming up with a triangle where swapping c[2] and c[3] will change its shape, but the above code will not see a difference.
The line let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2))) seems to assume that line1 and line2 represent the lengths of two sides of a right triangle and line3 is the length of the hypotenuse. But unless your two sides are really perpendicular to each other, this will not be true.
The line let angle2 = Math.atan2(line1, line3) * 180 / Math.PI is calculating an angle, but what angle? You can only use the arctangent to get an angle from the opposite and adjacent sides of a right triangle. But there might be no right triangles here, and since line3 was earlier assumed to be the hypotenuse of a right triangle where one of the sides was line1, there's no way line3 is now one of the perpendicular legs.
Um, I think I have to stop here. Suffice it to say that I would be very surprised if you could get this algorithm working by tweaking it. I'd strongly recommend starting over with reusable functions that perform well-defined calculations.
Good luck.

Procedural circle mesh with uniform faces

I'm trying to create a 2d circle procedurally with uniform faces like so.
Normally, I would create it with a triangle fan structure, but I need faces to be roughly identical. I looked for examples, but I could only find "cube to sphere" examples. A compromise could be something similar to this :
Could you help me finding a way to draw this structure? I'd like to do it in C# but js or even pseudo code would do!
Thanks a lot
You got me interested with your question, and I think I've got the solution you were looking for. Here is how we can create a topology that you desired:
1) We start with a hexagon. Why hexagon and not other shape? Because hexagon is the only magic shape with its radius equal too the length of its side. We will call this radius R. We will now try to create a shape that resembles circle and is made of triangles with side length approximately R.
2) Now imagine some concentric circles, with radius R, 2R, 3R and so on - the more, the higher is the resolution.
3) Circle number 1 has radius R. We will now replace that circle with a hexagon with radius R.
4) We will now add more nodes on second circle to expand our hexagon. What is the circumference of circle number N? It is 2PiRN. Now we want to divide it into X edges of length approximately R. Hence X=2PiN, which is approximately 6N. So we will divide first circle into 6 edges (hexagon), second one into 12, then 18, 24 and so on.
5) Now we have lots of circles divided into edges. We now need to connect edges into triangles. How do we build triangles between circle N (outer) and N-1 (inner)? Outer circle has 6 more edges than the inner one. If they had identical number of vertices, we could connect them with quads. But they don't. So, we will still try to build quads, but for each N quads we build, we will need to add 1 triangle. Each quad uses 2 vertices from inner and 2 vertices from outer circle. Each triangle uses 2 vertices from the outer circle and only 1 from inner, thus compensating the excess of vertices.
6) And now at last, there is some tested sample code that does what you need. It will generate a circle with uniform topology, with center point at origin and radius of 1, divided into *resolution sub circles. It could use some minor performance optimization (that's out of scope for now), but all in all it should do the job.
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class UniformCirclePlane : MonoBehaviour {
public int resolution = 4;
// Use this for initialization
void Start() {
GetComponent<MeshFilter>().mesh = GenerateCircle(resolution);
}
// Update is called once per frame
void Update() {
}
// Get the index of point number 'x' in circle number 'c'
static int GetPointIndex(int c, int x) {
if (c < 0) return 0; // In case of center point
x = x % ((c + 1) * 6); // Make the point index circular
// Explanation: index = number of points in previous circles + central point + x
// hence: (0+1+2+...+c)*6+x+1 = ((c/2)*(c+1))*6+x+1 = 3*c*(c+1)+x+1
return (3 * c * (c + 1) + x + 1);
}
public static Mesh GenerateCircle(int res) {
float d = 1f / res;
var vtc = new List<Vector3>();
vtc.Add(Vector3.zero); // Start with only center point
var tris = new List<int>();
// First pass => build vertices
for (int circ = 0; circ < res; ++circ) {
float angleStep = (Mathf.PI * 2f) / ((circ + 1) * 6);
for (int point = 0; point < (circ + 1) * 6; ++point) {
vtc.Add(new Vector2(
Mathf.Cos(angleStep * point),
Mathf.Sin(angleStep * point)) * d * (circ + 1));
}
}
// Second pass => connect vertices into triangles
for (int circ = 0; circ < res; ++circ) {
for (int point = 0, other = 0; point < (circ + 1) * 6; ++point) {
if (point % (circ + 1) != 0) {
// Create 2 triangles
tris.Add(GetPointIndex(circ - 1, other + 1));
tris.Add(GetPointIndex(circ - 1, other));
tris.Add(GetPointIndex(circ, point));
tris.Add(GetPointIndex(circ, point));
tris.Add(GetPointIndex(circ, point + 1));
tris.Add(GetPointIndex(circ - 1, other + 1));
++other;
} else {
// Create 1 inverse triange
tris.Add(GetPointIndex(circ, point));
tris.Add(GetPointIndex(circ, point + 1));
tris.Add(GetPointIndex(circ - 1, other));
// Do not move to the next point in the smaller circle
}
}
}
// Create the mesh
var m = new Mesh();
m.SetVertices(vtc);
m.SetTriangles(tris, 0);
m.RecalculateNormals();
m.UploadMeshData(true);
return m;
}
}
Final Result:

Customize chances of picking element randomly

I have two defined objects: x and y
If I do following, chances of getting either x or y are equal – 1 of 2:
var primary = [x, y];
var secondary = primary[Math.floor(Math.random() * primary.length)];
This would take a 1 of 3 (smaller) chances of getting y:
var primary = [x, x, y];
// secondary unchanged
etc.
But I believe, this is bad practice because if I'd wanted to set infinitesimal chances (e.g. 1 of 1e9) of getting y, I would have to do something extremely wasteful like this:
var primary = new Array();
for (i = 1e9 - 1; i--; i) primary.push(x);
primary.push(y);
var secondary = primary[Math.floor(Math.random() * primary.length)];
Is there a better way to do this in JavaScript?
Without digging too much into ECMAScript specification and its actual implementations, Math.random() appears to produce a number from the range 0..1 in a smooth fashion. This means that the number is 50% likely to be less than 0.5, 25% likely to be less than 0.25, 10% likely to be less than 0.1, etc.
To get x 17% of the time (and, conversely, y 83% of the time), one could use the corresponding number to be a gateway for Math.random()’s results:
const x = "x";
const y = "y";
function getRandom() {
return Math.random() < 0.17 ? x : y;
}
const value = getRandom();
// 17% "x", 83% "y"
This works fine for two values, but working with lists of 3+ elements would require different thinking.

Smoothing out values of an array

If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance?
I’ve looked into windowing the data using something called the Gaussian function for a 1-dimensional case, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values.
Context: I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time t to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied.
The language I'm using is Javascript.
EDIT: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array
[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]
and used a Gaussian function to map it to
[ 8.35, 9.35, 8.59, 8.98, 9.63, 7.94, 5.78, 7.32]
Notice how the numbers are much closer to each other.
EDIT 2: It worked! Thanks to user Awal Garg's algorithm, here are the results:
No smoothing
Some smoothing
Maximum smoothing
EDIT 3: Here's my final code in JS. I tweaked it so that the first and last elements of the array were able to find its neighbors by wrapping around the array, rather than calling itself.
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function smooth(values, alpha) {
var weighted = average(values) * alpha;
var smoothed = [];
for (var i in values) {
var curr = values[i];
var prev = smoothed[i - 1] || values[values.length - 1];
var next = curr || values[0];
var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));
smoothed.push(improved);
}
return smoothed;
}
function average(data) {
var sum = data.reduce(function(sum, value) {
return sum + value;
}, 0);
var avg = sum / data.length;
return avg;
}
smooth(array, 0.85);
Interesting question!
The algorithm to smooth out the values obviously could vary a lot, but here is my take:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise.
My method basically computes the average of all the elements in the array in advance, and uses that as a major factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one newly computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average.
A slight variation to loosen out the smoothing would be this:
"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];
function avg (v) {
return v.reduce((a,b) => a+b, 0)/v.length;
}
function smoothOut (vector, variance) {
var t_avg = avg(vector)*variance;
var ret = Array(vector.length);
for (var i = 0; i < vector.length; i++) {
(function () {
var prev = i>0 ? ret[i-1] : vector[i];
var next = i<vector.length ? vector[i] : vector[i-1];
ret[i] = avg([t_avg, prev, vector[i], next]);
})();
}
return ret;
}
function display (x, y) {
console.clear();
console.assert(x.length === y.length);
x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}
display(array, smoothOut(array, 0.85));
which doesn't take the averaged value as a major factor.
Feel free to experiment, hope that helps!
The technique you describe sounds like a 1D version of a Gaussian blur. Multiply the values of the 1D Gaussian array times the given window within the array and sum the result. For example
Assuming a Gaussian array {.242, .399, .242}
To calculate the new value at position n of the input array - multiply the values at n-1, n, and n+1 of the input array by those in (1) and sum the result. eg for [3, 5, 0, 8, 4, 2, 6], n = 1:
n1 = 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721
You can alter the variance of the Gaussian to increase or reduce the affect of the blur.
i stumbled upon this post having the same problem with trying to achieve smooth circular waves from fft averages.
i've tried normalizing, smoothing and wildest math to spread the dynamic of an array of averages between 0 and 1. it is of course possible but the sharp increases in averaged values remain a bother that basically makes these values unfeasable for direct display.
instead i use the fft average to increase amplitude, frequency and wavelength of a separately structured clean sine.
imagine a sine curve across the screen that moves right to left at a given speed(frequency) times the current average and has an amplitude of current average times whatever will then be mapped to 0,1 in order to eventually determine 'the wave's' z.
the function for calculating size, color, shift of elements or whatever visualizes 'the wave' will have to be based on distance from center and some array that holds values for each distance, e.g. a certain number of average values.
that very same array can instead be fed with values from a sine - that is influenced by the fft averages - which themselves thus need no smoothing and can remain unaltered.
the effect is pleasingly clean sine waves appearing to be driven by the 'energy' of the sound.
like this - where 'rings' is an array that a distance function uses to read 'z' values of 'the wave's x,y positions.
const wave = {
y: height / 2,
length: 0.02,
amplitude: 30,
frequency: 0.5
}
//var increment = wave.frequency;
var increment = 0;
function sinewave(length,amplitude,frequency) {
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, height / 2);
for (let i = 0; i < width; i+=cellSize) {
//ctx.lineTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude)
ctx.lineTo(i, wave.y + Math.sin(i * length + increment) * amplitude);
rings.push( map( Math.sin(i * length + increment) * amplitude,0,20,0.1,1) );
rings.shift();
}
ctx.stroke();
increment += frequency;
}
the function is called each frame (from draw) with the current average fft value driving the sine function like this - assuming that value is mapped to 0,1:
sinewave(0.006,averg*20,averg*0.3)
allowing fluctuating values to determine wavelength or frequency can have some visually appealing effect. however, the movement of 'the wave' will never seem natural.
i've accomplished a near enough result in my case.
for making the sine appear to be driven by each 'beat' you'd need beat detection to determine the exact tempo of 'the sound' that 'the wave' is supposed to visualize.
continuous averaging of distance between larger peaks in the lower range of fft spectrum might work there with setting a semi fixed frequency - with edm...
i know, the question was about smoothing array values.
forgive me for changing the subject. i just thought that the objective 'sound wave' is an interesting one that could be achieved differently.
and just so this is complete here's a bit that simply draws circles for each fft and assign colour according to volume.
with linewidths relative to total radius and sum of volumes this is quite nice:
//col generator
function getCol(n,m,f){
var a = (PIx5*n)/(3*m) + PIdiv2;
var r = map(sin(a),-1,1,0,255);
var g = map(sin(a - PIx2/3),-1,1,0,255);
var b = map(sin(a - PIx4/3),-1,1,0,255);
return ("rgba(" + r + "," + g + "," + b + "," + f + ")");
}
//draw circles for each fft with linewidth and colour relative to value
function drawCircles(arr){
var nC = 20; //number of elem from array we want to use
var cAv = 0;
var cAvsum = 0;
//get the sum of all values so we can map a single value with regard to this
for(var i = 0; i< nC; i++){
cAvsum += arr[i];
}
cAv = cAvsum/nC;
var lastwidth = 0;
//draw a circle for each elem from array
//compute linewith a fraction of width relative to value of elem vs. sum of elems
for(var i = 0; i< nC; i++){
ctx.beginPath();
var radius = lastwidth;//map(arr[i]*2,0,255,0,i*300);
//use a small col generator to assign col - map value to spectrum
ctx.strokeStyle = getCol(map(arr[i],0,255,0,1280),1280,0.05);
//map elem value as fraction of elem sum to linewidth/total width of outer circle
ctx.lineWidth = map(arr[i],0,cAvsum,0,width);
//draw
ctx.arc(centerX, centerY, radius, 0, Math.PI*2, false);
ctx.stroke();
//add current radius and linewidth to lastwidth
var lastwidth = radius + ctx.lineWidth/2;
}
}
codepen here: https://codepen.io/sumoclub/full/QWBwzaZ
always happy about suggestions.

Categories