Are two triangles similar? - javascript
As a part time project I am working on some geometry utilities and have come across a relatively simple question that seems to have a not so simple solution.
The problem involves EPSILON being too small for the problem. To see if two triangle are similar I workout the 3 interior angles in the form of their cosines for each triangle and then sort them. I then test Math.abs(t1[0]-t2[0]) < EPSILON where t1 is the one triangle and t2 the other each containing the three angles.
I am getting about a 20% - 80% failure rate on triangles I know to be similar. When I bring EPSILON to a larger value, for example still a very small 0.0000001 there is no failure ( well not in the time I have let the tests run).
Below is the extracted relevant triangle function and I have also included the testing code as a demo below that. Click the button and its runs tests and shows the results. The triangles are randomly generated. Every so often two similar triangle are created of which about half are exact copies and the rest are a copy but scaled, mirrored, rotated and vec order shuffled while still maintaining the similarity
I would like to know how to calculate a reasonable EPSILON that will reduce the incorrect results but keep the system as accurate as possible?
Though there is also the possibility that there is another error in the test code which I will continue to check.
const EPSILON = Number.EPSILON
function Triangle(p1,p2,p3){
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
Triangle.prototype.p1 = undefined;
Triangle.prototype.p2 = undefined;
Triangle.prototype.p3 = undefined;
Triangle.prototype.isSimilar = function(triangle){
var a1,b1,c1,a2,b2,c2,aa1,bb1,cc1,aa2,bb2,cc2; //
var t1 = [];
var t2 = [];
var sortF = function(a,b){ return a-b };
// get the length squared and length of each side
a1 = Math.sqrt(aa1 = Math.pow(this.p1.x - this.p2.x, 2) + Math.pow(this.p1.y - this.p2.y, 2));
b1 = Math.sqrt(bb1 = Math.pow(this.p2.x - this.p3.x, 2) + Math.pow(this.p2.y - this.p3.y, 2));
c1 = Math.sqrt(cc1 = Math.pow(this.p3.x - this.p1.x, 2) + Math.pow(this.p3.y - this.p1.y, 2));
a2 = Math.sqrt(aa2 = Math.pow(triangle.p1.x - triangle.p2.x, 2) + Math.pow(triangle.p1.y - triangle.p2.y, 2));
b2 = Math.sqrt(bb2 = Math.pow(triangle.p2.x - triangle.p3.x, 2) + Math.pow(triangle.p2.y - triangle.p3.y, 2));
c2 = Math.sqrt(cc2 = Math.pow(triangle.p3.x - triangle.p1.x, 2) + Math.pow(triangle.p3.y - triangle.p1.y, 2));
// get the cosin of each angle for both triangle
t1[0] = (cc1 - (aa1 + bb1)) / (-2 * a1 * b1);
t1[1] = (aa1 - (cc1 + bb1)) / (-2 * c1 * b1);
t1[2] = (bb1 - (cc1 + aa1)) / (-2 * c1 * a1);
t2[0] = (cc2 - (aa2 + bb2)) / (-2 * a2 * b2);
t2[1] = (aa2 - (cc2 + bb2)) / (-2 * c2 * b2);
t2[2] = (bb2 - (cc2 + aa2)) / (-2 * c2 * a2);
t1.sort(sortF);
t2.sort(sortF);
if(Math.abs(t1[0] - t2[0]) < EPSILON && Math.abs(t1[1] - t2[1]) < EPSILON && Math.abs(t1[2] - t2[2]) < EPSILON){
return true;
}
return false;
}
function Vec(x,y){
this.x = x;
this.y = y;
}
Vec.prototype.x = undefined;
Vec.prototype.y = undefined;
UPDATE
Some more information.
Failed similar triangle using cosine of angles EPSILON : 2.220446049250313e-16
Failed Triangles ID : 94
Method : compare cosine of angles
Both Compare T1 to T2 and T2 to T1 failed
Both Triangles known to be similare
Triangle 1
p1.x = -149241116087155.97;
p1.y = -1510074922190599.8;
p2.x = -2065214078816255.8;
p2.y = 6756872141691895;
p3.x = -7125027429739231;
p3.y = -5622578541875555;
Triangle 2
p1.x = -307440480802857.2;
p1.y = -404929352172871.56;
p2.x = -3020163594243123;
p2.y = -355583557775981.75;
p3.x = 595422457974710.8;
p3.y = 2291176238828451.5;
Compare T1 to T2 Result : false
Computed values
Triangle 1 length of side and square length
length a : 8486068945686473 squared : 7.201336615094433e+31
length b : 13373575078230092 squared : 1.78852510373057e+32
length c : 8097794805726894 squared : 6.557428071565746e+31
Unsorted cosines C is angle opposite side c
cosine C : 0.8163410767815653
cosine A : 0.7960251614312384
cosine B : -0.30024590551189423
ratio a : undefined
ratio b : undefined
ratio c : undefined
Triangle2
length a : 2713171888697380.5 squared : 7.36130169761771e+30
length b : 4480825808030667.5 squared : 2.0077799921913682e+31
length c : 2843263414467020.5 squared : 8.08414684404666e+30
Unsorted cosines C is angle opposite side c
cosine C : 0.7960251614312384
cosine A : 0.8163410767815651
cosine B : -0.3002459055118942
Compare T2 to T1 Result : false
Triangle1
Computed values
Triangle 1 length of side and square length
length a : 2713171888697380.5 squared : 7.36130169761771e+30
length b : 4480825808030667.5 squared : 2.0077799921913682e+31
length c : 2843263414467020.5 squared : 8.08414684404666e+30
Unsorted cosines C is angle opposite side c
cosine a : 0.7960251614312384
cosine b : 0.8163410767815651
cosine c : -0.3002459055118942
ratio a : undefined
ratio b : undefined
ratio c : undefined
Triangle2
length a : 8486068945686473 squared : 7.201336615094433e+31
length b : 13373575078230092 squared : 1.78852510373057e+32
length c : 8097794805726894 squared : 6.557428071565746e+31
cosine a : 0.8163410767815653
cosine b : 0.7960251614312384
cosine c : -0.30024590551189423
UPDATE 2
Results output and a bug fix (apologies #lhf I did not sqrt epsilon I was still using the original constant)
This shows the results of tests on the same set of triangles. Inconsistency means that comparing triangle 1 to triangle 2 is a different result than triangle 2 to 1. Incorrect Means that two known similar triangles failed and Incorrect Inconsistency means that two known similar triangle failed one test and passed the other.
Using the ratios of lengths gave the worst result but using cosine was not much better apart from the Incorrect Inconsistency similar triangles which had a very high rate of inconsistency between compare t1 to t2 and t2 to t1 using the ratio of length. But that makes sense are the magnitude of the ratios will vary greatly depending on which order the test is done.
As you can see using the square root of EPSILON completely eliminated the error for both methods.
If lhf wishes to put the sqrt(epsilon) comment as an answer I will accept that as an answer. And thanks to everyone for their input and I have some further reading thanks to Salix
======================================
Default EPSILON : 2.220446049250313e-16
======================================
Via cosine of angles
All Inconsistency failed : 0 of 10000
Similar Incorrect failed : 1924 of 5032
Similar Incorrect Inconsistency failed : 0 of 5032
======================================
Via ratio of lengths
All Inconsistency failed : 1532 of 10000
Similar Incorrect failed : 2082 of 5032
Similar Incorrect Inconsistency failed : 1532 of 5032
======================================
Squaring EPSILON : 1.4901161193847656e-8
======================================
Via cosine of angles
All Inconsistency failed : 0 of 10000
Similar Incorrect failed : 0 of 5032
Similar Incorrect Inconsistency failed : 0 of 5032
======================================
Via ratio of lengths
All Inconsistency failed : 0 of 10000
Similar Incorrect failed : 0 of 5032
Similar Incorrect Inconsistency failed : 0 of 5032
const EPSILON = Number.EPSILON
function Triangle(p1,p2,p3){
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
Triangle.prototype.p1 = undefined;
Triangle.prototype.p2 = undefined;
Triangle.prototype.p3 = undefined;
Triangle.prototype.isSimilar = function(triangle){
var a1,b1,c1,a2,b2,c2,aa1,bb1,cc1,aa2,bb2,cc2; //
var t1 = [];
var t2 = [];
var sortF = function(a,b){ return a-b };
// get the length squared and length of each side
a1 = Math.sqrt(aa1 = Math.pow(this.p1.x - this.p2.x, 2) + Math.pow(this.p1.y - this.p2.y, 2));
b1 = Math.sqrt(bb1 = Math.pow(this.p2.x - this.p3.x, 2) + Math.pow(this.p2.y - this.p3.y, 2));
c1 = Math.sqrt(cc1 = Math.pow(this.p3.x - this.p1.x, 2) + Math.pow(this.p3.y - this.p1.y, 2));
a2 = Math.sqrt(aa2 = Math.pow(triangle.p1.x - triangle.p2.x, 2) + Math.pow(triangle.p1.y - triangle.p2.y, 2));
b2 = Math.sqrt(bb2 = Math.pow(triangle.p2.x - triangle.p3.x, 2) + Math.pow(triangle.p2.y - triangle.p3.y, 2));
c2 = Math.sqrt(cc2 = Math.pow(triangle.p3.x - triangle.p1.x, 2) + Math.pow(triangle.p3.y - triangle.p1.y, 2));
// get the cosin of each angle for both triangle
t1[0] = (cc1 - (aa1 + bb1)) / (-2 * a1 * b1);
t1[1] = (aa1 - (cc1 + bb1)) / (-2 * c1 * b1);
t1[2] = (bb1 - (cc1 + aa1)) / (-2 * c1 * a1);
t2[0] = (cc2 - (aa2 + bb2)) / (-2 * a2 * b2);
t2[1] = (aa2 - (cc2 + bb2)) / (-2 * c2 * b2);
t2[2] = (bb2 - (cc2 + aa2)) / (-2 * c2 * a2);
t1.sort(sortF);
t2.sort(sortF);
if(Math.abs(t1[0] - t2[0]) < EPSILON && Math.abs(t1[1] - t2[1]) < EPSILON && Math.abs(t1[2] - t2[2]) < EPSILON){
return true;
}
return false;
}
function Vec(x,y){
this.x = x;
this.y = y;
}
Vec.prototype.x = undefined;
Vec.prototype.y = undefined;
var iterations = 1000; // number of tests
var presentSimilar = 1/2; // odds of similar triangle
var presentSuperSimilar = 1/2; // odds of triangles being identical
var presentInfinity = 0;//1/20; // odds of a divide by zero
var presentDegenerate = 0;//1/100; // odds of a degenerate triangle can be colinear or degenerate right triangle
var v; // temp for swap
var xdx,xdy,ydx,ydy; // vars for rotation
var copyVec = [["p1","p2","p3"],["p2","p3","p1"],["p3","p1","p2"]]; // pick a vec for selecting vecs
// the triangles for testing;
var tri1 = new Triangle(new Vec(0,0), new Vec(0,0), new Vec(0,0));
var tri2 = new Triangle(new Vec(0,0), new Vec(0,0), new Vec(0,0));
// max Random
function rMax(){
return ((Math.random()*2)-1) * Number.MAX_SAFE_INTEGER;
}
// rotate function
function rotate(vec){
var x = vec.x;
var y = vec.y;
vec.x = x * xdx + y * ydx;
vec.y = x * xdy + y * ydy;
};
function translateVec(vec,x,y){
vec.x += x;
vec.y += y;
}
function translateTriangle(tri,x,y){
translateVec(tri.p1);
translateVec(tri.p2);
translateVec(tri.p3);
}
// make infinite vec to simulate the result of a divide by zero
function doInfinity(vec){
if(Math.random() < presentInfinity){
if(Math.random() < 0.5){
vec.x = Infinity;
vec.y = Infinity;
}else{
vec.x = -Infinity;
vec.y = -Infinity;
}
}
}
// create a random vector;
function randomVec(vec){
vec.x = rMax();
vec.y = rMax();
doInfinity(vec);
}
// create a random triangle
function randomTriangle(tri){
var p,r;
randomVec(tri.p1);
randomVec(tri.p2);
randomVec(tri.p3);
if(Math.random() < presentDegenerate){
r = Math.random();
if(r < 1/3){ // Degenerate right triangle
p = copyVec[Math.floor(Math.random()*3)]; // get two vec to be at the same location
tri[p[0]].x = tri[p[1]].x;
tri[p[0]].y = tri[p[1]].y;
}else // Degenerate colinear triangle
if(r < 2/3){
p = copyVec[Math.floor(Math.random()*3)]; // get two vec to be at the same location
r = Math.random();
tri[p[0]].x = (tri[p[1]].x - tri[p[2]].x) * r + tri[p[2]].x;
tri[p[0]].y = (tri[p[1]].y - tri[p[2]].y) * r + tri[p[2]].y;
}else{ // degenerate undimentioned triangle. Has not area
tri.p1.x = tri.p2.x = tri.p3.x;
tri.p1.y = tri.p2.y = tri.p3.y;
}
}
}
function runTest(){
var result1,result2,mustBeSimilar;
var countSimilar = 0;
var countNorm = 0;
var error1 = 0;
var error2 = 0;
for(var i = 0; i < iterations; i ++){
randomTriangle(tri1);
if(Math.random() < presentSimilar){
mustBeSimilar = true;
countSimilar += 1;
tri2.p1.x = tri1.p1.x;
tri2.p1.y = tri1.p1.y;
tri2.p2.x = tri1.p2.x;
tri2.p2.y = tri1.p2.y;
tri2.p3.x = tri1.p3.x;
tri2.p3.y = tri1.p3.y;
if(Math.random() >= presentSuperSimilar){
if(Math.random() < 0.5){ // swap two
v = tri2.p1;
tri2.p1 = tri2.p2;
tri2.p2 = v;
}
if(Math.random() < 0.5){ // swap two
v = tri2.p2;
tri2.p2 = tri2.p3;
tri2.p3 = v;
}
if(Math.random() < 0.5){ // swap two
v = tri2.p1;
tri2.p1 = tri2.p3;
tri2.p3 = v;
}
// scale and or mirror the second triangle
v = Math.random() * 2 - 1;
tri2.p1.x *= v;
tri2.p1.y *= v;
tri2.p2.x *= v;
tri2.p2.y *= v;
tri2.p3.x *= v;
tri2.p3.y *= v;
// rotate the triangle
v = (Math.random()- 0.5) * Math.PI * 4;
ydy = xdx = Math.cos(v);
ydx = -(xdy = Math.sin(v));
rotate(tri2.p1);
rotate(tri2.p2);
rotate(tri2.p3);
}
}else{
randomTriangle(tri2);
mustBeSimilar = false;
}
countNorm += 1;
result1 = tri1.isSimilar(tri2);
result2 = tri2.isSimilar(tri1);
if(result1 !== result2){
error1 += 1;
}
if(mustBeSimilar && (!result1 || !result2)){
error2 += 1;
}
for(var j = 0; j < 10; j++){
translateTriangle(tri1,Math.random(),Math.random());
translateTriangle(tri2,Math.random(),Math.random());
if(mustBeSimilar){
countSimilar += 1;
}
countNorm += 1;
result1a = tri1.isSimilar(tri2);
result2a = tri2.isSimilar(tri1);
if(result1a !== result2a || result1 !== result1a || result2 !== result2a){
error1 += 1;
}
if(mustBeSimilar && (!result1a || !result2a)){
error2 += 1;
}
}
}
divResult1.textContent = "Inconsistancy result failed : "+error1 + " of "+ countNorm;
divResult2.textContent = "Incorrect result failed : "+error2 + " of "+ countSimilar
}
var button = document.createElement("input");
button.type = "button"
button.value = "Run test"
button.onclick = runTest;
var divResult1 = document.createElement("div");
var divResult2 = document.createElement("div");
document.body.appendChild(button);
document.body.appendChild(divResult1);
document.body.appendChild(divResult2);
Following willywokka's comment. You may be able to just see if there is a single scale factor.
// get the length squared and length of each side
a1 = Math.sqrt(...);
....
// Sort the values so a1 < b1 < c1, a2 < b2 < c2
if(b1 < a1) { tmp = b1; b1 = a1; a1 = tmp }
if(c1 < a1) { tmp = c1; c1 = a1; a1 = tmp }
if(c1 < b1) { tmp = c1; c1 = b1; b1 = tmp }
if(b2 < a2) { tmp = b2; b2 = a2; a2 = tmp }
if(c2 < a2) { tmp = c2; c2 = a2; a2 = tmp }
if(c2 < b2) { tmp = c2; c2 = b2; b2 = tmp }
// work out the scale factors
ka = a2 / a1;
kb = b2 / b1;
kc = c2 / c1;
if( abs(ka - kb) < epsilon && abs(kc - ka) < epsilon && abs(kc - kb) < epsilon )
// similar
else
// not similar
Rather than working with an absolute epsilon you might want one which is within x% of the value. So that the values are considered equal if ka - x% < kb < ka + x%, that is (1-x/100) ka < kb < (1+x/100) ka. Or (1-x/100) < kb/ka < (1+x/100), or abs(kb/ka) < x/100.
There is a statistically more rigorous approach to the problem. This would involve a pretty precise definition of what we mean by similar and examining the distribution of triangles. Statistical shape analysis is a poor starting point. David George Kendall did work examining the shape of triangles.
If it is really just angles, you can simply compute the three inner angles of both triangles. Just use the cosine,
cos(angle) = dot (normalized(edge[i]),normalized(edge[(i+1)%3])).
with edge[i] = p[(i+1)%3] - p[i].
So you have three cos(angle) for each triangle one and two. Then just check every permutation. A triangle has only six permutations. (http://mathworld.wolfram.com/Permutation.html)
besterr = max;
for i=1..6 perm(i) in tri1
for j=1..6 perm(j) in tri2
err = 0
for k=1..3 angle
err += abs(angletri1[perm[i,k]] - angletri2[perm[j,k]])
if (err<besterr) besterr = err;
return besterr;
Does that give your expected result? We certainly can do more efficient. But this is the brute force test algorithm. One thing to note is that it only works for triangles - any vertex permutation in a triangle is the same triangle outline. That would not be the case for a bigger polygon.
Once this works you can start experimenting. Do you get the same result for angle and cos(angle)? For err += abs(d) and err += d*d? Can you just check 2 permutations by sorting angles? Remember triangle angles sum (https://en.wikipedia.org/wiki/Sum_of_angles_of_a_triangle). What computations are redundant?
And finally: Is it really the metric you want? Are two triangles with opposite winding really similar? A huge and a tiny one?
Related
Color quantization using euclidean distance gives jumpy results
I'm working on an art project which converts pixels of live video feed into corporate logos based on the distance (in RGB) between the colors of the two. While this functions, it gives a jittery result. Seem like some points in the color space teeter in a sort of superposition between two "closest" points. I'm attempting a sort of naive clustering solution right now because I believe any proper one will be too slow for live video. I'm wondering if anyone has any good ideas to solve this problem? I'll include my code and an example of the result. Thank you! (imgs array is the logos) current result: https://gifyu.com/image/fk2y function distance(r1, g1, b1, bright1, r2, g2, b2, bright2) { d = ((r2 - r1) * 0.3) ** 2 + ((g2 - g1) * 0.59) ** 2 + ((b2 - b1) * 0.11) ** 2 + ((bright2 - bright1) * 0.75) ** 2; return Math.round(d); } function draw() { if (x > 100 && z == true) { video.loadPixels(); for (var y = 0; y < video.height; y++) { for (var x = 0; x < video.width; x++) { var index = (video.width - x - 1 + y * video.width) * 4; var r = video.pixels[index]; var g = video.pixels[index + 1]; var b = video.pixels[index + 2]; var bright = (r + g + b) / 3; let least = 9999999; for (var i = 0; i < imgs.length; i++) { if ( distance( imgs[i].r, imgs[i].g, imgs[i].b, imgs[i].bright, r, g, b, bright ) < least ) { least = distance( imgs[i].r, imgs[i].g, imgs[i].b, imgs[i].bright, r, g, b, bright ); place = imgs[i].img; } } image(place, round(x * vScale), y * vScale, vScale, vScale); } } } }
polygon weird redrawing by adding & dragging dynamic points (flickering)
I have co-ordinates for the points by taking which I draw a polygon. I can add points dynamically on the edges of the polygon and when I drag any point it should drag only the connected lines. As points can be added later on the edges so the point co-ordinates need to be ordered/sorted and the polygon should be redrawn by taking the ordered/sorted points so that on dragging any point the lines connected to the dragged point only should be dragged/updated. So to order/sort the points I am sorting the co-ordinates(2D-points) clockwise using Graham Scan/ sorting by polar angle. My sorting code is I find the center of the polygon like function findCenter(points) { let x = 0, y = 0, i, len = points.length; for (i = 0; i < len; i++) { x += Number(points[i][0]); y += Number(points[i][1]); } return { x: x / len, y: y / len }; // return average position } Then I sort the points by finding angles of each point from the center like function findAngle(points) { const center = findCenter(points); // find angle points.forEach((point) => { point.angle = Math.atan2(point[1] - center.y, point[0] - center.x); }); } //arrVertexes is the array of points arrVertexes.sort(function (a, b) { return a.angle >= b.angle ? 1 : -1; }); But the problem I am facing is if I drag any point more towards opposite side and add a new point on the edges afterward and drag the newly added point the sorting of co-ordinates is not ordered exactly because of which there is a flickering while dragging. Here is a pictorial view of the problem I am facing for quick understanding. Initially my svg looks like After this I add a point and dragged like Then I added one more point like once I drag the added point towards down, it redraws the polygon something like (is not it weird ?) Actually It should be like NOTE: I really don't know what logic should I apply to get the desire functionality. Seeking help from the community leads. Demo App So I am looking for a solution that won't give me weird redrawing of the lines. Only the connected lines to the dragged point should be dragged. EDIT I came up with MUCH BETTER solution. The only problem with this approach is, When I try to add a new point on left-vertical line and If I try to move it, that newly added point moves to top-horizontal line Updated-Demo
I've fixed this bug with left line. Take a look: codepen. I changed getClosestPointOnLines function (actually refactored a little): as I understood, the result here is to get i - the index for the new point in array, so I moved the algorithm to new function getI I changed getI to use not only n (current index), but just 2 any indexes: n1 and n2: const getI = (n1, n2) => { So all your aXys[n] is now a1 and aXys[n - 1] is now a2. the result of getI is return i; - this is what we want from this function I added new function-helper updateI. It calls getI and check if there any positive result. const updateI = (n1, n2) => { const newI = getI(n1, n2); if (newI !== undefined) { i = newI; return true; } }; So your loop over points is now: for (let n = 1; n < aXys.length; n++) { updateI(n, n - 1); } But we need to check "left" line separately (because it connects begin and end of the array): if (updateI(aXys.length - 1, 0)) i = aXys.length; Sorry, but I disabled part of your code. I did not check where do you use it: if (i < aXys.length) { let dx = aXys[i - 1][0] - aXys[i][0]; let dy = aXys[i - 1][1] - aXys[i][1]; x = aXys[i - 1][0] - dx * fTo; y = aXys[i - 1][1] - dy * fTo; } So the final version of getClosestPointOnLines now looks like this: function getClosestPointOnLines(pXy, aXys) { var minDist; var fTo; var fFrom; var x; var y; var i; var dist; if (aXys.length > 1) { const getI = (n1, n2) => { let i; const a1 = aXys[n1]; const a2 = aXys[n2]; if (a1[0] != a2[0]) { let a = (a1[1] - a2[1]) / (a1[0] - a2[0]); let b = a1[1] - a * a1[0]; dist = Math.abs(a * pXy[0] + b - pXy[1]) / Math.sqrt(a * a + 1); } else dist = Math.abs(pXy[0] - a1[0]); // length^2 of line segment let rl2 = Math.pow(a1[1] - a2[1], 2) + Math.pow(a1[0] - a2[0], 2); // distance^2 of pt to end line segment let ln2 = Math.pow(a1[1] - pXy[1], 2) + Math.pow(a1[0] - pXy[0], 2); // distance^2 of pt to begin line segment let lnm12 = Math.pow(a2[1] - pXy[1], 2) + Math.pow(a2[0] - pXy[0], 2); // minimum distance^2 of pt to infinite line let dist2 = Math.pow(dist, 2); // calculated length^2 of line segment let calcrl2 = ln2 - dist2 + lnm12 - dist2; // redefine minimum distance to line segment (not infinite line) if necessary if (calcrl2 > rl2) dist = Math.sqrt(Math.min(ln2, lnm12)); if (minDist == null || minDist > dist) { if (calcrl2 > rl2) { if (lnm12 < ln2) { fTo = 0; //nearer to previous point fFrom = 1; } else { fFrom = 0; //nearer to current point fTo = 1; } } else { // perpendicular from point intersects line segment fTo = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2); fFrom = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2); } minDist = dist; i = n1; } return i; }; const updateI = (n1, n2) => { const newI = getI(n1, n2); if (newI !== undefined) { i = newI; return true; } }; for (let n = 1; n < aXys.length; n++) { updateI(n, n - 1); } if (updateI(aXys.length - 1, 0)) i = aXys.length; if (i < aXys.length) { let dx = aXys[i - 1][0] - aXys[i][0]; let dy = aXys[i - 1][1] - aXys[i][1]; x = aXys[i - 1][0] - dx * fTo; y = aXys[i - 1][1] - dy * fTo; } } console.log(aXys[i - 1]); return { x: x, y: y, i: i, fTo: fTo, fFrom: fFrom }; } Working example on codepen.
You should not allow any point to be added that is not close to a line. When the user clicks, use the distance from a point to a line algorithm to check each line to see if the click is within an acceptable distance of the line. Perhaps a few pixels. If more than one line is within an acceptable distance, perhaps choose the one that is closest. You now know where in the array to insert the new point. It will be between the first and second points of the line that just matched. If you do that, the shape drawing should just work.
Biggest distance between different points based on x-values and y-values?
I got a bunch of nodes which are stored in an array arr. Each node has a x and y value which repesents the position on the screen. Now, i created the middle element of arr and save it in middle. Now, my goal is, to find out the distance between middle and all other nodes and also find out the one with the maximum distance. For the distance I use the Pythagorean theorem a^2 + b^2 = c^2, that means sqrt(a^2 + b^2) = c or in my case sqrt(x^2 + y^2) = distance between 2 nodes. For example to create the distance between (10,10) and (20,30) I create the difference of the x-scale and the y-scale, that means x = 20-10 = 10 and y = 30-10 = 20. The result is, that the distance between those nodes is sqrt( 10^2 + 20^2) = 22,3. In my code, I check with the if-loop, which x-value and y-value is bigger to avoid negative values. But something I made is wrong. Maybe someone can help? var middle = arr[Math.floor(arr.length / 2)]; var arrayForDistance = []; var distance = []; for(i = 0; i != arr[middle] & i< arr.length; i++ ) { if(arr[i].x > arr[middle].x) { var newX = arr[i].x - arr[middle].x; var newY = arr[i].y - arr[middle].y; } else if ( arr[i].x < arr[middle].x) { var newX = arr[middle].x - arr[i].x; var newY = arr[middle].y - arr[i].y; }} distance = sqrt( newX^2 + newY^2) arrayForDistance.push(distance[i]); } var maxDistance = Math.max.apply(null, arrayForDistance)
First of all you dont need to worry about negatives since you are squareing them, they will cancel out. secondly your for loop is wrong it should be var middle = arr[Math.floor(arr.length / 2)]; var arrayForDistance = []; var distance ; for(i = 0; i< arr.length; i++ ) { if (i != Math.floor(arr.length / 2)){ var newX = arr[i].x - arr[middle].x; var newY = arr[i].y - arr[middle].y; distance = sqrt( newX^2 + newY^2) arrayForDistance.push(distance); } } var maxDistance = Math.max.apply(null, arrayForDistance)
Collision detection: Separating Axis Theorem - Circle versus Polygon
I've been trying to implement collision detection between circles and polygons based on Randy Gaul's C++ Impulse Engine, following the code pretty closely, but the algorithm never returns true. Here's the JSFiddle. (the bodies are rendered using the HTML5 Canvas API for convenience) A snippet of the code (just collision detection): const circPoly = (a, b) => { let data = {}, center = a.pos; data.contacts = []; center = b.mat.clone().trans().mult(center.clone().sub(b.pos)); let sep = -Number.MAX_VALUE, faceNorm = 0; for (let i = 0; i < b.verts2.length; ++i) { let sep2 = b.norms[i].dot(center.clone().sub(b.verts2[i])); if (sep2 > a.radius) return data; if (sep2 > sep) { sep = sep2; faceNorm = i; } } let v1 = b.verts2[faceNorm], v2 = b.verts2[faceNorm + 1 < b.verts2.length ? faceNorm + 1 : 0]; if (sep < 0.0001) { data.depth = a.radius; data.norm = b.mat.clone().mult(b.norms[faceNorm]).neg(); data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius)); return data; } let dot1 = center.clone().sub(v1).dot(v2.clone().sub(v1)), dot2 = center.clone().sub(v2).dot(v1.clone().sub(v2)); data.depth = a.radius - sep; if (dot1 <= 0) { if (center.dist2(v1) > a.radius * a.radius) return data; let norm = v1.clone().sub(center); norm = b.mat.clone().mult(norm); norm.norm(); data.norm = norm; v1 = b.mat.clone().mult(v1.clone().add(b.pos)); data.contacts[0] = v1; } else if (dot2 <= 0) { if (center.dist2(v2) > a.radius * a.radius) return data; let norm = v2.clone().sub(center); norm = b.mat.clone().mult(norm); norm.norm(); data.norm = norm; v2 = b.mat.clone().mult(v2.clone().add(b.pos)); data.contacts[0] = v2; } else { let norm = b.norms[faceNorm]; if (center.clone().sub(v1).dot(norm) > a.radius) return data; norm = b.mat.clone().mult(norm); data.norm = norm.clone().neg(); data.contacts[0] = data.norm.clone().vmult(a.pos.clone().sadd(a.radius)); } return data; }; Note that b.verts2 refers to the polygon's vertices in real world coordinates. I know for a fact that there are no problems with the Vector class but as I don't exactly have very much experience with transformation matrices, that class could be the root of these errors, although the code for it is pretty much entirely derived from the Impulse Engine as well, so it should work. As mentioned before, the algorithm always returns false, even when a collision really has occurred. What am I doing wrong here? I tried taking out the early returns, but that just returns weird results like contact points with negative coordinates which obviously is not quite correct. EDIT: Modified my vector class's perpendicular function to work the same way as the Impulse Engine's (both ways are right, but I think one is clockwise and the other one counterclockwise -- I also modified my vertices to reflect the counterclockwise-ness). Unfortunately, it still fails the test. https://jsfiddle.net/khanfused/tv359kgL/4/
Well the are many problems and I really dont understand what you are trying to do as it seem overly complex. Eg why does matrix have trans??? and why are you using the Y up screen as the coordinate system for the transform??? (rhetorical) In the first loop. The first is that you are testing the distance of the normal vectors of each vert, should be testing the vert position. Also you are finding the distance using the vec.dot function that returns the square of the distance. But you test for the radius, you should be testing for if(sep2 < radius * radius) And you have the comparison the wrong way around you should be testing if less than radius squared (not greater than) Then when you do detect a vert within the radius you return the data object but forget to put the vert that was found inside the circle on the data.contacts array. I am not sure what the intention of keeping the index of the most distant vect is but then the rest of the function make zero sense to me???? :( and I have tried to understand it. All you need to do is A check if any verts on the poly are closer than radius, if so then you have a intercept (or is completely inside) Then you need to check the distance of each line segment Can be done for each line segment with the following if you dont need the intercepts (or below that if you need intercepts) only use one or the other. // circle is a point {x:?,y:?} // radius = is the you know what // p1,p2 are the start and end points of a line checkLineCircle = function(circle,radius,p1,p2){ var v1 = {}; var v2 = {}; var v3 = {}; var u; // get dist to end of line v2.x = circle.x - p1.x; v2.y = circle.y - p1.y; // check if end points are inside the circle if( Math.min( Math.hypot(p2.x - circle.x, p2.y - circle.y), Math.hypot(v2.x, v2.y) ) <= radius){ return true; } // get the line as a vector v1.x = p2.x - p1.x; v1.y = p2.y - p1.y; // get the unit distance of the closest point on the line u = (v2.x * v1.x + v2.y * v1.y)/(v1.y * v1.y + v1.x * v1.x); // is this on the line segment if(u >= 0 && u <= 1){ v3.x = v1.x * u; // get the point on the line segment v3.y = v1.y * u; // get the distance to that point and return true or false depending on the // it being inside the circle return (Math.hypot(v3.y - v2.y, v3.x - v2.x) <= radius); } return false; // no intercept } Do that for each line.To save time transform the circle center to the polygon local, rather than transform each point on the poly. If you need the points of intercept then use the following function // p1,p2 are the start and end points of a line // returns an array empty if no points found or one or two points depending on the number of intercepts found // If two points found the first point in the array is the point closest to the line start (p1) function circleLineIntercept(circle,radius,p1,p2){ var v1 = {}; var v2 = {}; var ret = []; var u1,u2,b,c,d; // line as vector v1.x = p2.x - p1.x; v1.y = p2.y - p1.y; // vector to circle center v2.x = p1.x - circle.x; v2.y = p1.y - circle.y; // dot of line and circle b = (v1.x * v2.x + v1.y * v2.y) * -2; // length of line squared * 2 c = 2 * (v1.x * v1.x + v1.y * v1.y); // some math to solve the two triangles made by the intercept points, the circle center and the perpendicular line to the line. d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - radius * radius)); // will give a NaN if no solution if(isNaN(d)){ // no intercept return ret; } // get the unit distance of each intercept to the line u1 = (b - d) / c; u2 = (b + d) / c; // check the intercept is on the line segment if(u1 <= 1 && u1 >= 0){ ret.push({x:line.p1.x + v1.x * u1, y : line.p1.y + v1.y * u1 }); } // check the intercept is on the line segment if(u2 <= 1 && u2 >= 0){ ret.push({x:line.p1.x + v1.x * u2, y : line.p1.y + v1.y * u2}); } return ret; } I will leave it up to you to do the polygon iteration.
Why is Firefox 30 times slower than Chrome, when calculating Perlin noise?
I have written a map generator in javascript, using classical perlin noise scripts I have found in various places, to get the functionality I want. I have been working in chrome, and have not experienced any problems with the map. However, when I tested it in firefox, it was incredibly slow - almost hanging my system. It fared better in the nightly build, but still 30 times slower than Chrome. You can find a test page of it here: http://jsfiddle.net/7Gq3s/ Here is the html code: <!DOCTYPE html> <html> <head> <title>PerlinMapTest</title> </head> <body> <canvas id="map" width="100" height="100" style="border: 1px solid red">My Canvas</canvas> <script src="//code.jquery.com/jquery-2.0.0.min.js"></script> <script> $(document).ready(function(){ //Log time in two ways var startTime = new Date().getTime(); console.time("Map generated in: "); var canvas = $("#map")[0]; var ctx = canvas.getContext("2d"); var id = ctx.createImageData(canvas.width, canvas.height); var noiseMap = new PerlinNoise(500); var startx = 0; var starty = 0; var value = 0; for(var i = startx; i < canvas.width; i++){ for(var j = starty; j < canvas.height; j++){ value = noiseMap.noise(i,j, 0, 42); value = linear(value,-1,1,0,255); setPixel(id, i, j, 0,0,0,value); } } ctx.putImageData(id,0,0); var endTime = new Date().getTime(); console.timeEnd("Map generated in: "); alert("Map generated in: " + (endTime - startTime) + "milliseconds"); }); function setPixel(imageData, x, y, r, g, b, a) { index = (x + y * imageData.width) * 4; imageData.data[index+0] = r; imageData.data[index+1] = g; imageData.data[index+2] = b; imageData.data[index+3] = a; } //This is a port of Ken Perlin's "Improved Noise" //http://mrl.nyu.edu/~perlin/noise/ //Originally from http://therandomuniverse.blogspot.com/2007/01/perlin-noise-your-new-best-friend.html //but the site appears to be down, so here is a mirror of it //Converted from php to javascript by Christian Moe //Patched the errors with code from here: http://asserttrue.blogspot.fi/2011/12/perlin-noise-in-javascript_31.html var PerlinNoise = function(seed) { this._default_size = 64; this.seed = seed; //Initialize the permutation array. this.p = new Array(512); this.permutation = [ 151,160,137,91,90,15, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 ]; for (var i=0; i < 256 ; i++) { this.p[256+i] = this.p[i] = this.permutation[i]; } }; PerlinNoise.prototype.noise = function(x,y,z,size) { if (size == undefined) { size = this._default_size; } //Set the initial value and initial size var value = 0.0; var initialSize = size; //Add finer and finer hues of smoothed noise together while(size >= 1) { value += this.smoothNoise(x / size, y / size, z / size) * size; size /= 2.0; } //Return the result over the initial size return value / initialSize; }; //This function determines what cube the point passed resides in //and determines its value. PerlinNoise.prototype.smoothNoise = function(x, y, z){ //Offset each coordinate by the seed value x += this.seed; y += this.seed; z += this.seed; var orig_x = x; var orig_y = y; var orig_z = z; var X = Math.floor(x) & 255, // FIND UNIT CUBE THAT Y = Math.floor(y) & 255, // CONTAINS POINT. Z = Math.floor(z) & 255; x -= Math.floor(x); // FIND RELATIVE X,Y,Z y -= Math.floor(y); // OF POINT IN CUBE. z -= Math.floor(z); var u = this.fade(x), // COMPUTE FADE CURVES v = this.fade(y), // FOR EACH OF X,Y,Z. w = this.fade(z); var A = this.p[X ]+Y, AA = this.p[A]+Z, AB = this.p[A+1]+Z, // HASH COORDINATES OF B = this.p[X+1]+Y, BA = this.p[B]+Z, BB = this.p[B+1]+Z; // THE 8 CUBE CORNERS, return this.lerp(w, this.lerp(v, this.lerp(u, this.grad(this.p[AA ], x , y , z ), // AND ADD this.grad(this.p[BA ], x-1, y , z )), // BLENDED this.lerp(u, this.grad(this.p[AB ], x , y-1, z ), // RESULTS this.grad(this.p[BB ], x-1, y-1, z ))),// FROM 8 this.lerp(v, this.lerp(u, this.grad(this.p[AA+1], x , y , z-1 ), // CORNERS this.grad(this.p[BA+1], x-1, y , z-1 )), // OF CUBE this.lerp(u, this.grad(this.p[AB+1], x , y-1, z-1 ), this.grad(this.p[BB+1], x-1, y-1, z-1 )))); }; PerlinNoise.prototype.fade = function(t) { return t * t * t * ( ( t * ( (t * 6) - 15) ) + 10); }; PerlinNoise.prototype.lerp = function(t, a, b) { //Make a weighted interpolaton between points return a + t * (b - a); }; PerlinNoise.prototype.grad = function(hash, x, y, z) { h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE u = h<8 ? x : y; // INTO 12 GRADIENT DIRECTIONS. v = h<4 ? y : (h==12||h==14 ? x : z); return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); }; PerlinNoise.prototype.scale = function(n) { return (1 + n)/2; }; function linear(int, s1, s2, t1, t2) { t = [t1, t2]; s = [s1, s2]; rangeS = s1 - s2; rangeT = t1 - t2; if((s1 < s2 && t1 > t2) || (s1>s2 && t1<t2)) { interpolated = ((int - s1) / rangeS*rangeT) + t1; } else { interpolated = ((int - s1) / rangeS)*rangeT + t1; } if(interpolated > Math.max.apply(Math, t)) { interpolated = Math.max.apply(Math, t); } if(interpolated < Math.min.apply(Math, t)) { interpolated = Math.min.apply(Math, t); } return interpolated; } </script> </body> </html> I get 33 ms on Chrome, and 1051ms on Firefox 24 Nightly The results are inconsistent though. Sometimes the Nightly results is as fast as chrome... Do you know why there is so much variation in this particular instance? I don't know enough about the theory of perlin noise to try optimizing the code, so don't know what to do.
I have found the culprit. The slowdown occurs when I have Firebug enabled. That extension must weigh it down.