How calculate a 1/4 circle arc to move along (bezier curve)? - javascript

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?
I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.
var path = {
start: {
x: currentLeft,
y: currentTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
},
end: {
x: endLeft,
y: endTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
}
};
jQuery(myElement).animate(
{
path: new jQuery.path.bezier(path)
}
);
Approx. what I want:
Approx what I'm getting (they're dipping too low):

A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.
First, you need a couple of utility functions :
function r2d(x) {
/* radians to degrees */
return x * 180 / Math.PI;
}
function smaller(x, y) {
/* returns the closer of x|y to zero */
var x_ = Math.abs(x);
var y_ = Math.abs(y);
return (Math.min(x_, y_) === x_) ? x : y;
}
Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).
function anim($el, end) {
var current = $el.position();
var slope1 = (end.top - current.top) / (end.left - current.left);
var slope2 = 1 / slope1;
var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
var startAngle = -endAngle;
var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.
//For debugging
$("#endAngle").text(endAngle);
$("#startAngle").text(startAngle);
$("#length").text(length);
var path = {
start: {
x: current.left,
y: current.top,
angle: startAngle,
length: length
},
end: {
x: end.left,
y: end.top,
angle: endAngle,
length: length
}
};
$el.animate({ path: new jQuery.path.bezier(path) });
}
The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.
DEMO

If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:
p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }
And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.
In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

Related

Function that generates a path along other path, from a starting to an end point

const coords = [
{
name: "Rijnstraat vervolg",
points: [
[695, 500],
[680, 480],
[580, 475],
[520, 460],
],
width: 10,
types: [types.car, types.truck, types.pedestrian, types.bike],
oneway: true,
},
...
]
I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads)
And the function can be described as someone who is trying to get to somewhere.
I already tried a recursive function that looks like this:
function calculatePathToShop(startPoint, shopPoint) {
const targetShopPoint = findClosestPointOnPath(shopPoint);
const targetPathIndex = findPathByPoint(targetShopPoint);
const connectedPaths = calculateConnectedPaths(targetPathIndex);
let startPathIndex = -1;
connectedPaths.forEach(path => {
const pathPoints = coords[path].points;
pathPoints.forEach(pathPoint => {
if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path;
});
});
if (startPathIndex == -1) return false;
let startPathPoints = coords[startPathIndex].points;
let targetPathPoints = coords[targetPathIndex].points;
if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse();
ctx.strokeStyle = "rgba(255, 0, 0, .05)";
}
This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail
So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image)
Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js
(The rest of the code is in the github repo itself)
Fixed step interpolation
To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps.
EG
steps = 100;
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps};
Then loop that number of steps adding the vector to a position initialized to the start point.
points = []; // array of interpolated points
point = {...start} // set start position.
while (steps--) {
points.push({...point});
point.x += vec.x;
point.y += vec.y;
}
points.push({...end}); // last point at end
This will create different spacing for different line lengths.
Fixed distance interpolation
To get a constant spacing between points you will need to use the lines' length to get the number of steps.
pixelsPerStep = 2; // distance between points.
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: end.x - start.x, y: end.y - start.y};
lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep;
points = []; // array of interpolated points
for (i = 0; i < lineSteps ; i += 1) {
u = i / lineSteps;
points.push({x: start.x + step.x * u, y: start.y + step.y * u});
}
// check to add end point
Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it.
eg from code above
// add last point if within (0.01 * pixelsPerStep) pixels of correct spacing
if (Math.abs(lineSteps - i) < 0.01) {
points.push({...end});
}
Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment.
Example
The code below is an example of a constant spaced set of points interpolated from another set of points.
The example draws the new points in black dots. The original points are rendered in red.
Note that the distance between new points is constant and thus may not fall on the original (red) points.
Note that there is a check at the end to test if a last point should be added.
const ctx = canvas.getContext("2d");
const P2 = (x, y) => ({x, y});
const points = [
P2(100,90),
P2(300,210),
P2(350,110),
P2(50,10),
P2(6,219),
];
const interpolatedPoints = interpolatePath(points, 35);
drawPoints(interpolatedPoints, 2);
ctx.fillStyle = "RED";
drawPoints(points);
function drawPoints(points, size = 1) {
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1);
}
ctx.fill();
}
function interpolatePath(path, pixelStep) {
const res = [];
var p2, i = 1, overflow = 0;
while (i < path.length) {
const p1 = path[i - 1];
p2 = path[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const len = Math.hypot(dx, dy) / pixelStep;
let j = overflow;
while (j < len) {
const u = j / len;
res.push(P2(p1.x + dx * u, p1.y + dy * u));
j++;
}
overflow = j - len;
i++;
}
// add last point if close to correct distance
if (Math.abs(overflow) < 0.01) {
res.push(P2(p2.x, p2.y));
}
return res;
}
<canvas id="canvas" width="400" height="400"></canvas>

svg.js animated rotation of elements gives unexpected results (visible "jiggling")

I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).
If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).
I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:
var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
x: 70,
y: 70
}
var length = 60
var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#00f",
width: 4
})
blueLine.angle = 0
var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#0f0",
width: 4
})
greenLine.angle = 0
var previous = 0
var dt = 0
var step = function(timestamp) {
dt = timestamp - previous
previous = timestamp
blueLine.angle += 0.18 * dt
blueLine.rotate(blueLine.angle, origin.x, origin.y)
var endX = Math.cos(toRad(blueLine.angle)) * length
var endY = Math.sin(toRad(blueLine.angle)) * length
// Comment out this line, and rotation works fine
greenLine.move(endX, endY)
greenLine.angle = blueLine.angle - 10
// Comment out this line, and movement works fine
greenLine.rotate(greenLine.angle, origin.x, origin.y)
// But they don't work together. If I both move and rotate
// the green line, it goes in this crazy huge arc, rather
// than rotating neatly around the end of the blue line
// as expected.
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
function toRad(deg) {
return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>
Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.
I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.
Thank you kind vector graphics experts!
Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:
Rider.Leg = function(foot, front, xOffset, yOffset) {
var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
this.foot = foot
this.draw = foot.draw
this.geo = {
upper: {
x: this.foot.pedal.gear.x + 150,
y: this.foot.pedal.gear.y - 750,
length: 396
},
lower: {
length: 390
}
}
this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
.transform({ scale: 0.95, cx: 0, cy: 0 })
this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}
// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
var angle = this.pedalAngle() - Math.PI
var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
var x = this.geo.upper.length * Math.cos(ha + angle)
var y = this.geo.upper.length * Math.sin(ha + angle)
this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)
}
// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.hypot(xDist, yDist)
}
// Gets the angle between the hip joint and the pedal
Rider.Leg.prototype.pedalAngle = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.atan2(yDist, xDist)
}
Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}
When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.
Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:
greenLine.transform({x:endX, y:endY, relative: true})
To move the line by a certain amount. That should work way better.

How to find angle between two straight lines (paths) on a SVG in Javascript?

I have two straight lines as <path> in SVG canvas. Using pixel coordinates of LineA (A1x, A1y) (A2x, A2y) and LineB (B1x, B1y) (B2x, B2y) how can I calculate the angle between these lines.
I have below code which works for THREE points (it works for green cases in below image). It does not work when (A2x, A2y) != (B1x, B1y).
How can I modify this formula to work even when lines are not joined.
function find_angle(p0,p1,c) {
var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
Math.pow(c.y-p0.y,2));
var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
Math.pow(c.y-p1.y,2));
var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
Math.pow(p1.y-p0.y,2));
var angle = Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
return angle * (180 / Math.PI);
}
You can exploit Math.atan2 function with cross product and dot product of direction vectors for these segments. Note the atan2 returns signed angle in range -Pi...Pi
//find vector components
var dAx = A2x - A1x;
var dAy = A2y - A1y;
var dBx = B2x - B1x;
var dBy = B2y - B1y;
var angle = Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy);
if(angle < 0) {angle = angle * -1;}
var degree_angle = angle * (180 / Math.PI);
You want P0-C, P1-D instead of P0-C, P1-C: just translate one of the segments to let D and C coincide: P1' = P1 - D + C (then D' = C).
After submitting my answer, I realize this is the same solution as the one provided by #YvesDaoust. That answer is a more concise conceptual summary of the same approach I have fleshed out here with a JavaScript example.
The answer is fairly simple:
function find_disconnected_angle(p0,c0, p1,c1) {
return find_angle({x:p0.x-c0.x+c1.x,y:p0.y-c0.y+c1.y},p1,c1);
}
You can calculate the angle from scratch using trigonometry fundamentals. However, to make your life easier you can also just use the function you already have. First, just mathematically translate one line so that one of its end points coincides with one of the end points of the other line. There are four different ways of matching up one end point from each line, and each way will produce a potentially different angle measure. However, this is no bigger of a dilemma than you having to figure out which angle you want of the four angles you get when you take each of the original untranslated line segments, extend each into an infinite line, and examine the four angles where those two lines intersect.
You need a function that takes 4 points as inputs, i.e. p0, p1, p2 and p3. However, just to make clear which points are being made coincidental, I've instead labeled them as p0, c0, p1 and c1, such that p0 and c0 are both being moved in such a way as to make c0 and c1 coincide, resulting in three points: p0new, p1 and c, the latter of which equals both c1 and c0new.
Update: Upon examining your original function more closely, I realize my discussion above of the choice of four possible angles may not be relevant with the exact function implementation you have written, as the order of points p0 and p1 does not matter for your function. You could rewrite your original function, perhaps using some of the concepts from the other answers, to be able to more fully control which angle you get as a result, if that's really what you want. In any case, the general concept behind my answer holds: if you already have a function that calculates an angle between 3 points (with whatever limitations the algorithm has), you can use the same function on two disconnected line segments by simply translating one so that two end points coincide and then using the same function (again, with whatever limitations the algorithm still has).
function find_angle(p0,p1,c) {
var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
Math.pow(c.y-p0.y,2));
var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
Math.pow(c.y-p1.y,2));
var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
Math.pow(p1.y-p0.y,2));
var angle = Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
return angle * (180 / Math.PI);
}
function find_disconnected_angle(p0,c0, p1,c1) {
return find_angle({x:p0.x-c0.x+c1.x,y:p0.y-c0.y+c1.y},p1,c1);
}
console.log(
find_angle(
{x: 7, y: 2},
{x: 7, y: 7},
{x: 2, y: 2}
)
);
console.log(
find_disconnected_angle(
{x: 27, y: 42},
{x: 22, y: 42},
{x: 7, y: 7},
{x: 2, y: 2}
)
);
finding the angle (#) between two lines:
tan# = (m1-m2)/(1+m1.m2)
where m1 and m2 are the gradient of the lines respectively. In terms of JS:
var m1 = (A1y-A2y)/(A1x-A2x)
var m2 = (B1y-B2y)/(B1x-B2x)
var angle
if(m1*m2==-1){
angle = Math.PI/2
}else{
angle = Math.atan((m1-m2)/(1+m1*m2))
}

How to make a parallel lines in html canvas

I have these conditions:
Point A, B and C are created.
Point A to Point B will create a line.
Parallel lines are created depending on the position of Point A, B and C (refer to the figure below).
If you move Point A, the lines will also move but Points B and C remain on their respective positions.
They can be moved at any position.
What I want is to create this:
Consider the figure 1 below (I'm sure you already know this basic 2D geometry but without this my answer would be incomplete):
Coordinates for points A and B are known and we want to find function that can be used to calculate y-coordinate whenever x-coordinate is known, in such a way that point (x,y) lies on the line. From the figure 1:
k = tan(alpha) = (y2 - y1) / (x2 - x1) - the slope of line
Putting coordinates of either A or B into well known line equation y = kx + m we can calculate m to make the equation complete. Having this equation, for any coordinate x we can calculate coordinate y using this equation. The good thing about it is that it doesn't depend on the position of point A and B or slop (angle) of the line - you will have to take care of the special case of vertical/horizontal lines where y/x will be infinite according to this equation.
Back to your question. Take a look at figure 2 below:
We have very similar situation here, there is a line between points A and C, and line between points B and D. I assumed that point A is at the center of the coordinate system! This generally won't be the case but this is really not a restriction as you can perform translation that will put A in the center, then make your calculations and then translate everything back.
Using the technique described at the beginning, you can find the line equation for the line that connects A and C points and for the line that connects B and D points (D coordinates can be easily calculated). Let's assume you did just that:
A-C: y = k1*x (m is zero as line goes through the center A)
B-D: y = k2*x + m2 (m2 is not zero as line doesn't go through the center A)
Finally the algorithm you could use to draw these parallel lines:
Choose a space with which you want to take x-coordinates between x1 and x3. For example, if you want 4 lines this space will be s = (x3 - x1) / 4 and so on.
Set value x_start = x1 + s (and later x_start += s), and calculate y-coordinate using the equation for A-C line y_end = k1*x_start. This will give you point that lies on the line A-C and this is the start of your line.
Similarly, calculate the end point that will lie on the line that connects B and D:
x_end = x2 + s (later x_end += s)
y_end = k2*x_end + m2
Using these equations calculate points (x_start,y_start) and (x_end,y_end) for all lines that you want to draw (there is |x3 - x1| / desired_num_of_lines of them).
You'll have to form new equations each time point A moves out of the current A-C line, as every time this happens the slop of the A-C (and B-D) line changes invalidating the current equations.
I'm not going to write any JS code, but having the logic behind the possible solution should give you more then enough information to move forward with you own implementation.
Always think, when using the Context2D, that using the transforms (translate, rotate, scale), can spare you some math.
With those transforms you can think of your drawing like you would do with a pen : where do you put the pen ? where do you move next (translate) ? do you rotate the page ? do you get closer or further from the page (scale) ?
Here you want to start at A, then move along AC.
Each step on the way, you want to draw the AB vector.
Here's how you could code it, as you see, just simple vector math here, so if you remember that AB vector has (B.x-A.x, B.y-A.y) coordinates, you know most of the math you'll need.
// boilerPlate
var ctx = document.getElementById('cv').getContext('2d');
ctx.strokeStyle = '#000';
// params : Points : {x,y}
var A, B, C;
A = { x: 20, y: 170 };
B = { x: 80, y: 60 };
C = { x: 140, y: 120 };
// param : number of lines to draw.
var stepCount = 5;
// ----------
// compute AB vector = B - A
var AB = { x: B.x - A.x, y: B.y - A.y };
// compute step : ( C - A ) / stepCount
var step = { x: (C.x - A.x) / stepCount, y: (C.y - A.y) / stepCount };
// -- start draw
ctx.save();
// Move pen to A
ctx.translate(A.x, A.y);
for (var i = 0; i <= stepCount; i++) {
// draw AB vector at current position
ctx.lineWidth= ( i==0 || i==stepCount ) ? 2 : 1 ;
drawVector(AB);
// move pen one step further
ctx.translate(step.x, step.y);
}
ctx.restore();
// --
// draws vector V at the current origin ((0,0)) of the context.
function drawVector(V) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(V.x, V.y);
ctx.stroke();
}
// ----------
// legend
drawPoint(A, 'A');
drawPoint(B, 'B');
drawPoint(C, 'C');
function drawPoint(P, name) {
ctx.beginPath();
ctx.arc(P.x, P.y, 3, 0, 6.28);
ctx.fill();
ctx.strokeText(name, P.x + 6, P.y + 6);
}
<canvas id='cv' width=300 height=200></canvas>
Džanan has it right, and in simple terms, you need the X and Y offsets between the starting points of the two lines, i'e' point A and point C. When drawing the line that starts at C, and assuming that it ends at D, you will need to add the same X and Y offsets, e.g., if you draw AB with starting coordinates (100, 150) as follows:
context.beginPath();
context.moveTo(100, 150);
context.lineTo(450, 50);
context.stroke();
And if C has to start at (150, 200), the offset here would be
X: 50, Y:50
so CD would be drawn as
context.beginPath();
context.moveTo(150, 200);
context.lineTo((450+50), (50+50));
context.stroke();
Now this assumes that the length of both the lines are going to be same. If they are to differ, the equation will be slightly more complex.

Particles "run away" from mouse

Im creating a simple particle experiment on canvas. Now i want them to "run away" from mouse coursor over canvas. detecting the distance from the mouse is not a problem, but how to code their behaviour?
each particle is created as following:
var particle = {
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
xVel: Math.random() * 10 - 5,
yVel: Math.random() * 10 - 5,
}
so i assume i should also save the direction somehow, and if the distance from pointer is < x, reverse the direction? maybe also save old speed, and decrease it slowly while moving away?
how to detect the direction?
Velocity (xVel, yVel, together) is a 2D vector. And so is the distance between the mouse and the particles. A vector contains both direction and magnitude. So you want a vector that is the difference between the mouse position and the particle position.
var posRelativeToMouse = {
x: particle.x - mousPosX,
y: particle.y - mousPosY
};
So small numbers of x and y mean the the particle is close to the mouse, and big mean it's far away.
Next we need to figure out how these numbers should affect the velocity of the particle. So we need 2 things.
What direction do we push them in?
We already have this, mostly. posRelativeToMouse is a vector that has the direction we want. We just normalize it, which means to set the length of the vector to 1. To do that, we divide each component by the current length of the vector. The length of this vector is always the distance to from the particle to the mouse.
var distance = Math.sqrt(
posRelativeToMouse.x * posRelativeToMouse.x +
posRelativeToMouse.y * posRelativeToMouse.y
);
var forceDirection = {
x: posRelativeToMouse.x / distance,
y: posRelativeToMouse.y / distance,
};
How hard do we push the particles?
This is an inverse of the distance. Close means a big push, far means a little push. So lets reuse our distance we calculated above.
// distance past which the force is zero
var maxDistance = 1000;
// convert (0...maxDistance) range into a (1...0).
// Close is near 1, far is near 0
// for example:
// 250 => 0.75
// 100 => 0.9
// 10 => 0.99
var force = (maxDistance - distance) / maxDistance;
// if we went below zero, set it to zero.
if (force < 0) force = 0;
Ok we have a direction, and we have the force. All that's left is to apply this to the particle velocity.
particle.xVel += forceDirection.x * force * timeElapsedSinceLastFrame;
particle.yVel += forceDirection.y * force * timeElapsedSinceLastFrame;
And assuming you are animating your position each frame by that xVel/yVel, you should now have particles being pushed away by the mouse.
you can obtain a vector v by subtracting the position of particle from position of mouse,
then you can find the magnitude of this vector my taking sqrt(x^2 + y^2)
by dividing v by magnitude, you obtain a unit vector in the direction you want your particles to go.
for instance.
suppose I have 10 particles in a list U, each has an x and y field.
I can obtain it's vector from each particle v by setting v = (xpart - mousepart, ypart - mousepart)
then you need to find the magnitude vmag by taking sqrt(vx^2 + vy^2)
then you obtain vunit = (vx / vmag, vy / vmag)
This is the vector "away from the mouse".
the rest can be left to detemining speed you want to move at, and ensuring you bounce of walls and such.
I have a similar project at github open source:
https://github.com/dmitrymakhnin/JavaParticleSystem/blob/master/Main.java

Categories