Edit: I just changed my Control Point to the Intersection. That's why it couldn't fit anymore.
I know it's a very presumptuous. But I am working on a Web-Application and I need to calculate the intersection with a quadratic Beziere-Curve and a Line.
Linear Bezier-Curve: P=s(W-V)+V
Quadratic Bezier-Curve: P=t²(A-2B+C)+t(-2A+2B)+A
Because W, V, A, B, and C are points, I could make two equation. I rearranged the first equation to seperate s to solve the equation.
I'm pretty sure i did it correctly, but my intersection was not on the line. So i was wondering and made my own quadratic-Beziercurve by the correct formular and my intersection hits this curve. Now I am wondering what did I wrong?
That is my function:
intersectsWithLineAtT(lineStartPoint, lineEndPoint)
{
let result = []
let A = this.startPoint, B = this.controlPoint, C = this.endPoint, V = lineStartPoint, W = lineEndPoint
if (!Common.isLineIntersectingLine(A, B, V, W)
&& !Common.isLineIntersectingLine(B, C, V, W)
&& !Common.isLineIntersectingLine(A, C, V, W))
return null
let alpha = Point.add(Point.subtract(A, Point.multiply(B, 2)), C)
let beta = Point.add(Point.multiply(A, -2), Point.multiply(B, 2))
let gamma = A
let delta = V
let epsilon = Point.subtract(W, V)
let a = alpha.x * (epsilon.y / epsilon.x) - alpha.y
let b = beta.x * (epsilon.y / epsilon.x) - beta.y
let c = (gamma.x - delta.x) * (epsilon.y / epsilon.x) - gamma.y + delta.y
let underSquareRoot = b * b - 4 * a * c
if (Common.compareFloats(0, underSquareRoot))
result.push(-b / 2 * a)
else if (underSquareRoot > 0)
{
result.push((-b + Math.sqrt(underSquareRoot)) / (2 * a))
result.push((-b - Math.sqrt(underSquareRoot)) / (2 * a))
}
result = result.filter((t) =>
{
return (t >= 0 && t <= 1)
})
return result.length > 0 ? result : null
}
I hope someone can help me.
Lena
The curve/line intersection problem is the same as the root finding problem, after we rotate all coordinates such that the line ends up on top of the x-axis:
which involves a quick trick involving atan2:
const d = W.minus(V);
const angle = -atan2(d.y, d.x);
const rotated = [A,B,C].map(p => p.minus(V).rotate(angle));
Assuming you're working with point classes that understand vector operations. If not, easy enough to do with standard {x, y} objects:
const rotated = [A,B,C].map(p => {
p.x -= V.x;
p.y -= V.y;
return {
x: p.x * cos(a) - p.y * sin(a),
y: p.x * sin(a) + p.y * cos(a)
};
});
Then all we need to find out is which t values yield y=0, which is (as you also used) just applying the quadratic formula. And we don't need to bother with collapsing dimensions: we've reduced the problem to finding solutions in just the y dimension, so taking
const a = rotated[0].y;
const b = rotated[1].y;
const c = rotated[2].y;
and combining that with the fact that we know that Py = t²(a-b+c)+t(-2a+2b)+a we just work out that t = -b/2a +/- sqrt(b² - 4ac))/2a with the usual checks for negative, zero, and positive discriminant, as well as checking for division by zero.
This gives us with zero or more t value(s) for the y=0 intercept in our rotated case, and for the intersection between our curve and line in the unrotated case. No additional calculations required. Aside from "evaluating B(t) to get the actual (x,y) cooordinates", of course.
Related
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.
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.
Trying to write a simple web app to solve the following common calculus problem in JavaScript.
Suppose you wanted to make an open-topped box out of a flat piece of cardboard that is L long by W wide by cutting the same size
square (h × h) out of each corner and then folding the flaps to form the box,
as illustrated below:
You want to find out how big to make the cut-out squares in order to maximize the volume of the box.
Ideally I want to avoid using any calculus library to solve this.
My initial naive solution:
// V = l * w * h
function getBoxVolume(l, w, h) {
return (l - 2*h)*(w - 2*h)*h;
}
function findMaxVol(l, w) {
const STEP_SIZE = 0.0001;
let ideal_h = 0;
let max_vol = 0;
for (h = 0; h <= Math.min(l, w) / 2; h = h + STEP_SIZE) {
const newVol = getBoxVolume(l, w, h);
if (max_vol <= newVol) {
ideal_h = h;
max_vol = newVol;
} else {
break;
}
}
return {
ideal_h,
max_vol
}
}
const WIDTH_1 = 20;
const WIDTH_2 = 30;
console.log(findMaxVol(WIDTH_1, WIDTH_2))
// {
// ideal_h: 3.9237000000038558,
// max_vol: 1056.3058953402121
// }
The problem with this naive solution is that it only gives an estimate because you have to provide STEP_SIZE and it heavily limits the size of the problem this can solve.
You have an objective function: getBoxVolume(). Your goal is to maximize the value of this function.
Currently, you're maximizing it using something equivalent to sampling: you're checking every STEP_SIZE, to see whether you get a better result. You've identified the main problem: there's no guarantee the edge of the STEP_SIZE interval falls anywhere near the max value.
Observe something about your objective function: it's convex. I.e., it starts by going up (when h = 0, volume is zero, then it grows as h does), it reaches a maximum, then it goes down, eventually reaching zero (when h = min(l,w)/2).
This means that there's guaranteed to be one maximum value, and you just need to find it. This makes this problem a great case for binary search, because given the nature of the function, you can sample two points on the function and know which direction the maximum lies relative to those two points. You can use this, with three points at a time (left, right, middle), to figure out whether the max is between left and middle, or middle and right. Once these values get close enough together (they're within some fixed amount e of each other), you can return the value of the function there. You can even prove that the value you return is within some value e' of the maximum possible value.
Here's pseudocode:
max(double lowerEnd, upperEnd) {
double midPoint = (upperEnd + lowerEnd) / 2
double midValue = getBoxVolume(l, w, midpoint)
double slope = (getBoxVolume(l, w, midpoint + epsilon) - midValue) / epsilon
if (Math.abs(slope) < epsilon2) { // or, if you choose, if (upperEnd - lowerEnd < epsilon3)
return midpoint
}
if (slope < 0) { // we're on the downslope
return max(lowerEnd, midPoint)
}
else { // we're on the up-slope
return max(midpoint, upperEnd)
}
}
After realising that the derivative of the volume function is a second degree polynomial you can apply a quadratic formula to solve for x.
Using calculus, the vertex point, being a maximum or minimum of the function, can be obtained by finding the roots of the derivative
// V = l * w * h
function getBoxVolume(l, w, h) {
return (l - 2*h)*(w - 2*h)*h;
}
// ax^2 + bx + c = 0
function solveQuad(a, b, c) {
var x1 = (-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
var x2 = (-1 * b - Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
return { x1, x2 };
}
function findMaxVol(l, w) {
// V'(h) = 12h^2-4(l+w)h+l*w - second degree polynomial
// solve to get the critical numbers
const result = solveQuad(12, -4*(l + w), l*w)
const vol1 = getBoxVolume(l, w, result.x1);
const vol2 = getBoxVolume(l, w, result.x2);
let ideal_h = 0;
let max_vol = 0;
// check for max
if (vol1 > vol2) {
ideal_h = result.x1;
max_vol = vol1;
} else {
ideal_h = result.x2;
max_vol = vol2;
}
return {
ideal_h,
max_vol
}
}
const WIDTH_1 = 20;
const WIDTH_2 = 30;
console.log(findMaxVol(WIDTH_1, WIDTH_2))
// {
// ideal_h: 3.9237478148923493,
// max_vol: 1056.30589546119
// }
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.
Based on an answer to a previous question of mine about angular velocity, I've modified the given demo and implemented the seperating axis theorem (collision detection) along with rudimentary linear impulse resolution. (here's the JSFiddle). However, there's a tiny problem with the response.
If the bodies manage to penetrate each other (which does occasionally occur), the penetrating one temporarily disappears and then reappears again when they are no longer penetrating. But why?
let aVel = [a.dx, a.dy];
let bVel = [b.dx, b.dy];
const invA = a.static ? 0 : 1 / a.mass;
const invB = b.static ? 0 : 1 / b.mass;
const relativeVel = Sub(bVel, aVel);
const velAlongNorm = DotProduct(relativeVel, data.unit);
if (velAlongNorm > 0)
return;
const cor = a.cor * b.cor;
let _j = -(1 + cor) * velAlongNorm;
_j /= invA + invB;
const impulse = ScalarMultiply(data.unit, _j);
aVel = Sub(aVel, ScalarMultiply(impulse, invA));
bVel = Add(bVel, ScalarMultiply(impulse, invB));
a.dx = aVel[0];
a.dy = aVel[1];
b.dx = bVel[0];
b.dy = bVel[1];
const percent = 0.2;
const slop = 0.01;
const correction = Math.max(data.overlap - slop, 0) / (invA + invB) * percent;
a.x -= invA * correction;
a.y -= invA * correction;
b.x += invB * correction;
b.y += invB * correction;
Note that dx and dy refer to the x and y components of the bodies' velocities, respectively, and COR refers to the coefficient of restitution. (bounciness) invA and invB are the inverse masses.
How do I fix the problem of the penetrating body disappearing?
Ah, I figured it out. I just forgot to translate the bodies by the minimum translation vector (MTV).
It's calculated by multiplying the collision normal by the overlap. (AKA penetration, depth, etc.)