Query set of points in AREA within distance from line segment - javascript

I have line segments and points stored in a db. How would I query the db in order to retrieve the all the points that are within a certain distance of multiple line segments.
The purpose is that when the user clicks on a path (road), all the objects that are within a distance from the path should be highlighted.
Thank you.
Update:
Example...
I have a path that goes from (0,0) to (0, 10). The program should find and highlight all objects within x-distance of this path.
Suppose that the x-distance is "2"... then, the program should highlight all objects within the rectangle (0,2)(10,-2). Basically, this is the same as finding all objects with a proximity to the line (not just a single point).
It is easy when the line is horizontal... But I don't know how to solve for all cases, including then the line may be a slope.
Update: The points are stored in a large database, so I cannot check each and every one of them for the proximity. I'm trying to find a way to retrieve only the ones that are close enough without overlapping requests too much... Once they are retrieved, I can refine the search by using the method described in "distance between a point and a line segment". (I think!)
Thanks!

This will give you the distance from point p to line segment v,w. (based on this question: Shortest distance between a point and a line segment). You'll have to run through all your points and calculate the distance to all your line segments to find the ones close enough to the route.
If it's too slow, you'll have to make some kind of simplification that doesn't need square roots.
function distanceToLineSegment(p, v, w)
{
var len2 = dist2(v, w);
if (len2 == 0) return Math.sqrt(dist2(p, v));
var s = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
if (s < 0) return Math.sqrt(dist2(p, v));
if (s > 1) return Math.sqrt(dist2(p, w));
var i = {x: v.x + s * (w.x - v.x), y: v.y + s * (w.y - v.y)};
return Math.sqrt(dist2(p, i));
function dist2(p, q) {
return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
}
}
alert(distanceToLineSegment({x:2, y:3}, {x:-1, y:4}, {x:3, y:8}));
This is a somewhat optimized implementation that checks a list of points against a route.
The points to check are stored as an array far[] of points with x and y values and an id string. There is a second, initially empty array close[] into which the points are moved if they are found to be close to the route, so that points aren't checked twice. These two arrays are stored in an object points, so that they can be passed by reference between the functions, instead of constantly being copied. I've also removed the square root functions for efficiency.
Further optimization is probably possible by changing the distance calculation to a coarser approximation (maybe using rectangles) instead of a mathematically correct one.
function isCloseToRoute(points, route, distance) {
var distance2 = Math.pow(distance, 2);
for (var i = 0; i < route.length - 1; i++) {
isCloseToLineSegment(points, route[i], route[i + 1], distance2);
}
function isCloseToLineSegment(points, v, w, distance2) {
for (var i = points.far.length - 1; i >= 0; i--) {
if (distanceToLineSegment2(points.far[i], v, w) <= distance2) {
points.close.push(points.far.splice(i, 1)[0]);
}
}
}
function distanceToLineSegment2(p, v, w) {
var len2 = dist2(v, w);
if (len2 == 0) return dist2(p, v);
var q = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
if (q < 0) return dist2(p, v);
if (q > 1) return dist2(p, w);
var i = {x: v.x + q * (w.x - v.x), y: v.y + q * (w.y - v.y)};
return dist2(p, i);
function dist2(p, q) {
return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
}
}
}
var points = {close: [], far: [{x: 1, y: 0, id: "A"},
{x: 2, y: 1, id: "B"},
{x:-1, y: 8, id: "C"},
{x:-3, y: 4, id: "D"}]};
var route = [{x: 0, y: 0}, {x: 1, y: 2}, {x:-1, y: 4}, {x: 2, y: 8}];
isCloseToRoute(points, route, 2);
alert(points.close.length + " points found near route");
for (i in points.close) console.log(points.close[i].id);
If you divide your map into a grid, you can use isCloseToRoute() to check which grid cells are near the route. It will return a list of grid cells which have a key like "6,4"; if you give each point in your database a key that indicates in which grid cells it's located, you can look them up without having to do any math on the coordinates.
You make an input object just like when checking a list of points, fill the far[] array with the center points of the grid cells, and run isCloseToRoute() on it with a distance of (distance + gridSize*sqrt(2)/2).
In the example, the map is a 1000 x 1000 square, divided into 64 grid cells each sized 125 x 125.
function isCloseToRoute(points, route, distance) {
var distance2 = Math.pow(distance, 2);
for (var i = 0; i < route.length - 1; i++) {
isCloseToLineSegment(points, route[i], route[i + 1], distance2);
}
function isCloseToLineSegment(points, v, w, distance2) {
for (var i = points.far.length - 1; i >= 0; i--) {
if (distanceToLineSegment2(points.far[i], v, w) <= distance2) {
points.close.push(points.far.splice(i, 1)[0]);
}
}
}
function distanceToLineSegment2(p, v, w) {
var len2 = dist2(v, w);
if (len2 == 0) return dist2(p, v);
var q = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / len2;
if (q < 0) return dist2(p, v);
if (q > 1) return dist2(p, w);
var i = {x: v.x + q * (w.x - v.x), y: v.y + q * (w.y - v.y)};
return dist2(p, i);
function dist2(p, q) {
return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
}
}
}
var route = [{x: 210, y: 190}, {x: 820, y: 480}, {x:530, y: 470}, {x: 440, y: 760}];
var distance = 100;
var mapSize = 1000;
var gridSize = 125;
var gridCells = Math.floor(mapSize / gridSize);
var grid = {close: [], far: []};
for (x = 0; x < gridCells; x++) {
for (y = 0; y < gridCells; y++) {
grid.far[y * (gridCells) + x] = {x: (x + 0.5) * gridSize,
y: (y + 0.5) * gridSize,
key: x + "," + y};
}
}
isCloseToRoute(grid, route, distance + 0.707107 * gridSize);
alert(grid.close.length + " grid cells near route");
for (i in grid.close) console.log(grid.close[i].key);
I've optimized isCloseToRoute() a bit more. The example runs a test with 1000 random points checked against a 1000-segment random route.
function isCloseToRoute(points, route, distance) {
var distance2 = Math.pow(distance, 2);
for (var i = 0; i < route.length - 1; i++) {
isCloseToLineSegment(route[i], route[i + 1]);
}
function isCloseToLineSegment(v, w) {
var len2 = distanceToPoint2(v, w);
var lenX = w.x - v.x, lenY = w.y - v.y;
for (var i = points.far.length - 1; i >= 0; i--) {
if (distanceToLineSegment2(points.far[i]) <= distance2) {
points.near.push(points.far.splice(i, 1)[0]);
}
}
function distanceToLineSegment2(p) {
if (len2 == 0) return distanceToPoint2(p, v); // enable if zero-length segments are possible
var q = ((p.x - v.x) * lenX + (p.y - v.y) * lenY) / len2;
if (q < 0) return distanceToPoint2(p, v);
if (q > 1) return distanceToPoint2(p, w);
var r = {x: v.x + q * lenX, y: v.y + q * lenY};
return distanceToPoint2(p, r);
}
function distanceToPoint2(p, q) {
return Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2);
}
}
}
// generate random test data
var points = {near: [], far: [{x: 500, y: 500}]};
var route = [{x: 200, y: 200}];
var distance = 100;
for (var i = 1; i < 1000; i++) {
points.far[i] = {x: Math.random() * 1000, y: Math.random() * 1000};
route[i] = {x: route[i - 1].x + 3 * Math.random() - 1, y: route[i - 1].y + 3 * Math.random() - 1};
}
var t = new Date().getTime();
isCloseToRoute(points, route, distance);
t = new Date().getTime() - t;
alert(points.near.length + " points found near route.\n(1000 points checked against 1000 segments in " + t + " ms)");
for (i in points.near) console.log(points.near[i].x + "," + points.near[i].y);

Related

How to plot an ellipse on canvas from 2 points on the ellipse, where slope of major axis (rx), and minor axis (ry) length are unknown

This may be more of a mathematics problem, but maybe there is a simple javascript solution that I am missing.
I want to plot an ellipse on html canvas from user input of a center point, radius of the major (longest) axis, and 2 points will fall on the ellipse.
This should potentially create 2 possible ellipse paths, both of which will center around the center point, and cross through the 2 points.
So for example, if the center = [2, 1] major axis radius a = 10, point 1 u = [4, 2] and point 2 v = [5, 6], what is the minor axis radius b and angle of rotation theta?
So far I have tried to implement an equation that I found from https://math.stackexchange.com/questions/3210414/find-the-angle-of-rotation-and-minor-axis-length-of-ellipse-from-major-axis-leng,
but it does not return valid values. My javascript code looks like this:
function getEllipseFrom2Points(center, u, v, a) {
function getSlope(plusOrMinus) {
return Math.sqrt(((uy * vx - ux * vy) ** 2) / (-ux * uy * (a * (v2x + v2y) - 1) + vx * vy * (a * (u2x + u2y) - 1) - plusOrMinus * (uy * vx - ux * vy) * q) / (u2x * (1 - a * v2y) + v2x * (a * u2y - 1)));
}
function getMinorAxis(plusOrMinus) {
return (u2x + u2y + v2x + v2y - a * (2 * u2x * v2x + 2 * u2y * v2y + 2 * ux * uy * vx * vy + u2y * v2x + u2x * v2y) + plusOrMinus * 2 * (ux * vx + uy * vy) * q);
}
var vx = v[0],
vy = v[1],
ux = u[0],
uy = u[1],
v2x = vx ** 2,
v2y = vy ** 2,
u2x = ux ** 2,
u2y = uy ** 2,
q = Math.sqrt((1 - a * (u2x + u2y)) * (1 - a * (v2x + v2y))),
ellipse1 = { rx: a, ry: getMinorAxis(1), origin: center, rotation: getSlope(1) },
ellipse2 = { rx: a, ry: getMinorAxis(-1), origin: center, rotation: getSlope(-1) };
}
Either the equation that I am following is wrong, or I have implemented it wrong
In case anyone is interested, here is my solution to the problem, which isn't really "the" solution. If anyone can solve this I would still be happy to know.
Since I can't solve for both slope of the major axis and length of the minor axis, I just take a guess at slope and then test how close it is, and then refine the result by trying in a smaller and smaller region. Since the final ellipse that gets drawn is actually an estimation constructed from bezier curves, I can get close enough in a reasonable amount of time.
function getEllipseFrom2Points (center, u, v, a) {
function getSemiMinorAxis([x, y], a, t) {
// equation for rotated ellipse
// b = a(ycos(t) - xsin(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t)) and
// b = a(xsin(t) - ycos(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t))
// where a^2 !== (xcos(t) + ysin(t))^2
// and aycos(t) !== axsin(t)
if (a ** 2 !== (x * Math.cos(t) + y * Math.sin(t)) ** 2 &&
a * y * Math.cos(t) !== a * x * Math.sin(t)) {
var b = [],
q = (Math.sqrt(a ** 2 - x ** 2 * (Math.cos(t)) ** 2 - 2 * x * y * Math.sin(t) * Math.cos(t) - y ** 2 * (Math.sin(t)) ** 2));
b[0] = (a * (y * Math.cos(t) - x * Math.sin(t))) / q;
b[1] = (a * (x * Math.sin(t) - y * Math.cos(t))) / q;
return b;
}
}
function getAngle_radians(point1, point2){
return Math.atan2(point2[1] - point1[1], point2[0] - point1[0]);
}
function getDistance(point1, point2) {
return Math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2);
}
function rotatePoint(point, center, radians) {
var x = (point[0] - center[0]) * Math.cos(radians) - (point[1] - center[1]) * Math.sin(radians) + center[0];
var y = (point[1] - center[1]) * Math.cos(radians) + (point[0] - center[0]) * Math.sin(radians) + center[1];
return [x, y];
}
function measure(ellipseRotation, pointOnEllipse, minorAxisLength) {
var d = getDistance(point, pointOnEllipse);
if (d < bestDistanceBetweenPointAndEllipse) {
bestDistanceBetweenPointAndEllipse = d;
bestEstimationOfB = minorAxisLength;
bestEstimationOfR = ellipseRotation;
}
}
function getBestEstimate(min, max) {
var testIncrement = (max - min) / 10;
for (let r = min; r < max; r = r + testIncrement) {
if (radPoint1 < r && radPoint2 < r || radPoint1 > r && radPoint2 > r) {//points both on same side of ellipse
semiMinorAxis = getSemiMinorAxis(v, a, r);
if (semiMinorAxis) {
for (let t = 0; t < circle; t = t + degree) {
ellipsePoint1 = [a * Math.cos(t), semiMinorAxis[0] * Math.sin(t)];
ellipsePoint2 = [a * Math.cos(t), semiMinorAxis[1] * Math.sin(t)];
point = rotatePoint(u, [0, 0], -r);
measure(r, ellipsePoint1, semiMinorAxis[0]);
measure(r, ellipsePoint2, semiMinorAxis[1]);
}
}
}
}
count++;
if (new Date().getTime() - startTime < 200 && count < 10) //refine estimate
getBestEstimate(bestEstimationOfR - testIncrement, bestEstimationOfR + testIncrement);
}
if (center instanceof Array &&
typeof center[0] === "number" &&
typeof center[1] === "number" &&
u instanceof Array &&
typeof u[0] === "number" &&
typeof u[1] === "number" &&
v instanceof Array &&
typeof v[0] === "number" &&
typeof v[1] === "number" &&
typeof a === "number") {
// translate points
u = [u[0] - center[0], u[1] - center[1]];
v = [v[0] - center[0], v[1] - center[1]];
var bestDistanceBetweenPointAndEllipse = a,
point,
semiMinorAxis,
ellipsePoint1,
ellipsePoint2,
bestEstimationOfB,
bestEstimationOfR,
radPoint1 = getAngle_radians([0, 0], v),
radPoint2 = getAngle_radians([0, 0], u),
circle = 2 * Math.PI,
degree = circle / 360,
startTime = new Date().getTime(),
count = 0;
getBestEstimate(0, circle);
var ellipseModel = MakerJs.$(new MakerJs.models.Ellipse(a, bestEstimationOfB))
.rotate(MakerJs.angle.toDegrees(bestEstimationOfR), [0, 0])
.move(center)
.originate([0, 0])
.$result;
return ellipseModel;
}

Isometric position multi dimensional array sorting where z property are defines how many pixels should it be lifted upwards

I have an array of isometric objects that has x,y,z properties. I'm trying to sort the array for rendering purposes (first index rendered first). I tried to sort the array using the code below but it's inaccurate.
this.objects.sort((a,b) => {
return ((a.x + a.y) / 2) - ((b.x + b.y) / 2)
});
this.objects.sort((a,b) => {
return a.z - b.z
})
and when I try this code
this.objects.sort((a,b) => {
if(a.z > b.z) return 1;
if(((a.x + a.y) / 2) > ((b.x + b.y) / 2)) return 1;
return -1
});
this happens:
Is there a way to sort the array so it renders properly?
for(var obj of this.objects) {
if(!this.sprites[obj.sprite] || !this.sprites[obj.sprite].loaded) {
continue;
}
var centralPoint = obj.centralPoint();
this.ctx.drawImage(this.sprites[obj.sprite].image,centralPoint.x - this.sprites[obj.sprite].offset[0], centralPoint.y - this.sprites[obj.sprite].offset[1]);
}
My Isometric Object:
function IsoObject(main,sprite,x,y,z = 0,height = 0, scale = 1,onClick = function() {}) {
this.main = main;
if(!main.sprites[sprite]) main.addSprite(sprite,sprite);
this.sprite = sprite;
this.x = x;
this.y = y;
this.z = z;
this.scale = scale;
this.height = height;
this.onclick = onClick;
return this;
}
IsoObject.prototype.centralPoint = function() {
var x = this.x * this.main.increment.x - (this.y * this.main.increment.x);
var y = this.y * this.main.increment.y - this.z + (this.x * this.main.increment.y);
return {
x: x,
y: y
}
}
this.main.increment:
this.increment = {
x: this.tileSize * Math.sin(this.angle * (Math.PI / 180)),
y: this.tileSize * Math.cos(this.angle * (Math.PI / 180))
}
Object positions:
0: "x:4.999000000000007,y:4.136499999999993,z:0"
1: "x:1,y:3,z:0"
2: "x:4,y:3,z:-1"
3: "x:2,y:4,z:-1"
4: "x:1,y:3,z:39"
5: "x:1,y:3,z:40"
if you see, z property is not the same as x and y, z property defines how many pixels should be the image translated upwards.
EDIT
This sorting kinda works but still inaccurate
this.objects.sort((a,b) => {
var ac = a.centralPoint();
var bc = b.centralPoint();
return (ac.x + ac.y + a.z) - (bc.x + bc.y + b.z)
}
You could take z first then y and at last x.
this.objects.sort((a, b) =>
a.z - b.z ||
a.y - b.y ||
a.x - b.x
);

How can I create an array of X,Y coordinates in a square shape given three inputs?

I am trying to create a grid of x/y coordinates in a square pattern, given three points on an x/y plane. A grid like this
This is to be used to drive a gcode generator for moving a tool-head to desired x,y positions on a 3d printer. I need to account for skew and off-alignment of the square, so the grid of x / y points inside the square needs to account for the alignment.
function genGrid (topLeft, btmRight, btmLeft, rows, cols) {
// Generate Grid
// Return array of coordinates like the red dots in the picture I made.
}
[This picture helps explain it better!]
This code did the trick!
<script>
function grid(p1, p2, count) {
pointPerRow = Math.sqrt(count);
p3 = {
x: (p1.x + p2.x + p2.y - p1.y) / 2,
y: (p1.y + p2.y + p1.x - p2.x) / 2
};
p4 = {
x: (p1.x + p2.x + p1.y - p2.y) / 2,
y: (p1.y + p2.y + p2.x - p1.x) / 2
};
edgeLenght = Math.sqrt( (p3.x - p1.x)**2 + (p3.y - p1.y)**2);
vectorH = {
x: (p3.x - p1.x) / edgeLenght,
y: (p3.y - p1.y) / edgeLenght
};
vectorV = {
x: (p4.x - p1.x) / edgeLenght,
y: (p4.y - p1.y) / edgeLenght
};
movingStep = edgeLenght / (pointPerRow -1);
result = {};
for (var i = 0; i < pointPerRow; i++) {
row = {};
point = {
x: p1.x + vectorH.x * movingStep * (i),
y: p1.y + vectorH.y * movingStep * (i),
}
for (var j = 0; j < pointPerRow; j++) {
row[j] = {
x: point.x + vectorV.x * movingStep * (j),
y: point.y + vectorV.y * movingStep * (j),
};
}
result[i] = row;
}
// Debugging
for (var x=0;x < pointPerRow; x++) {
for (var y=0; y < pointPerRow; y++) {
ctx.fillStyle="#000000";
ctx.fillRect(result[x][y].x,result[x][y].y,10,10);
}
}
ctx.fillStyle="#FF0000";
ctx.fillRect(p1.x,p1.y,5,5);
ctx.fillRect(p2.x,p2.y,5,5);
ctx.fillRect(p3.x,p3.y,5,5);
ctx.fillRect(p4.x,p4.y,5,5);
return result;
}
// Create a canvas that extends the entire screen
// and it will draw right over the other html elements, like buttons, etc
var canvas = document.createElement("canvas");
canvas.setAttribute("width", window.innerWidth);
canvas.setAttribute("height", window.innerHeight);
canvas.setAttribute("style", "position: absolute; x:0; y:0;");
document.body.appendChild(canvas);
//Then you can draw a point at (10,10) like this:
var ctx = canvas.getContext("2d");
var grid = grid({x:100, y:50}, {x:200, y:350}, 16);
</script>

Motion paths using cardinal splines

I reformatted some code I found to use cardinal splines and draw a curve given a set of points to work with my Canvas library, which works quite nicely, but then I wanted to also use said technique to move objects along a given set of points -- a path. SO had several questions pertaining to my problem, and I've tried to implement the last answer of this question, but I honestly have no idea what half the variables in his code mean. Here's my library, and the curve object by itself:
Art.prototype.modules.display.Base.extend({
constructor: function (options) {
// Declare variables for brevity.
var extend = Art.prototype.modules.utility.extend,
defaults = {
points: [],
tension: 0.5,
closed: false
};
// Extend the object with the defaults overwritten by the options.
extend(this, extend(defaults, options));
},
id: 'curve',
draw: function () {
// Declare variables for brevity.
var t = this,
graphics = Art.prototype.modules.display.curve.core.graphics,
controls = [],
n = t.points.length,
getControlPoints = function (a, b, c, d, e, f, tension) {
var x = {
x: Math.sqrt(Math.pow(c - a, 2) + Math.pow(d - b, 2)),
y: Math.sqrt(Math.pow(e - c, 2) + Math.pow(f - d, 2))
};
var y = {
x: tension * x.x / (x.x + x.y)
};
y.y = tension - y.x;
var z = {
x: c + y.x * (a - e),
y: d + y.x * (b - f)
};
var $z = {
x: c - y.y * (a - e),
y: d - y.y * (b - f)
};
return [z.x, z.y, $z.x, $z.y];
};
graphics.strokeStyle = t.stroke;
graphics.lineWidth = t.lineWidth;
if (t.closed) {
t.points.push(t.points[0], t.points[1], t.points[2], t.points[3]);
t.points.unshift(t.points[n - 1]);
t.points.unshift(t.points[n - 1]);
for (var p = 0; p < n; p += 2) {
controls = controls.concat(getControlPoints(t.points[p], t.points[p + 1], t.points[p + 2], t.points[p + 3], t.points[p + 4], t.points[p + 5], t.tension));
}
controls = controls.concat(controls[0], controls[1]);
for (var $p = 2; $p < n + 2; $p += 2) {
graphics.beginPath();
graphics.moveTo(t.points[$p], t.points[$p + 1]);
graphics.bezierCurveTo(controls[2 * $p - 2], controls[2 * $p - 1], controls[2 * $p], controls[2 * $p + 1], t.points[$p + 2], t.points[$p + 3]);
graphics.stroke();
graphics.closePath();
}
} else {
for (var p = 0; p < n - 4; p += 2) {
controls = controls.concat(getControlPoints(t.points[p], t.points[p + 1], t.points[p + 2], t.points[p + 3], t.points[p + 4], t.points[p + 5], t.tension));
}
for (var $p = 2; $p < t.points.length - 5; $p += 2) {
graphics.beginPath();
graphics.moveTo(t.points[$p], t.points[$p + 1]);
graphics.bezierCurveTo(controls[2 * $p - 2], controls[2 * $p - 1], controls[2 * $p], controls[2 * $p + 1], t.points[$p + 2], t.points[$p + 3]);
graphics.stroke();
graphics.closePath();
}
graphics.beginPath();
graphics.moveTo(t.points[0], t.points[1]);
graphics.quadraticCurveTo(controls[0], controls[1], t.points[2], t.points[3]);
graphics.stroke();
graphics.closePath();
graphics.beginPath();
graphics.moveTo(t.points[n - 2], t.points[n - 1]);
graphics.quadraticCurveTo(controls[2 * n - 10], controls[2 * n - 9], t.points[n - 4], t.points[n - 3]);
graphics.stroke();
graphics.closePath();
}
return this;
}
});
I don't necessarily want the code handed to me on a silver platter (although... that would be nice) -- rather, I want to learn the math involved, but preferably in psuedocode and in relatively simple terms. An explanation of the SO answer I linked to would be especially helpful, as it works nicely.
Using an alternative implementation (https://gitlab.com/epistemex/cardinal-spline-js disclaimer: I'm the author) will produce all points along the path that you need in a simple way.
You can now calculate the total length
Find the corresponding segment in the returned array
Normalize the remainder to find exact location on the path
Example
After obtaining the points as a spline point array, the main function will iterate over the array to find the segment the two points the desired position is between. Next it will interpolate between those to get a final (x,y) position (there is plenty of room for optimization here):
This allows us to move along the spline with an even speed -
function getXY(points, pos, length) {
var len = 0, // total length so far
lastLen, // last segment length
i, // iterator
l = points.length; // length of point array
// find segment
for(i = 2; i < l; i += 2) {
// calculate length of this segment
lastLen = dist(points[i], points[i+1], points[i-2], points[i-1]);
// add to total length for now
len += lastLen;
// are we inside a segment?
if (pos < len && lastLen) {
len -= lastLen; // to back to beginning
pos -= len; // adjust position so we can normalize
return {
// interpolate prev X + (next X - prev X) * normalized
x: points[i-2] + (points[i] - points[i-2]) * (pos / lastLen),
y: points[i-1] + (points[i+1] - points[i-1]) * (pos / lastLen)
}
}
}
}
Full example
var ctx = document.querySelector("canvas").getContext("2d"),
points = [
10, 10, // x,y pairs
100, 50,
500, 100,
600, 200,
400, 220,
200, 90
],
spline = getCurvePoints(points),
length = getLength(spline),
t = 0,
dx = 3; // number of pixels to move object
// move along path:
(function loop() {
// make t ping-pong, and clamp t to [0, (length-1)]
t += dx;
if (t < 0 || t >= length) dx = -dx;
t = Math.max(0, Math.min(length - 1, t));
// find segment in points which t is inside:
var pos = getXY(spline, t, length);
// redraw
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
render();
// show marker
ctx.fillRect(pos.x - 3, pos.y - 3, 6, 6);
requestAnimationFrame(loop)
})();
function render(points) {
ctx.beginPath();
ctx.moveTo(spline[0], spline[1]);
for(var i = 2; i < spline.length; i+=2)
ctx.lineTo(spline[i], spline[i+1]);
ctx.stroke()
}
function getXY(points, pos, length) {
var len = 0, lastLen, i, l = points.length;
// find segment
for(i = 2; i < l; i += 2) {
lastLen = dist(points[i], points[i+1], points[i-2], points[i-1]);
len += lastLen;
if (pos < len && lastLen) {
len -= lastLen;
pos -= len;
return {
x: points[i-2] + (points[i] - points[i-2]) * (pos / lastLen),
y: points[i-1] + (points[i+1] - points[i-1]) * (pos / lastLen)
}
}
}
return null
}
function getLength(points) {
for(var len = 0, i = 0, dx, dy; i < points.length - 2; i+=2) {
len += dist(points[i+2], points[i+3], points[i], points[i+1])
}
return len
}
function dist(x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1;
return Math.sqrt(dx*dx + dy*dy)
}

Inverse kinematics with an arbitrary number of points

I've modified this example of inverse kinematics in JavaScript with HTML5 Canvas and made it dynamic by seperating it into a function, and it works, but the example only uses 3 points -- start, middle, and end, and I'd like to change the number of points at will. Here's my current fiddle...
function _kinematics(joints, fx, mouse) {
joints.forEach(function (joint) {
joint.target = joint.target || {
x: fx.canvas.width / 2,
y: fx.canvas.height / 2
};
joint.start = joint.start || {
x: 0,
y: 0
};
joint.middle = joint.middle || {
x: 0,
y: 0
};
joint.end = joint.end || {
x: 0,
y: 0
};
joint.length = joint.length || 50;
});
var theta,
$theta,
_theta,
dx,
dy,
distance;
joints.forEach(function (joint) {
if (mouse) {
joint.target.x = mouse.x;
joint.target.y = mouse.y;
}
dx = joint.target.x - joint.start.x;
dy = joint.target.y - joint.start.y;
distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
_theta = Math.atan2(dy, dx);
if (distance < joint.length) {
theta = Math.acos(distance / (joint.length + joint.length)) + _theta;
dx = dx - joint.length * Math.cos(theta);
dy = dy - joint.length * Math.sin(theta);
$theta = Math.atan2(dy, dx);
} else {
theta = $theta = _theta;
}
joint.middle.x = joint.start.x + Math.cos(theta) * joint.length;
joint.middle.y = joint.start.y + Math.sin(theta) * joint.length;
joint.end.x = joint.middle.x + Math.cos($theta) * joint.length;
joint.end.y = joint.middle.y + Math.sin($theta) * joint.length;
fx.beginPath();
fx.moveTo(joint.start.x, joint.start.y);
/* for (var i = 0; i < joint.points.length / 2; i++) {
fx.lineTo(joint.points[i].x, joint.points[i].y);
} */
fx.lineTo(joint.middle.x, joint.middle.y);
/* for (var j = joint.points.length / 2; j < joint.points.length; j++) {
fx.lineTo(joint.points[j].x, joint.points[j].y);
} */
fx.lineTo(joint.end.x, joint.end.y);
fx.strokeStyle = "rgba(0,0,0,0.5)";
fx.stroke();
fx.beginPath();
fx.arc(joint.start.x, joint.start.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(255,0,0,0.5)";
fx.fill();
fx.beginPath();
fx.arc(joint.middle.x, joint.middle.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(0,255,0,0.5)";
fx.fill();
fx.beginPath();
fx.arc(joint.end.x, joint.end.y, 10, 0, Math.PI * 2);
fx.fillStyle = "rgba(0,0,255,0.5)";
fx.fill();
});
}
That's just the function, I've omitted the rest for brevity. As you can see, the commented-out lines were my attempt to draw the other points.
Also, here's where I populate the joints array with the points and such. See commented lines.
populate(_joints, $joints, function() {
var coords = randCoords(map);
var o = {
start: {
x: coords.x,
y: coords.y
},
// points: [],
target: {
x: mouse.x,
y: mouse.y
}
};
/* for (var p = 0; p < 10; p++) {
o.points.push({
x: p === 0 ? o.start.x + (o.length || 50) : o.points[p - 1].x + (o.length || 50),
y: p === 0 ? o.start.y + (o.length || 50) : o.points[p - 1].y + (o.length || 50)
});
}; */
return o;
});
How would I make this function work with n points?

Categories