Intersection between 2 circles javascript - javascript

I’m trying to do a function to detect intersections between two circles. If yes it scores true, otherwise it scores false, but I think I got lost so it does not display what I want. If anyone can help me please . Thank you
Surely I have incorrectly coded in javascript if there is a person who knows the answer I am all ears
function AreCirclesIntersecting(c0,c1) {
x0 = c0['center']['x'];
y0 = c0['center']['y'];
r0 = c0['center']['r'];
x1 = c1['center']['x'];
y1 = c1['center']['y'];
r1 = c1['center']['r'];
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a/d);
y2 = y0 + (dy * a/d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0*r0) - (a*a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h/d);
ry = dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
const circles = [
{center: {x: 10.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 20.0}, radius: 15.0},
{center: {x: 20.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 25.0}, radius: 7.5},
];
const q7_result1 = AreCirclesIntersecting(circles[0], circles[1]);
console.log(q7_result1); // Expected output: true
const q7_result2 = AreCirclesIntersecting(circles[0], circles[2]);
console.log(q7_result2); // Expected output: true
const q7_result3 = AreCirclesIntersecting(circles[1], circles[3]);
console.log(q7_result3); // Expected output: false
const q7_result4 = AreCirclesIntersecting(circles[2], circles[3]);
console.log(q7_result4); // Expected output: false

I can't speak to the math, but there's an issue in how you're retrieving your r values. in circles they're radius directly on the object, but you're trying to retrieve them with ['center']['r']. I modified your code and it's outputing results now:
function AreCirclesIntersecting(c0,c1) {
x0 = c0['center']['x'];
y0 = c0['center']['y'];
r0 = c0['radius']; // MODIFICATION HERE
x1 = c1['center']['x'];
y1 = c1['center']['y'];
r1 = c1['radius']; // MODIFICATION HERE
var a, dx, dy, d, h, rx, ry;
var x2, y2;
/* dx and dy are the vertical and horizontal distances between
* the circle centers.
*/
dx = x1 - x0;
dy = y1 - y0;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
}
if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
}
/* 'point 2' is the point where the line through the circle
* intersection points crosses the line between the circle
* centers.
*/
/* Determine the distance from point 0 to point 2. */
a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
/* Determine the coordinates of point 2. */
x2 = x0 + (dx * a/d);
y2 = y0 + (dy * a/d);
/* Determine the distance from point 2 to either of the
* intersection points.
*/
h = Math.sqrt((r0*r0) - (a*a));
/* Now determine the offsets of the intersection points from
* point 2.
*/
rx = -dy * (h/d);
ry = dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx;
var xi_prime = x2 - rx;
var yi = y2 + ry;
var yi_prime = y2 - ry;
return [xi, xi_prime, yi, yi_prime];
}
const circles = [
{center: {x: 10.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 20.0}, radius: 15.0},
{center: {x: 20.0, y: 10.0}, radius: 5.0},
{center: {x: 20.0, y: 25.0}, radius: 7.5},
];
const q7_result1 = AreCirclesIntersecting(circles[0], circles[1]);
console.log(q7_result1); // Expected output: true
const q7_result2 = AreCirclesIntersecting(circles[0], circles[2]);
console.log(q7_result2); // Expected output: true
const q7_result3 = AreCirclesIntersecting(circles[1], circles[3]);
console.log(q7_result3); // Expected output: false
const q7_result4 = AreCirclesIntersecting(circles[2], circles[3]);
console.log(q7_result4); // Expected output: false
// draw the circles for debugging
(() => {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = '100';
document.body.append(canvas);
const context = canvas.getContext('2d');
for (const {center: {x,y},radius :r} of circles) {
context.beginPath();
context.arc(x,y,r,0, 2 * Math.PI);
context.stroke();
}
})()

Javascript natively offers a hypothenus function,
useful here to calculate the distance between 2 points on a 2 D system
const circles =
[ { center: { x: 10.0, y: 10.0 } , radius: 5.0 }
, { center: { x: 20.0, y: 20.0 } , radius: 15.0 }
, { center: { x: 20.0, y: 10.0 } , radius: 5.0 }
, { center: { x: 20.0, y: 25.0 } , radius: 7.5 }
]
function AreCirclesIntersecting(c0,c1)
{
let delta_x = Math.abs(c0.center.x - c1.center.x)
, delta_y = Math.abs(c0.center.y - c1.center.y)
, dist = Math.hypot(delta_x, delta_y)
, out = (dist > (c0.radius + c1.radius) )
, c0_in_c1 = !out && ( c1.radius > (c0.radius + dist ))
, c1_in_c0 = !out && ( c0.radius > (c1.radius + dist ))
;
return !(out || c0_in_c1 || c1_in_c0)
}
function testing (name, expected, indx_a, indx_b )
{
let test = AreCirclesIntersecting(circles[indx_a], circles[indx_b])
, divTest = document.createElement('div')
, c0 = circles[indx_a]
, c1 = circles[indx_b]
;
divTest.className = 'test'
divTest.innerHTML = `
<p>
<strong>${name}</strong> <br>
${JSON.stringify(circles[indx_a]) } green <br>
${JSON.stringify(circles[indx_b]) } red <br><br>
expected: ${expected}<br>result:<strong>${test}</strong><br>sucess: ${(expected===test)? '✅':'❌'}
</p>
<svg viewBox="0 0 50 40" xmlns="http://www.w3.org/2000/svg" width="200" heigth="160">
<circle cx="${c0.center.x}" cy="${c0.center.y}" r="${c0.radius}" fill="none" stroke="green"/>
<circle cx="${c1.center.x}" cy="${c1.center.y}" r="${c1.radius}" fill="none" stroke="red"/>
</svg>`
document.body.appendChild(divTest)
}
testing('q7_result1',true, 0,1)
testing('q7_result2',true, 0,2)
testing('q7_result3',false,1,3)
testing('q7_result4',false,2,3)
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
}
div.test {
display : inline-block;
margin : .5em;
border : 1px solid grey;
padding : 1em;
width : 400px;
}
div.test p { margin: .3em; }

You could do like this (check the interactive demo):
document.addEventListener('keydown', (function () {
function getCircleGeometry(circle) {
const {
top: circleTop,
left: circleLeft,
right: circleRight,
bottom: circleBottom,
} = circle.getBoundingClientRect();
return {
circleRadius: (circleBottom - circleTop) / 2,
circleCenterX: (circleLeft + circleRight) / 2,
circleCenterY: (circleTop + circleBottom) / 2
}
}
function collision(circle1, circle2) {
const {
circleRadius: circle1Radius,
circleCenterX: circle1CenterX,
circleCenterY: circle1CenterY
} = getCircleGeometry(circle1);
const {
circleRadius: circle2Radius,
circleCenterX: circle2CenterX,
circleCenterY: circle2CenterY
} = getCircleGeometry(circle2);
const deltaX = circle1CenterX - circle2CenterX;
const deltaY = circle1CenterY - circle2CenterY;
// Is the distance between the centers less than the sum of the radii
return deltaX ** 2 + deltaY ** 2 < (circle1Radius + circle2Radius) ** 2;
}
const circle1 = document.getElementById('circle1');
const circle2 = document.getElementById('circle2');
let circleTop = 26;
let circleLeft = 8;
return function (e) {
e.preventDefault();
switch(e.keyCode) {
case 37: // left
circleLeft -= 1;
break;
case 38: // up
circleTop -= 1;
break;
case 39: // right
circleLeft += 1;
break;
case 40: // down
circleTop += 1;
break;
}
circle1.style.top = `${circleTop}px`;
circle1.style.left = `${circleLeft}px`;
circle1.style['background-color'] = circle2.style['background-color'] = collision(circle1, circle2) ? 'red' : 'blue';
}
})());
.circle {
position: absolute;
border-radius: 50%;
height: 20px;
width: 20px;
}
<div>Use the keyboard arrows to move the circle</div>
<div class="circle" id="circle1" style="background-color: red;"></div>
<div class="circle" id="circle2" style="background-color: red;"></div>

This is a link only answer because there is a great answer to this question on the Stack Overflow Mathematics site.
In a comment to the answer on Mathematics, contributor Jared Updike posted a link to a gist he wrote for a JavaScript function intersectTwoCircles to provide the intersection points of two circles. Gist comments contain ports of the code into several other languages.
While the parameter formats of the intersectTwoCircles and the posted AreCirclesIntersecting functions differ slightly, they appear to be returning the same information.
According to a staff answer about the licensing of gists on Github:
If you set your pages and repositories to be viewed publicly, you grant each User of GitHub a nonexclusive, worldwide license to use, display, and perform Your Content through the GitHub Service and to reproduce Your Content solely on GitHub as permitted through GitHub’s functionality (for example, through forking).
which, along with the authors comment on Mathematica, seems to indicate while you are free to use it as you see fit, I am not licensed to display a copy of the code here.
As a matter of etiquette (and required under many open source software licenses) please provide attribution of the source of intersectionTwoCircles if you use it.

Related

Konva .to with "dynamic" coordinates

I'm creating a small game using Konva.
You control a single unit which can move around in a map and the "camera" is panned to it. I've achieved this effect by centering the screen on the x, y coordinates of the unit and then drawing everything else relative to that:
class GameWorld {
public entities = new Map();
public width = window.innerWidth;
public height = window.innerHeight;
public center = {
x: 0,
y: 0
};
tick(world: any) {
world.forEach((entity: any) => {
...
if (entity.id === user.id) {
// this is the user's unit
this.center.x = entity.x;
this.center.y = entity.y;
}
...
let dx = this.dx(entity.x);
let dy = this.dy(entity.y);
entity.shape.position({ x: dx, y: dy });
...
});
this.layer.draw();
}
dx(x: number) {
return x - this.center.x + this.width / 2;
}
dy(y: number) {
return y - this.center.y + this.height / 2;
}
}
Currently what I'm having trouble with is a simple blood splatter effect, which works by creating 5 "large" red circles and 15 small ones scattered at random directions and distances around a dying unit (not the player unit) at different speeds.
let bloodDot = (x: number, y: number, size: number) => {
let dx = this.dx(x);
let dy = this.dy(y);
let dot = new Konva.Circle({
x: dx,
y: dy,
radius: size,
fill: 'red',
});
this.layer.add(dot);
let dir = Math.random() * Math.PI * 2;
let dis = Math.random() * size * 5;
dot.to({
x: dx + Math.cos(dir) * dis,
y: dy + Math.sin(dir) * dis,
duration: Math.random() * 3,
easing: Konva.Easings.StrongEaseOut,
onFinish: () => dot.destroy()
});
}
for (let lg = 0; lg < 5; lg++) {
for (let sm = 0; sm < 3; sm++) {
bloodDot(entity.x, entity.y, entity.size / 6);
}
bloodDot(entity.x, entity.y, entity.size / 3);
}
The problem comes with using the .to() method. It all works well if the player is stationary, but if the player is moving, and thus everything else, including the blood, should be moving relative to them, the x and y used in the .to remain the ones that were at the time of creating the blood dots and it seems as if the blood is following the player or rather stuck on the same place on the screen, while everything else is relatively moving.
How can I dynamically change properties (coordinates) of the tween while Konva is still animating?
Well this is not a direct answer to the question asked but it is a solution to the problem so here it goes.
I did not find a way to modify a tween's properties while it's animating, but I found that I can group the dots, which will then animate relative to the group, and I'll have to draw the group on the derived x,y which is easy.
let bloodDot = (size: number) => {
let dot = new Konva.Circle({
x: 0,
y: 0,
radius: size,
fill: 'red',
});
let dir = Math.random() * Math.PI * 2;
let dis = Math.random() * size * 5;
g.add(dot);
dot.to({
x: Math.cos(dir) * dis,
y: Math.sin(dir) * dis,
duration: Math.random() * 3,
easing: Konva.Easings.StrongEaseOut,
onFinish: () => dot.destroy()
});
}
let g = new Konva.Group({
x: this.dx(entity.x),
y: this.dy(entity.y)
});
this.layer.add(g);
for (let lg = 0; lg < 5; lg++) {
for (let sm = 0; sm < 3; sm++) {
bloodDot(entity.size / 6);
}
bloodDot(entity.size / 3);
}
setTimeout(() => g.destroy(), 3000);

How to get random point near edges of a square in javascript

I want to make a function that gives me a random point near the edges of a rectangle from a point. This is what I came up with so far, but I have absolutely no idea why it is not working.
function Point(x, y) {
this.x = x;
this.y = y;
}
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomPointNearEdges(rectPos, width, height, border) {
var point = new Point(rectPos.x, rectPos.y);
if (randomNumber(0, 1) == 0) {
point.x = randomNumber(rectPos.x, rectPos.x + border);
if (randomNumber(0, 1) == 0) {
point.y = randomNumber(rectPos.y, rectPos.y + border);
}
else {
point.y = randomNumber(rectPos.y + height, (rectPos.y + height) + border);
}
}
else {
point.y = randomNumber(rectPos.y, rectPos.y + border);
if (randomNumber(0, 1) == 0) {
point.y = randomNumber(rectPos.x, rectPos.x + border);
}
else {
point.y = randomNumber(rectPos.x + height, (rectPos.x + width) + border);
}
}
return point;
};
window.onload = function() {
canvas = document.getElementById("canvas");
canvas.width = 700;
canvas.height = 700;
var ctx = canvas.getContext("2d");
ctx.strokeRect(130, 130, 500, 500);
for (var i = 0; i < 30; i++) {
var point = getRandomPointNearEdges(new Point(130, 130), 500, 500, 100);
ctx.fillRect(point.x, point.y, 2, 2);
}
};
<canvas id="canvas"></canvas>
Just to clarify, the black region in this 'Not to scale' diagram is where I want to allow the point to generate. The width / height of that black region is the border property in the code snippet.
Why is my function not working?
Random with even distribution.
Just to point out that the answer by SimpleJ is statistical flawed with the distribution of random locations having a bias to the corners and then to the shorter sides, even though they cover much less area.
The ideal random location should be spread equally over the area in question, if the height of the box is less than the width then there is less chance of the the sides getting a point.
The example below provides a much faster and a much better distribution. I have added the given answers solution as well so you can compare.
The function that gets a random pos. The arguments x,y top left inside edge of rectangle, w,h inside width and height of the rectangle minDist, maxDist the min and max dist the random point can be from the inside edge of the box. You can also use negative values have the points outside the rectangle. Note that the distances are always from the inside edge of the box. The values are also floored when return (can easily be remove and still works)
function randomPointNearRect(x, y, w, h, minDist, maxDist) {
const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
x += dist;
y += dist;
w -= dist * 2
h -= dist * 2
if (Math.random() < w / (w + h)) { // top bottom
x = Math.random() * w + x;
y = Math.random() < 0.5 ? y : y + h -1;
} else {
y = Math.random() * h + y;
x = Math.random() < 0.5 ? x: x + w -1;
}
return [x | 0, y | 0];
}
Note there is a slight bias to the inside of the box. It can be removed with a little calculus with the bias rate of change f'(x) = 8*x 8 pixels per pixel inward and the anti derivative f(x)=4*(x**2) + c would directly relate to the distribution. Where x is dist from edge and c is related to perimeter length
Example to compare
The example has two canvases. Many random points are drawn. click the top canvas to add more points. Note how the bottom canvas sides and corners get darker due to the bias of the random points.
const ctx = canvas.getContext("2d");
canvas.onclick = ()=>{
getRandomPointsForBox(200, box,4, 18);
getRandomPoints(200);
}
const edgeClear = 30;
var box = {
x: edgeClear,
y: edgeClear,
w: canvas.width - edgeClear * 2,
h: canvas.height - edgeClear * 2,
edge: 4,
}
function drawBox(box) {
ctx.fillRect(box.x, box.y, box.w, box.h);
ctx.clearRect(box.x + box.edge, box.y + box.edge, box.w - box.edge * 2, box.h - box.edge * 2);
}
function drawPixel(x, y) {
ctx.fillRect(x, y, 1, 1);
}
function getRandomPointsForBox(count, box, min, max) {
min += box.edge;
max += box.edge;
while (count--) {
const [x, y] = randomPointNearRect(box.x, box.y, box.w, box.h, min, max);
drawPixel(x, y);
}
}
drawBox(box);
getRandomPointsForBox(200, box,4, 18);
ctx.font = "18px arial"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText("Click to add more random points.",canvas.width / 2, canvas.height / 2);
function randomPointNearRect(x, y, w, h, minDist, maxDist) {
const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
x += dist;
y += dist;
w -= dist * 2
h -= dist * 2
if (Math.random() < w / (w + h)) { // top bottom
x = Math.random() * w + x;
y = Math.random() < 0.5 ? y : y + h -1;
} else {
y = Math.random() * h + y;
x = Math.random() < 0.5 ? x: x + w -1;
}
return [x | 0, y | 0];
}
/* The following is from the answer provided by SimpleJ https://stackoverflow.com/a/49581326/3877726 */
const ctx1 = canvas1.getContext('2d');
const rect = {
x: box.x, y: box.y,
width: box.w, height: box.h,
};
drawRect(rect);
ctx1.font = "18px arial"
ctx1.textAlign = "center"
ctx1.textBaseline = "middle"
ctx1.fillText("SimpleJ's method.",canvas1.width / 2, canvas1.height / 2);
ctx1.fillText("Note density of sides and corners.",canvas1.width / 2, canvas1.height / 2 + 20);
function getRandomPoints(count) {
while (count--) {
drawPoint(randomPointInRect(sample(rects)));
}
}
var rects = getBorderRects(rect, 10);
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
function drawRect({x, y, width, height}) {
ctx1.strokeRect(x, y, width, height);
}
function drawPoint({x, y}) {
ctx1.fillRect(x, y, 1,1);
}
getRandomPoints(200);
<canvas id="canvas" width="500" height="200"></canvas>
<canvas id="canvas1" width="500" height="200"></canvas>
If you think about the problem of getting a random point near an edge as getting a random point in one of four edge rectangles, this problem becomes much easier to break down:
Get edge rectangles.
Pick a random edge rectangle.
Generate a random point in the edge rectangle.
To generate edge rectangles, we need a max distance (how far from the edge can the point be?):
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
To pick a random rectangle from our array of edge rectangles, we can define a sample function:
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
Then to pick a random point in a rectangle, we just need some Math.random:
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
And putting everything together:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const rect = {
x: 10, y: 20,
width: 300, height: 200,
};
drawRect(rect);
drawPoint(
randomPointInRect(
sample(
getBorderRects(rect, 10)
)
)
);
function getBorderRects(rect, distance) {
const { x, y, width, height } = rect;
return [
{x: x, y: y, width: width, height: distance}, // top
{x: x, y: y + height - distance, width: width, height: distance}, // bottom
{x: x, y: y, width: distance, height: height}, // left
{x: x + width - distance, y: y, width: distance, height: height}, // right
];
}
function sample(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randomPointInRect({x, y, width, height}) {
return {
x: x + (Math.random() * width),
y: y + (Math.random() * height),
};
}
function drawRect({x, y, width, height}) {
context.strokeRect(x, y, width, height);
}
function drawPoint({x, y}) {
context.arc(x, y, 1, 0, Math.PI * 2);
context.fill();
}
<canvas width="500" height="500"/>
For anybody here like me, looking for a short, simple solution, this post is closest I found that is not talking trigonometry .. An while what I came up with might not directly be a solution to OPs problem, maybe someone will find this useful..
The approach is fairly simple.
Math.random() a number between 0 & 800. Make use of modulus and divide what's left by 200 to get a random side and axis point. Push the random side all the way, assign the random value to the other axis and yeah, that's about it .. here's an ex:
let rndm = Math.floor(Math.random()*800-1);
let offset = rndm % 200;
let side = (rndm - offset) / 200; // 0:top 1:right 2:btm 3:left
let y = side % 2 > 0 ? offset+1 : 100 * side ;
let x = side % 2 < 1 ? offset+1 : 100 * (side - 1) ;
point.y = y - 100;
point.x = x - 100;
In my case, I needed both negative and positive values with an origin point.
And if you want to spawn a point inside a border, just do another random number spanning the width of the border.
Just remember to adjust the corners.
offset += rndmBorder * 2; // creates an inward line in the corners
point.x = x - 100 + rndmBorder; // still keeping the origin point nice and center
_____________
|\_________/| <-// inward line
| | | |
| | | |
All I was in need for is to offset some letters .. and most of what I found seemed like overkill .. This actually works fairly well, hope it helps.

Calculate the mid point of latitude and longitude co-ordinates

Does anyone know the best way to go about getting the mid-point of a pair of latitude and longitude points?
I mm using d3.js to draw points on a map and need to draw a curved line between two points, so I need to create a mid point to draw a curve in the lines.
Please see image below for a better understanding of what I am trying to do:
Apologies for the long script - it just seemed fun to draw stuff :-). I've marked off sections that are not required
// your latitude / longitude
var co2 = [70, 48];
var co1 = [-70, -28];
// NOT REQUIRED
var ctx = document.getElementById("myChart").getContext("2d");
function drawPoint(color, point) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI, false);
ctx.fill();
}
function drawCircle(point, r) {
ctx.strokeStyle = 'gray';
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.arc(point.x, point.y, r, 0, 2 * Math.PI, false);
ctx.stroke();
}
// REQUIRED
// convert to cartesian
var projection = d3.geo.equirectangular()
var cot1 = projection(co1);
var cot2 = projection(co2);
var p0 = { x: cot1[0], y: cot1[1] };
var p1 = { x: cot2[0], y: cot2[1] };
// NOT REQUIRED
drawPoint('green', p0);
drawPoint('green', p1);
// REQUIRED
function dfn(p0, p1) {
return Math.pow(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2), 0.5);
}
// from http://math.stackexchange.com/a/87374
var d = dfn(p0, p1);
var m = {
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2,
}
var u = (p1.x - p0.x) / d
var v = (p1.y - p0.y) / d;
// increase 1, if you want a larger curvature
var r = d * 1;
var h = Math.pow(Math.pow(r, 2) - Math.pow(d, 2) / 4, 0.5);
// 2 possible centers
var c1 = {
x: m.x - h * v,
y: m.y + h * u
}
var c2 = {
x: m.x + h * v,
y: m.y - h * u
}
// NOT REQUIRED
drawPoint('gray', c1)
drawPoint('gray', c2)
drawCircle(c1, r)
drawCircle(c2, r)
// REQUIRED
// from http://math.stackexchange.com/a/919423
function mfn(p0, p1, c) {
// the -c1 is for moving the center to 0 and back again
var mt1 = {
x: r * (p0.x + p1.x - c.x * 2) / Math.pow(Math.pow(p0.x + p1.x - c.x * 2, 2) + Math.pow(p0.y + p1.y - c.y * 2, 2), 0.5)
};
mt1.y = (p0.y + p1.y - c.y * 2) / (p0.x + p1.x - c.x * 2) * mt1.x;
var ma = {
x: mt1.x + c.x,
y: mt1.y + c.y,
}
var mb = {
x: -mt1.x + c.x,
y: -mt1.y + c.y,
}
return (dfn(ma, p0) < dfn(mb, p0)) ? ma : mb;
}
var m1 = mfn(p0, p1, c1);
var m2 = mfn(p0, p1, c2);
var mo1 = projection.invert([m1.x, m1.y]);
var mo2 = projection.invert([m2.x, m2.y]);
// NOT REQUIRED
drawPoint('blue', m1);
drawPoint('blue', m2);
// your final output (in lat long)
console.log(mo1);
console.log(mo2);
Fiddle - https://jsfiddle.net/srjuc2gd/
And here's just the relevant portion (most of it is just copy-pasta from the beginning of this answer)
var Q31428016 = (function () {
// adjust curvature
var CURVATURE = 1;
// project to convert from lat / long to cartesian
var projection = d3.geo.equirectangular();
// distance between p0 and p1
function dfn(p0, p1) {
return Math.pow(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2), 0.5);
}
// mid point between p0 and p1
function cfn(p0, p1) {
return {
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2,
}
}
// get arc midpoint given end points, center and radius - http://math.stackexchange.com/a/919423
function mfn(p0, p1, c, r) {
var m = cfn(p0, p1);
// the -c1 is for moving the center to 0 and back again
var mt1 = {
x: r * (m.x - c.x) / Math.pow(Math.pow(m.x - c.x, 2) + Math.pow(m.y - c.y, 2), 0.5)
};
mt1.y = (m.y - c.y) / (m.x - c.x) * mt1.x;
var ma = {
x: mt1.x + c.x,
y: mt1.y + c.y,
}
var mb = {
x: -mt1.x + c.x,
y: -mt1.y + c.y,
}
return (dfn(ma, p0) < dfn(mb, p0)) ? ma : mb;
}
var Q31428016 = {};
Q31428016.convert = function (co1, co2) {
// convert to cartesian
var cot1 = projection(co1);
var cot2 = projection(co2);
var p0 = { x: cot1[0], y: cot1[1] };
var p1 = { x: cot2[0], y: cot2[1] };
// get center - http://math.stackexchange.com/a/87374
var d = dfn(p0, p1);
var m = cfn(p0, p1);
var u = (p1.x - p0.x) / d
var v = (p1.y - p0.y) / d;
var r = d * CURVATURE;
var h = Math.pow(Math.pow(r, 2) - Math.pow(d, 2) / 4, 0.5);
// 2 possible centers
var c1 = {
x: m.x - h * v,
y: m.y + h * u
}
var c2 = {
x: m.x + h * v,
y: m.y - h * u
}
// get arc midpoints
var m1 = mfn(p0, p1, c1, r);
var m2 = mfn(p0, p1, c2, r);
// convert back to lat / long
var mo1 = projection.invert([m1.x, m1.y]);
var mo2 = projection.invert([m2.x, m2.y]);
return [mo1, mo2]
}
return Q31428016;
})();
// your latitude / longitude
var co1 = [-70, -28];
var co2 = [70, 48];
var mo = Q31428016.convert(co1, co2)
// your output
console.log(mo[0]);
console.log(mo[1]);
For the accurate side:
You may use the Esri web API. Nothing beats decades of experience implementing the hard side of projection systems and datums... Athough the ArcGIS for Server line is a commercial product, the JS API is free, and here there's a pure JS function that does just what you want : geometryEngine.densify ; that function requires an interval parameter, that you can, in your case, get by dividing by two the results of geometryEngine.geodesicLength
That'll need you to get acquainted with the Polyline class in a very basic way, such as var mySegment = new Polyline([[50,3], [55,8]]); and probably nothing further.
For the visual side :
Your segment has two middles ? You may also be interested in geometryEngine.offset ; first offset the original segment once in each direction, then get the center point for each of the resulting segments.
For the practical side :
Given the short distances involved, provided you're not dealing with a weird place that'd be too close to the poles, I'd simply go with an arithmetic average of X and Y, and then add/subtract a rotated vector to offset your two "middles". Doing it this way would be both lighter on the machine (no libs to load from a CDN), easier on you, and as long as the purpose of it is a nice display, the result will be more than good enough.
(added as per comment : a sample)
// Your known starting points, and a k factor of your choice.
var a = {x:3, y:50};
var b = {x:8, y:55};
var k = 0.2;
// Intermediate values
var vab = {x:b.x-a.x, y:b.y-a.y};
var v_rotated = {x:-k*vab.y, y:k*vab.x};
var middle = {x:a.x+vab.x/2, y:a.y+vab.y/2};
// Your two resulting points
var result_i = {x: middle.x + v_rotated.x, y: middle.y + v_rotated.y};
var result_j = {x: middle.x - v_rotated.x, y: middle.y - v_rotated.y};
Check this question, you can use it to find the middle of your coordination on google map. I customized it to use with d3js.
Hope this help you.
In D3
function midpoint (lat1, lng1, lat2, lng2) {
lat1= deg2rad(lat1);
lng1= deg2rad(lng1);
lat2= deg2rad(lat2);
lng2= deg2rad(lng2);
dlng = lng2 - lng1;
Bx = Math.cos(lat2) * Math.cos(dlng);
By = Math.cos(lat2) * Math.sin(dlng);
lat3 = Math.atan2( Math.sin(lat1)+Math.sin(lat2),
Math.sqrt((Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) + By*By ));
lng3 = lng1 + Math.atan2(By, (Math.cos(lat1) + Bx));
return (lat3*180)/Math.PI .' '. (lng3*180)/Math.PI;
}
function deg2rad (degrees) {
return degrees * Math.PI / 180;
};
Update 1
If you try to draw curve line, you should create a path with coordination such as :
var lat1=53.507651,lng1=10.046997,lat2=52.234528,lng2=10.695190;
var svg=d3.select("body").append("svg").attr({width:700 , height:600});
svg.append("path").attr("d", function (d) {
var dC="M "+lat1+","+lng1+ " A " + midpoint (lat1, lng1, lat2, lng2)
+ " 0 1,0 " +lat2 +" , "+lng2 +"Z";
return dC;
})
.style("fill","none").style("stroke" ,"steelblue");
You need to create your curve line as you want in d3.
JsFiddle here.
I always use geo-lib
and works

Inverted circle radius calculation

I want to calculate the radius of an inverted circle.
I managed to implement everything but, after hours of struggle, I could not find a formula to calculate the correct inverted radius.
More info about circle inversion:
http://en.wikipedia.org/wiki/Inversive_geometry
https://www.youtube.com/watch?v=sG_6nlMZ8f4
My code so far: http://codepen.io/rafaelcastrocouto/pen/Mwjdga
It seems to be working but you can easily tell it's totally wrong.
var c = $('#c'),
b = $('body'),
canvas = c[0],
ctx = canvas.getContext('2d'),
pi = Math.PI,
r = 100,
mr = 30,
width, height, hw, hh;
var setup = function() {
width = b.width();
height = b.height();
hw = width/2;
hh = height/2;
canvas.width = width;
canvas.height = height;
mid();
};
var mid = function() {
circle(hw,hh,0.25);
circle(hw,hh,r);
}
var circle = function(x,y,r) {
ctx.beginPath();
ctx.arc(x,y,r,0,pi*2);
ctx.stroke();
ctx.closePath();
};
var move = function(evt) {
var x = evt.clientX,
y = evt.clientY;
ctx.clearRect(0,0,width,height);
mid();
circle(x,y,mr);
var dx = x-hw,
dy = y-hh,
d = dist(dx,dy),
nd = r*r/d,
nx = dx*nd/d,
ny = dy*nd/d,
nr = mr*mr*pi/d; // whats the correct formula?
console.log(nr);
circle(nx+hw, ny+hh, nr);
};
var dist = function(x,y) {
return Math.pow(x*x + y*y, 1/2);
};
$(setup);
$(window).resize(setup);
$(window).mousemove(move);
Need help from the math experts!
As you said, inverting the centre of a circle doesn't give you the centre of the other one. Likewise if we invert two oposite points of one circle, it doesn't mean they'll be opposing points on the inverted circle.
Since three points describe a unique circle we can use these to find the equation for the inverse circle. That gives us the centre of the inverse circle. We can then find the distance from the centre to one of the inverted points, that's the radius.
The following c++ code gives the centre. (I don't know javascript). The function v.norm2() gives the squared norm of the vector v.
Vector2D getcircle(Vector2D p1, Vector2D p2, Vector2D p3){
Vector2D result;
long double div = 2*(p1.x*(p2.y-p3.y)-p1.y*(p2.x-p3.x)+p2.x*p3.y-p3.x*p2.y);
result.x = (p1.norm2()*(p2.y-p3.y)+p2.norm2()*(p3.y-p1.y)+p3.norm2()*(p1.y-p2.y))/div;
result.y = (p1.norm2()*(p3.x-p2.x)+p2.norm2()*(p1.x-p3.x)+p3.norm2()*(p2.x-p1.x))/div;
return result;
}
So if you have a circle c of radius r, and you are inverting respect to another circle C and radius R, you could do something like
float getRadius(Vector2D C, float R, Vector2D c, float r){
Vector2D p1 = Vector2D(c.x + r, c.y).invert(C, R);
Vector2D p2 = Vector2D(c.x - r, c.y).invert(C, R);
Vector2D p3 = Vector2D(c.x, c.y + r).invert(C, R);
return (getcircle(p1, p2, p3) - p1).norm();
}
Here is an image of a circle with centre (130, -130) and radius 128, and it's inversion respect to another circle (not shown) of centre (0, 0) and radius 40.
The red points on the big circle are polar opposites. They are then inverted and shown on the little circle where you can see they are not polar opposites.
My error was that I was assuming that the center of the inverted circle also respected OP x OP' = r2, but as the image below shows, it clearly does not. The solution was to calculate two points on the circle and reflect each one, then use half the distance between this points to find the radius.
So this is the correct code:
var c = $('#c'),
b = $('body'),
canvas = c[0],
ctx = canvas.getContext('2d'),
fixedRadius = 100,
saved = [],
width, height,
half = {
w: 0,
h: 0
},
mouse = {
r: 31,
x: 0,
y: 0
},
reflect = {
x: 0,
y: 0,
r: 0
};
var setup = function() {
width = b.width();
height = b.height();
half.w = width/2;
half.h = height/2;
canvas.width = width;
canvas.height = height;
move();
};
var mid = function() {
circle(half.w,half.h,1.5);
circle(half.w,half.h,fixedRadius);
};
var circle = function(x,y,r,c) {
ctx.strokeStyle = c || 'black';
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI*2);
ctx.stroke();
ctx.closePath();
};
var line = function(x1,y1,x2,y2,c) {
ctx.strokeStyle = c || 'black';
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.closePath();
};
var axis = function () {
line(half.w,0,half.w,height,'#ccc');
line(0,half.h,width,half.h,'#ccc');
};
var move = function(evt) {
mouse.x = evt ? evt.clientX : half.w;
mouse.y = evt ? evt.clientY : half.h + 11;
ctx.clearRect(0,0,width,height);
axis();
mid();
circle(mouse.x,mouse.y,mouse.r);
circle(mouse.x,mouse.y,1,'grey');
var di = {
x: mouse.x - half.w, // orange
y: mouse.y - half.h // green
}
di.v = dist(di.x,di.y);
var a = Math.atan2(di.y,di.x); // angle
line(mouse.x - di.x,mouse.y,mouse.x,mouse.y,'orange');
line(mouse.x,mouse.y - di.y,mouse.x,mouse.y,'green');
var p1 = {
v: di.v + mouse.r // cyan
};
p1.x = half.w + (Math.cos(a) * p1.v);
p1.y = half.h + (Math.sin(a) * p1.v);
circle(p1.x,p1.y,1.5,'cyan');
var p2 = {
v: di.v - mouse.r // red
};
p2.x = half.w+Math.cos(a)*p2.v;
p2.y = half.h+Math.sin(a)*p2.v;
circle(p2.x,p2.y,1.5,'red');
var rp1 = {
v: Math.pow(fixedRadius,2) / p1.v // cyan
};
rp1.x = Math.cos(a) * rp1.v,
rp1.y = Math.sin(a) * rp1.v;
circle(rp1.x+half.w,rp1.y+half.h,1.5,'cyan');
var rp2 = {
v: Math.pow(fixedRadius,2) / p2.v // red
};
rp2.x = Math.cos(a) * rp2.v,
rp2.y = Math.sin(a) * rp2.v;
circle(rp2.x+half.w,rp2.y+half.h,1.5,'red');
var newDi = {
v: dist(rp1.x - rp2.x, rp1.y - rp2.y)
};
newDi.r = newDi.v/2,
newDi.x = rp1.x + (Math.cos(a) * newDi.r), // yellow
newDi.y = rp1.y + (Math.sin(a) * newDi.r); // purple
if (p2.v < 0) {
newDi.x = rp1.x - (Math.cos(a) * newDi.r),
newDi.y = rp1.y - (Math.sin(a) * newDi.r);
}
reflect.x = half.w+newDi.x;
reflect.y = half.h+newDi.y
// reflected lines
if (di.v<fixedRadius) line(rp1.x+half.w,rp1.y+half.h,p1.x,p1.y,'cyan');
else line(rp2.x+half.w,rp2.y+half.h,p2.x,p2.y,'red');
line(p1.x,p1.y,half.w,half.h,'#ccc');
line(rp2.x+half.w,rp2.y+half.h,half.w,half.h,'#ccc');
line(reflect.x-newDi.x,reflect.y,reflect.x,reflect.y,'yellow');
line(reflect.x,reflect.y-newDi.y,reflect.x,reflect.y,'purple');
// reflected circle
circle(reflect.x, reflect.y, newDi.r);
circle(reflect.x,reflect.y,1,'grey');
circles(); // saved circles
reflect.r = newDi.r;
};
var dist = function(x,y) {
return Math.pow(x*x + y*y, 1/2);
};
var scroll = function(evt) {
if(evt.originalEvent.wheelDelta > 0) {
mouse.r++;
} else {
mouse.r--;
}
move(evt);
};
var click = function(evt) {
saved.push(['c',mouse.x,mouse.y,mouse.r]);
saved.push(['c',reflect.x,reflect.y,reflect.r]);
saved.push(['l',mouse.x,mouse.y,reflect.x,reflect.y]);
};
var circles = function() {
for(var i = 0; i < saved.length; i++) {
var s = saved[i];
if (s[0]=='c') circle(s[1],s[2],s[3],'grey');
if (s[0]=='l') line(s[1],s[2],s[3],s[4],'grey');
}
};
$(setup);
$(window)
.on('resize', setup)
.on('mousemove', move)
.on('mousewheel', scroll)
.on('click', click);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c"></canvas>

How to constrain movement within the area of a circle

This might be more a geometry related question, but I'm trying to constrain a controller within an area of a circle. I know I have to touch the Math.sin() and Math.cos() methods, but my attemps so far have been fruitless so far.
Here is the jsfiddle:
So far I've been able to constrain it to an invisible square. http://jsfiddle.net/maGVK/
So I finally was able to complete this with a bit of everyone's help.
var pointerEl = document.getElementById("pointer");
var canvasEl = document.getElementById("canvas");
var canvas = {
width: canvasEl.offsetWidth,
height: canvasEl.offsetHeight,
top: canvasEl.offsetTop,
left: canvasEl.offsetLeft
};
canvas.center = [canvas.left + canvas.width / 2, canvas.top + canvas.height / 2];
canvas.radius = canvas.width / 2;
window.onmousemove = function(e) {
var result = limit(e.x, e.y);
pointer.style.left = result.x + "px";
pointer.style.top = result.y + "px";
}
function limit(x, y) {
var dist = distance([x, y], canvas.center);
if (dist <= canvas.radius) {
return {x: x, y: y};
}
else {
x = x - canvas.center[0];
y = y - canvas.center[1];
var radians = Math.atan2(y, x)
return {
x: Math.cos(radians) * canvas.radius + canvas.center[0],
y: Math.sin(radians) * canvas.radius + canvas.center[1]
}
}
}
function distance(dot1, dot2) {
var x1 = dot1[0],
y1 = dot1[1],
x2 = dot2[0],
y2 = dot2[1];
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
You can see the result here:
http://jsfiddle.net/7Asn6/
var pointerEl = document.getElementById("pointer");
var canvasEl = document.getElementById("canvas");
var canvas = {
width: canvasEl.offsetWidth,
height: canvasEl.offsetHeight,
top: canvasEl.offsetTop,
left: canvasEl.offsetLeft
};
canvas.center = [canvas.left + canvas.width / 2, canvas.top + canvas.height / 2];
canvas.radius = canvas.width / 2;
window.onmousemove = function(e) {
var result = limit(e.x, e.y);
if (!result.limit) {
pointer.style.left = result.x + "px";
pointer.style.top = result.y + "px";
}
}
function limit(x, y) {
var dist = distance([x, y], canvas.center);
if (dist <= canvas.radius) {
return {x: x, y: y};
} else {
return {limit: true};
}
}
function distance(dot1, dot2) {
var x1 = dot1[0],
y1 = dot1[1],
x2 = dot2[0],
y2 = dot2[1];
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
this could do the work, though the movement is not smooth....that will need more geometry knowledge...
fiddle: http://jsfiddle.net/cRxMa/
This arithmetic is trivial as long as you normalize each data point (prospective position), which i have tried to do in the function below:
function locatePoint(canvas_size, next_position) {
// canvas_size & next_position are both 2-element arrays
// (w, h) & (x, y)
dist = function(x, y) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
};
x = next_position[0];
y = next_position[1];
rescaledX = x/(canvas_size[0]/2);
rescaledY = y/(canvas_size[1]/2);
if (distance(x, y) <= 1) {
// the base case; position is w/in the circle
}
else {
// position is outside the circle, so perhaps
// do something like random select a new position, then
// call this function again (recursively) passing in
// that new position
}
}
so in the simple diagram below, i have just inscribed a unit circle (r=1) inside a square whose sides are r*2. Your canvas dimensions do not have to be square though. To further simplify the calculation, you only need to consider one of the four quadrants--the upper right quadrant, let's say. The reason is that the Euclidean distance formula squares each coordinate value, so negative values become positive.
Put another way, the simplest way is to imagine a circle inscribed in your canvas and whose center is also the center of your canvas (so (0, 0) is the center not the upper left-hand corner); next, both canvas and circle are shrunk until the circle has radius = 1. Hopefully i have captured this in the function above.
Hi and thanks for sharing your solution.
Your jsfiddle helps me a lot to constraint the movement of a rotation handle.
Here's my solution using jQuery :
function getBall(xVal, yVal, dxVal, dyVal, rVal, colorVal) {
var ball = {
x: xVal,
lastX: xVal,
y: yVal,
lastY: yVal,
dx: dxVal,
dy: dyVal,
r: rVal,
color: colorVal,
normX: 0,
normY: 0
};
return ball;
}
var canvas = document.getElementById("myCanvas");
var xLabel = document.getElementById("x");
var yLabel = document.getElementById("y");
var dxLabel = document.getElementById("dx");
var dyLabel = document.getElementById("dy");
var ctx = canvas.getContext("2d");
var containerR = 200;
canvas.width = containerR * 2;
canvas.height = containerR * 2;
canvas.style["border-radius"] = containerR + "px";
var balls = [
getBall(containerR, containerR * 2 - 30, 2, -2, 20, "#0095DD"),
getBall(containerR, containerR * 2 - 50, 3, -3, 30, "#DD9500"),
getBall(containerR, containerR * 2 - 60, -3, 4, 10, "#00DD95"),
getBall(containerR, containerR * 2 / 5, -1.5, 3, 40, "#DD0095")
];
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < balls.length; i++) {
var curBall = balls[i];
ctx.beginPath();
ctx.arc(curBall.x, curBall.y, curBall.r, 0, Math.PI * 2);
ctx.fillStyle = curBall.color;
ctx.fill();
ctx.closePath();
curBall.lastX = curBall.x;
curBall.lastY = curBall.y;
curBall.x += curBall.dx;
curBall.y += curBall.dy;
var dx = curBall.x - containerR;
var dy = curBall.y - containerR;
var distanceFromCenter = Math.sqrt(dx * dx + dy * dy);
if (distanceFromCenter >= containerR - curBall.r) {
var normalMagnitude = distanceFromCenter;
var normalX = dx / normalMagnitude;
var normalY = dy / normalMagnitude;
var tangentX = -normalY;
var tangentY = normalX;
var normalSpeed = -(normalX * curBall.dx + normalY * curBall.dy);
var tangentSpeed = tangentX * curBall.dx + tangentY * curBall.dy;
curBall.dx = normalSpeed * normalX + tangentSpeed * tangentX;
curBall.dy = normalSpeed * normalY + tangentSpeed * tangentY;
}
xLabel.innerText = "x: " + curBall.x;
yLabel.innerText = "y: " + curBall.y;
dxLabel.innerText = "dx: " + curBall.dx;
dyLabel.innerText = "dy: " + curBall.dy;
}
requestAnimationFrame(draw);
}
draw();
canvas { background: #eee; }
<div id="x"></div>
<div id="y"></div>
<div id="dx"></div>
<div id="dy"></div>
<canvas id="myCanvas"></canvas>
Hope this help someone.

Categories