I need to have all point coordinates for a given circle one after another, so I can make an object go in circles by hopping from one point to the next. I tried the Midpoint circle algorithm, but the version I found is meant to draw and the coordinates are not sequential. They are produced simultaneously for 8 quadrants and in opposing directions on top of that. If at least they were in the same direction, I could make a separate array for every quadrant and append them to one another at the end. This is the JavaScript adapted code I have now:
function calcCircle(centerCoordinates, radius) {
var coordinatesArray = new Array();
// Translate coordinates
var x0 = centerCoordinates.left;
var y0 = centerCoordinates.top;
// Define variables
var f = 1 - radius;
var ddFx = 1;
var ddFy = -radius << 1;
var x = 0;
var y = radius;
coordinatesArray.push(new Coordinates(x0, y0 + radius));
coordinatesArray.push(new Coordinates(x0, y0 - radius));
coordinatesArray.push(new Coordinates(x0 + radius, y0));
coordinatesArray.push(new Coordinates(x0 - radius, y0));
// Main loop
while (x < y) {
if (f >= 0) {
y--;
ddFy += 2;
f += ddFy;
}
x++;
ddFx += 2;
f += ddFx;
coordinatesArray.push(new Coordinates(x0 + x, y0 + y));
coordinatesArray.push(new Coordinates(x0 - x, y0 + y));
coordinatesArray.push(new Coordinates(x0 + x, y0 - y));
coordinatesArray.push(new Coordinates(x0 - x, y0 - y));
coordinatesArray.push(new Coordinates(x0 + y, y0 + x));
coordinatesArray.push(new Coordinates(x0 - y, y0 + x));
coordinatesArray.push(new Coordinates(x0 + y, y0 - x));
coordinatesArray.push(new Coordinates(x0 - y, y0 - x));
}
// Return the result
return coordinatesArray;
}
I prefer some fast algorithm without trigonometry, but any help is appreciated!
EDIT
This is the final solution. Thanks everybody!
function calcCircle(centerCoordinates, radius) {
var coordinatesArray = new Array();
var octantArrays =
{oct1: new Array(), oct2: new Array(), oct3: new Array(), oct4: new Array(),
oct5: new Array(), oct6: new Array(), oct7: new Array(), oct8: new Array()};
// Translate coordinates
var xp = centerCoordinates.left;
var yp = centerCoordinates.top;
// Define add coordinates to array
var setCrd =
function (targetArray, xC, yC) {
targetArray.push(new Coordinates(yC, xC));
};
// Define variables
var xoff = 0;
var yoff = radius;
var balance = -radius;
// Main loop
while (xoff <= yoff) {
// Quadrant 7 - Reverse
setCrd(octantArrays.oct7, xp + xoff, yp + yoff);
// Quadrant 6 - Straight
setCrd(octantArrays.oct6, xp - xoff, yp + yoff);
// Quadrant 3 - Reverse
setCrd(octantArrays.oct3, xp - xoff, yp - yoff);
// Quadrant 2 - Straight
setCrd(octantArrays.oct2, xp + xoff, yp - yoff);
// Avoid duplicates
if (xoff != yoff) {
// Quadrant 8 - Straight
setCrd(octantArrays.oct8, xp + yoff, yp + xoff);
// Quadrant 5 - Reverse
setCrd(octantArrays.oct5, xp - yoff, yp + xoff);
// Quadrant 4 - Straight
setCrd(octantArrays.oct4, xp - yoff, yp - xoff);
// Quadrant 1 - Reverse
setCrd(octantArrays.oct1, xp + yoff, yp - xoff);
}
// Some weird stuff
balance += xoff++ + xoff;
if (balance >= 0) {
balance -= --yoff + yoff;
}
}
// Reverse counter clockwise octant arrays
octantArrays.oct7.reverse();
octantArrays.oct3.reverse();
octantArrays.oct5.reverse();
octantArrays.oct1.reverse();
// Remove counter clockwise octant arrays last element (avoid duplicates)
octantArrays.oct7.pop();
octantArrays.oct3.pop();
octantArrays.oct5.pop();
octantArrays.oct1.pop();
// Append all arrays together
coordinatesArray =
octantArrays.oct4.concat(octantArrays.oct3).concat(octantArrays.oct2).concat(octantArrays.oct1).
concat(octantArrays.oct8).concat(octantArrays.oct7).concat(octantArrays.oct6).concat(octantArrays.oct5);
// Return the result
return coordinatesArray;
}
Use can try the following approach: use the algorithm you gave but push your coordinates to eight different coordinateArrays. Afterwards you have to reverse half of them (those with (x0+x,y0-y), (x0-x,y0+y), (x0+y,y0+x), (x0-y,y0-x)) and afterwards append all arrays in the correct order. Take care that you add the first four points to the correct arrays.
As far as I know, you cannot do it without trigonometry, but it works pretty fast for me. Sorry I'm not familiar with Java, so I write the code in VB:
Dim PointList As New List(Of PointF)
For angle = 0 To Math.PI * 2 Step 0.01
'the smaller the step, the more points you get
PointList.Add(New PointF(Math.Cos(angle) * r + x0, Math.Sin(angle) * r + y0))
Next
x0 and y0 are the center coordinates of the circle, r is the radius.
Hope I answered your question.
Here is a javascript implementation based on Dave's answer. A bit over-engineered, I wanted to avoid calling sin and cos more than necessary. Ironically making use of Dave's first answer without the radius :)
function calculateCircle(x,y,radius) {
var basicPoints = getBasicCircle();
var i = basicPoints.length;
var points = []; // don't change basicPoints: that would spoil the cache.
while (i--) {
points[i] = {
x: x + (radius * basicPoints[i].x),
y: y + (radius * basicPoints[i].y)
};
}
return points;
}
function getBasicCircle() {
if (!arguments.callee.points) {
var points = arguments.callee.points = [];
var end = Math.PI * 2;
for (var angle=0; angle < end; angle += 0.1) {
points.push({x: Math.sin(angle),
y: Math.cos(angle)
});
}
}
return arguments.callee.points
}
Related
The above expression was taken from the below method.
I know that to rotate a point around the center we have to
Move the point to the origin
Make the rotation and
Move the point back
But the pieces I don't get my head around are:
r[0] = x * Math.cos(angle) - y * Math.sin(angle);
^
|
why we use the minus sign here?
r[1] = x * Math.sin(angle) + y * Math.cos(angle);
^
|
And why here we use plus sign instead of minus?
Vec2.prototype.rotate = function (center, angle) {
//rotate in counterclockwise
var r = [];
var x = this.x - center.x;
var y = this.y - center.y;
r[0] = x * Math.cos(angle) - y * Math.sin(angle);
r[1] = x * Math.sin(angle) + y * Math.cos(angle);
r[0] += center.x;
r[1] += center.y;
return new Vec2(r[0], r[1]);
};
The book was to be great but it doesn't explain most of the code it simply spits out.
I got it! Just saw a video of Dr Peyam on transformation matrix.
To get x' and y' we multiply the transformation matrix by the current coordinate (x,y)
enter image description here
I am working on an animation library and have a variable that is incrementing from 0 to 1 (X axis in the graphic) while a timer is running. I now want to apply easing to my function in a similar fashion as the CSS function cubic-bezier.
My goal is that the y value doesn't increment linearly but instead is converted by the easing function. The function should look something like this:
function cubic-bezier(x, p0x, p0y, p1x, p1y){
// Any suggestions for a formula or algorithm?
return y;
};
I am not using any libraries and can't use any third party libraries on this project.
Thanks!
EDIT: This is the code I came up with. It is an adaption of this code jsfiddle.net/fQYsU.
var p1 = {0.5, 0.5};
var p2 = {0.5, 0.5};
cubic-bezier(t, p1, p2){
var cX = 3 * (p1.x),
bX = 3 * (p2.x - p1.x) - cX,
aX = 1 - cX - bX;
var cY = 3 * (p1.y - 1),
bY = 3 * (p2.y - p1.y) - cY,
aY = -1 - cY - bY;
var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t);
var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + 1;
return {x: x, y: y};
}
var y = cubic-bezier(progress, p1, p2).y;
But the value it returns corresponds to x which might not be t. So I still don't know how to get the value at the correct position.
I'm doing a graph theory project, and I need to show the edge weight above each of the edges.
currently I'm using this method:
var x1; //starting point
var x2; //ending point
function setup() {
createCanvas(640, 480);
x1 = createVector(random(0, width/2), random(0, height/2)); //random position to the upper left
x2 = createVector(random(width/2, width), random(height/2, height)); //random position to the lower right
}
function draw() {
background(200);
stroke(0);
line(x1.x, x1.y, x2.x, x2.y); //draw a line beetween the points
d = dist(x1.x, x1.y, x2.x, x2.y);
var angle = atan2(x1.y - x2.y, x1.x - x2.x); // gets the angle of the line
textAlign(CENTER);
text("X1", x1.x + 5, x1.y + 5); // just to show where is the begining
text("X2", x2.x - 5, x2.y - 5); // just to show there is the end
fill(0);
signalx = x1.x > x2.x ? -1 : 1; // if the start is to the right of the end
signaly = x1.y > x2.y ? -1 : 1; // if the start is below the end
// I think i need to use the angle here
text(42, (x1.x + (d / 2) * signalx), (x1.y + (d / 2) * signaly));
}
the problem is that the result, well, is not as expected:
The idea is that the text I'm showing (42, the edge weight) is a little bit above the middle of the line, what is currently not happening.
I know that I have to take the angle of the line into consideration, but not sure where.
Thanks for any help, and if there's any need of more information let me know.
What you want to do is use linear interpolation. First, find the equation of the line in slope-intercept form, so you can solve for y (when you know x). (I'm just going to rename x1 to p1 and x2 to p2 for clarity.)
(math)
// with points (x1, y1) and (x2, y2)
y - y1 = m*(x - x1) // point-slope form (just a step)
y - y1 = m*x - m*x1
y = m*x - m*x1 + y1 // slope-intercept
Then, since x is the midpoint of the line, x equals the average of the two endpoints. And then calculate y, based on the above equation:
(code)
float m = (p2.y - p1.y) / (p2.x - p1.x);
int x = (x2 + x1) / 2;
int y = m*x - m*p1.x + p1.y;
What I am aiming to do is arc the position of a circle towards the position of the mouse cursor, this all being relative to the world viewed through the canvas. To keep a handle on the speed at which the circle moves I decided to make a boundary larger than the circle, if the mouse is outside the boundary then the "position" of the mouse is brought to the boundary so that when I arc towards the coords, if they arent super far from the position of the circle it doesnt move at crazy speeds. I have this working and this is the code which does it:
dx = Game.controls.mouseX - (this.x - xView); // get the distance between the x coords
dy = Game.controls.mouseY - (this.y - yView); // get the distance between the y coords
radii = this.radius + 1; // +1 because the "radius" of the mouse is 1
if((dx * dx) + (dy * dy) > radii * radii) // is the mouse not over the player?
{
if((dx * dx) + (dy * dy) < 301 * 301)
{
this.x += ((Game.controls.mouseX - (this.x - xView)) * 2 / (this.mass)) + step;
this.y += ((Game.controls.mouseY - (this.y - yView)) * 2 / (this.mass)) + step;
}
else
{
mx = Game.controls.mouseX;
my = Game.controls.mouseY;
do
{
dx = mx - (this.x - xView);
dy = my - (this.y - yView);
mx += (((this.x - xView) - mx) * 2 / (this.mass)) + step;
my += (((this.y - yView) - my) * 2 / (this.mass)) + step;
} while((dx * dx) + (dy * dy) > 301 * 301)
this.x += ((mx - (this.x - xView)) * 2 / (this.mass)) + step;
this.y += ((my - (this.y - yView)) * 2 / (this.mass)) + step;
}
}
The magic for 'outside the boundary' lies withing the do while. This is the best fix I could come up with and I cant see this as being an elegant or fast solution and am wondering what the proper course of action should be.
Im no artist but hopefully this image helps to illustrate what I am trying to achieve. The Black dot is the mouse pos, the black circle is the circle and the red circle is the boundary I have specified. I want to get the coords marked by the X.
Your question is a special case of Circle line-segment collision detection algorithm?, in this case with B and C being the same points, so you can use your center point for both of them.
That solution is given in C, but it translates to JavaScript very easily, just replace float with var, use Math.sqrt() and so on...
Oh, and then there is a JvaScript version here: Calculate the point of intersection of circle and line through the center, that's more appropriate :-)
If the black circle is in the center of the red circle and you have the radius of the red circle
// c is circle center
// mouse is the mouse position. Should have properties x,y
// radius is the circle radius;
// returns the point on the line where the circle intercepts it else it returns undefined.
function findX(c, mouse, radius)
var v = {};
// get the vector to the mouse
v.x = mouse.x - c.x;
v.y = mouse.y - c.y;
var scale = radius / Math.hypot(v.x,v.y);
if(scale < 1){ // is it outside the circle
return {
x : c.x + v.x * scale,
y : c.y + v.y * scale
};
}
return;
}
And if the the line start is not the center then a general purpose line circle intercept function will solve the problem. If the line starts inside the circle the function will return just one point. If the line is not long enough it will return an empty array..
// 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'm trying to draw a smooth curved arc between two points in canvas, I have set up the points as sutch note these are dynamic and can change.
var p1 = {
x=100, y=100
}
var p2 = {
x=255, y=255
}
The curve would look something like this
Here my started code, I can't get my head around the math/logic of this function:
function curveA2B(a,b){
var mindpoint = {
x: (a.x+b.x)/2,
y: (a.y+b.y)/2,
d: Math.sqrt(Math.pow(b.x-a.x,2) + Math.pow(b.y-a.y,2))
};
context.beginPath();
context.arc(
a.x,
a.y,
mindpoint.d/2,
1.5*Math.PI,
0,
false
);
context.arc(
b.x,
b.y,
mindpoint.d/2,
1*Math.PI,
0.5*Math.PI,
true
);
context.context.stroke();
}
The dynamic examples is here: http://jsfiddle.net/CezarisLT/JDdjp/6/
I created a function that would be easily modifiable to many needs called plot_curve that gives you an idea of the breakdown of your problem.
A quick DEMO: http://jsfiddle.net/LVFat/
function plot_curve(x,y,xx,yy, target,color)
{
var startX=x;
var startY=y;
var endX=xx;
var endY=yy;
var diff_x = xx - x;
var diff_y = yy - y;
var bezierX=x; // x1
var bezierY=yy; // y2
console.log("bx:"+bezierX);
console.log("by:"+bezierY);
var cx,cy, t;
for(t=0.0; t<=1; t+=0.01)
{
cx = Math.round( (1-t)*(1-t)*startX + 2*(1-t) * t * bezierX + t*t*endX);
cy = Math.round( (1-t)*(1-t)*startY + 2*(1-t) * t * bezierY + t*t*endY);
// change this part to whatever you are trying to manipulate to the curve
plot_pixel( Math.round(cx), Math.round(cy), target, color);
}
}
example... (works with a divCanvas function I made.. check out jsfiddle link...)
plot_curve(25,25,5,5, ".divCanvas","blue");
if you just want the coords for the curve between the two points, try this:
function plot_curve(x,y,xx,yy)
{
// returns an array of x,y coordinates to graph a perfect curve between 2 points.
var startX=x;
var startY=y;
var endX=xx;
var endY=yy;
var diff_x = xx - x;
var diff_y = yy - y;
var xy = [];
var xy_count = -1;
var bezierX=x; // x1
var bezierY=yy; // y2
var t;
for(t=0.0; t<=1; t+=0.01)
{
xy_count++;
xy[xy_count] = {};
xy[xy_count].x = Math.round( (1-t)*(1-t)*startX + 2*(1-t) * t * bezierX + t*t*endX);
xy[xy_count].y = Math.round( (1-t)*(1-t)*startY + 2*(1-t) * t * bezierY + t*t*endY);
}
return xy; // returns array of coordinates
}
You can use the mid of the two points as two radius settings for the x and y axis.
The following example is simplified but it shows one approach to create smooth curves inside the boxes as in your example.
The boxes will always scale so that the curves goes through the mid point between the two points (alter the end point for example).
DEMO
/// set up some values
var ctx = demo.getContext('2d'),
p1 = {x:100, y:100}, /// point 1
p2 = {x:355, y:255}, /// point 2
mx = (p2.x - p1.x) * 0.5, /// mid-point between point 1 and 2
my = (p2.y - p1.y) * 0.5,
c1 = {x: p1.x, y: p1.y + my}, /// create center point objects
c2 = {x: p2.x, y: p2.y - my},
steps = 0.05; /// curve resolution
/// mark the points and the boxes which represent the center of those
ctx.fillStyle = '#ff6e6e';
ctx.fillRect(p1.x, p1.y, mx, my);
ctx.fillStyle = '#6e93ff';
ctx.fillRect(p1.x + mx, p1.y + my, mx, my);
Then we render the quarter ellipse for each "box":
/// render the smooth curves using 1/4 ellipses
ctx.beginPath();
for(var isFirst = true, /// first point is moveTo, rest lineTo
angle = 1.5 * Math.PI, /// start angle in radians
goal = 2 * Math.PI, /// goal angle
x, y; angle < goal; angle += steps) {
/// calculate x and y using cos/sin
x = c1.x + mx * Math.cos(angle);
y = c1.y + my * Math.sin(angle);
/// move or draw line
(isFirst) ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
isFirst = false;
}
/// second box
for(var isFirst = true,
angle = Math.PI,
goal = 0.5 * Math.PI,
x, y;angle > goal; angle -= steps) {
x = c2.x + mx * Math.cos(angle);
y = c2.y + my * Math.sin(angle);
(isFirst) ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
isFirst = false;
}
ctx.stroke();
I'll leave it to you to put this into re-usable functions. Hope this helps!
If this doesn't cut it I would recommend you to take a look at my cardinal spline implementation.