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.
Related
I am making a drawing application. I have created a class Polygon. Its constructor will receive three arguments and these will be its properties:
points(Number): Number of points the polygon will have.
rotation(Number): The angle the whole polygon will be rotated.
angles(Array Of number): The angles between two lines of the polygon.
I have been trying for the whole day, but I couldn't figure out the correct solution.
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
let isMouseDown = false;
let tool = 'polygon';
let savedImageData;
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
const mouse = {x:null,y:null}
let mousedown = {x:null,y:null}
const toDegree = val => val * 180 / Math.PI
class Polygon {
constructor(points, rotation, angles){
this.points = points;
this.rotation = rotation;
//if angles are given then convert them to radian
if(angles){
this.angles = angles.map(x => x * Math.PI/ 180);
}
//if angles array is not given
else{
/*get the angle for a regular polygon for given points.
3-points => 60
4-points => 90
5-points => 108
*/
let angle = (this.points - 2) * Math.PI/ this.points;
//fill the angles array with the same angle
this.angles = Array(points).fill(angle)
}
let sum = 0;
this.angles = this.angles.map(x => {
sum += x;
return sum;
})
}
draw(startx, starty, endx, endy){
c.beginPath();
let rx = (endx - startx) / 2;
let ry = (endy - starty) / 2;
let r = Math.max(rx, ry)
c.font = '35px cursive'
let cx = startx + r;
let cy = starty + r;
c.fillRect(cx - 2, cy - 2, 4, 4); //marking the center
c.moveTo(cx + r, cy);
c.strokeText(0, cx + r, cy);
for(let i = 1; i < this.points; i++){
//console.log(this.angles[i])
let dx = cx + r * Math.cos(this.angles[i] + this.rotation);
let dy = cy + r * Math.sin(this.angles[i] + this.rotation);
c.strokeStyle = 'red';
c.strokeText(i, dx, dy, 100);
c.strokeStyle ='black';
c.lineTo(dx, dy);
}
c.closePath();
c.stroke();
}
}
//update();
c.beginPath();
c.lineWidth = 1;
document.addEventListener('mousemove', function(e){
//Getting the mouse coords according to canvas
const canvasData = canvas.getBoundingClientRect();
mouse.x = (e.x - canvasData.left) * (canvas.width / canvasData.width);
mouse.y = (e.y - canvasData.top) * (canvas.height / canvasData.height);
if(tool === 'polygon' && isMouseDown){
drawImageData();
let pol = new Polygon(5, 0);
pol.draw(mousedown.x, mousedown.y, mouse.x, mouse.y);
}
})
function saveImageData(){
savedImageData = c.getImageData(0, 0, canvas.width, canvas.height);
}
function drawImageData(){
c.putImageData(savedImageData, 0, 0)
}
document.addEventListener('mousedown', () => {
isMouseDown = true;
mousedown = {...mouse};
if(tool === 'polygon'){
saveImageData();
}
});
document.addEventListener('mouseup', () => isMouseDown = false);
<canvas></canvas>
In the above code I am trying to make a pentagon but it doesn't work.
Unit polygon
The following snippet contains a function polygonFromSidesOrAngles that returns the set of points defining a unit polygon as defined by the input arguments. sides, or angles
Both arguments are optional but must have one argument
If only sides given then angles are calculated to make the complete polygon with all side lengths equal
If only angles given then the number of sides is assumed to be the number of angles. Angles are in degrees 0-360
If the arguments can not define a polygon then there are several exceptions throw.
The return is a set of points on a unit circle that define the points of the polygon. The first point is at coordinate {x : 1, y: 0} from the origin.
The returned points are not rotated as that is assumed to be a function of the rendering function.
All points on the polygon are 1 unit distance from the origin (0,0)
Points are in the form of an object containing x and y properties as defined by the function point and polarPoint
Method used
I did not lookup an algorithm, rather I worked it out from the assumption that a line from (1,0) on the unit circle at the desired angle will intercept the circle at the correct distance from (1,0). The intercept point is used to calculate the angle in radians from the origin. That angle is then used to calculate the ratio of the total angles that angle represents.
The function that does this is calcRatioOfAngle(angle, sides) returning the angle as a ratio (0-1) of Math.PI * 2
It is a rather long handed method and likely can be significantly reduced
As it is unclear in your question what should be done with invalid arguments the function will throw a range error if it can not proceed.
Polygon function
Math.PI2 = Math.PI * 2;
Math.TAU = Math.PI2;
Math.deg2Rad = Math.PI / 180;
const point = (x, y) => ({x, y});
const polarPoint = (ang, dist) => ({x: Math.cos(ang) * dist, y: Math.sin(ang) * dist});
function polygonFromSidesOrAngles(sides, angles) {
function calcRatioOfAngle(ang, sides) {
const v1 = point(Math.cos(ang) - 1, Math.sin(ang));
const len2 = v1.x * v1.x + v1.y * v1.y;
const u = -v1.x / len2;
const v2 = point(v1.x * u + 1, v1.y * u);
const d = (1 - (v2.y * v2.y + v2.x * v2.x)) ** 0.5 / (len2 ** 0.5);
return Math.atan2(v2.y + v1.y * d, v2.x + 1 + v1.x * d) / (Math.PI * (sides - 2) / 2);
}
const vetAngles = angles => angles.reduce((sum, ang) => sum += ang, 0) === (angles.length - 2) * 180;
var ratios = [];
if(angles === undefined) {
if (sides < 3) { throw new RangeError("Polygon must have more than 2 side") }
const rat = 1 / sides;
while (sides--) { ratios.push(rat) }
} else {
if (sides === undefined) { sides = angles.length }
else if (sides !== angles.length) { throw new RangeError("Numbers of sides does not match number of angles") }
if (sides < 3) { throw new RangeError("Polygon must have more than 2 side") }
if (!vetAngles(angles)) { throw new RangeError("Set of angles can not create a "+sides+" sided polygon") }
ratios = angles.map(ang => calcRatioOfAngle(ang * Math.deg2Rad, sides));
ratios.unshift(ratios.pop()); // rotate right to get first angle at start
}
var ang = 0;
const points = [];
for (const rat of ratios) {
ang += rat;
points.push(polarPoint(ang * Math.TAU, 1));
}
return points;
}
Render function
Function to render the polygon. It includes the rotation so you don't need to create a separate set of points for each angle you want to render the polygon at.
The radius is the distance from the center point x,y to any of the polygons vertices.
function drawPolygon(ctx, poly, x, y, radius, rotate) {
ctx.setTransform(radius, 0, 0, radius, x, y);
ctx.rotate(rotate);
ctx.beginPath();
for(const p of poly.points) { ctx.lineTo(p.x, p.y) }
ctx.closePath();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
Example
The following renders a set of test polygons to ensure that the code is working as expected.
Polygons are rotated to start at the top and then rendered clock wise.
The example has had the vetting of input arguments removed.
const ctx = can.getContext("2d");
can.height = can.width = 512;
Math.PI2 = Math.PI * 2;
Math.TAU = Math.PI2;
Math.deg2Rad = Math.PI / 180;
const point = (x, y) => ({x, y});
const polarPoint = (ang, dist) => ({x: Math.cos(ang) * dist, y: Math.sin(ang) * dist});
function polygonFromAngles(sides, angles) {
function calcRatioOfAngle(ang, sides) {
const x = Math.cos(ang) - 1, y = Math.sin(ang);
const len2 = x * x + y * y;
const u = -x / len2;
const x1 = x * u + 1, y1 = y * u;
const d = (1 - (y1 * y1 + x1 * x1)) ** 0.5 / (len2 ** 0.5);
return Math.atan2(y1 + y * d, x1 + 1 + x * d) / (Math.PI * (sides - 2) / 2);
}
var ratios = [];
if (angles === undefined) {
const rat = 1 / sides;
while (sides--) { ratios.push(rat) }
} else {
ratios = angles.map(ang => calcRatioOfAngle(ang * Math.deg2Rad, angles.length));
ratios.unshift(ratios.pop());
}
var ang = 0;
const points = [];
for(const rat of ratios) {
ang += rat;
points.push(polarPoint(ang * Math.TAU, 1));
}
return points;
}
function drawPolygon(poly, x, y, radius, rot) {
const xdx = Math.cos(rot) * radius;
const xdy = Math.sin(rot) * radius;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y);
ctx.beginPath();
for (const p of poly) { ctx.lineTo(p.x, p.y) }
ctx.closePath();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
const segs = 4;
const tests = [
[3], [, [45, 90, 45]], [, [90, 10, 80]], [, [60, 50, 70]], [, [40, 90, 50]],
[4], [, [90, 90, 90, 90]], [, [90, 60, 90, 120]],
[5], [, [108, 108, 108, 108, 108]], [, [58, 100, 166, 100, 116]],
[6], [, [120, 120, 120, 120, 120, 120]], [, [140, 100, 180, 100, 100, 100]],
[7], [8],
];
var angOffset = -Math.PI / 2; // rotation of poly
const w = ctx.canvas.width;
const h = ctx.canvas.height;
const wStep = w / segs;
const hStep = h / segs;
const radius = Math.min(w / segs, h / segs) / 2.2;
var x,y, idx = 0;
for (y = 0; y < segs && idx < tests.length; y ++) {
for (x = 0; x < segs && idx < tests.length; x ++) {
drawPolygon(polygonFromAngles(...tests[idx++]), (x + 0.5) * wStep , (y + 0.5) * hStep, radius, angOffset);
}
}
canvas {
border: 1px solid black;
}
<canvas id="can"></canvas>
I do just a few modification.
Constructor take angles on degree
When map angles to radian complement 180 because canvas use angles like counterclockwise. We wan to be clockwise
First point start using the passed rotation
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
let isMouseDown = false;
let tool = 'polygon';
let savedImageData;
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
const mouse = {x:null,y:null}
let mousedown = {x:null,y:null}
const toDegree = val => val * 180 / Math.PI;
const toRadian = val => val * Math.PI / 180;
class Polygon {
constructor(points, rotation, angles){
this.points = points;
this.rotation = toRadian(rotation);
//if angles array is not given
if(!angles){
/*get the angle for a regular polygon for given points.
3-points => 60
4-points => 90
5-points => 108
*/
let angle = (this.points - 2) * 180 / this.points;
//fill the angles array with the same angle
angles = Array(points).fill(angle);
}
this.angles = angles;
let sum = 0;
console.clear();
// To radians
this.angles = this.angles.map(x => {
x = 180 - x;
x = toRadian(x);
return x;
})
}
draw(startx, starty, endx, endy){
c.beginPath();
let rx = (endx - startx) / 2;
let ry = (endy - starty) / 2;
let r = Math.max(rx, ry)
c.font = '35px cursive'
let cx = startx + r;
let cy = starty + r;
c.fillRect(cx - 2, cy - 2, 4, 4); //marking the center
c.moveTo(cx + r, cy);
let sumAngle = 0;
let dx = cx + r * Math.cos(this.rotation);
let dy = cy + r * Math.sin(this.rotation);
c.moveTo(dx, dy);
for(let i = 0; i < this.points; i++){
sumAngle += this.angles[i];
dx = dx + r * Math.cos((sumAngle + this.rotation));
dy = dy + r * Math.sin((sumAngle + this.rotation));
c.strokeStyle = 'red';
c.strokeText(i, dx, dy, 100);
c.strokeStyle ='black';
c.lineTo(dx, dy);
}
c.closePath();
c.stroke();
}
}
//update();
c.beginPath();
c.lineWidth = 1;
document.addEventListener('mousemove', function(e){
//Getting the mouse coords according to canvas
const canvasData = canvas.getBoundingClientRect();
mouse.x = (e.x - canvasData.left) * (canvas.width / canvasData.width);
mouse.y = (e.y - canvasData.top) * (canvas.height / canvasData.height);
if(tool === 'polygon' && isMouseDown){
drawImageData();
let elRotation = document.getElementById("elRotation").value;
let rotation = elRotation.length == 0 ? 0 : parseInt(elRotation);
let elPoints = document.getElementById("elPoints").value;
let points = elPoints.length == 0 ? 3 : parseInt(elPoints);
let elAngles = document.getElementById("elAngles").value;
let angles = elAngles.length == 0 ? null : JSON.parse(elAngles);
let pol = new Polygon(points, rotation, angles);
pol.draw(mousedown.x, mousedown.y, mouse.x, mouse.y);
}
})
function saveImageData(){
savedImageData = c.getImageData(0, 0, canvas.width, canvas.height);
}
function drawImageData(){
c.putImageData(savedImageData, 0, 0)
}
document.addEventListener('mousedown', () => {
isMouseDown = true;
mousedown = {...mouse};
if(tool === 'polygon'){
saveImageData();
}
});
document.addEventListener('mouseup', () => isMouseDown = false);
<!DOCTYPE html>
<html lang="en">
<body>
Points: <input id="elPoints" style="width:30px" type="text" value="3" />
Rotation: <input id="elRotation" style="width:30px" type="text" value="0" />
Angles: <input id="elAngles" style="width:100px" type="text" value="[45, 45, 90]" />
<canvas></canvas>
</body>
</html>
I'm working on a certain layout where I need to draw a hexagon which needs to be clickable. I'm using the Path2D construct and isPointInPath function. I'm constructing an animation where a number of hexagons is created and then each moved to a certain position. After the movement is done, I am attaching onclick event handlers to certain hexagons. However there is weird behaviour.
Some initialized variables
const COLOR_DARK = "#73b6c6";
const COLOR_LIGHT = "#c3dadd";
const COLOR_PRIMARY = "#39a4c9";
const TYPE_PRIMARY = 'primary';
let hexagons = [];
Below is the function which draws the hexagons.
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
This function populates the hexagon array
function populateLeftHex(canvasWidth, canvasHeight, hexProps) {
const startX = canvasWidth / 2;
const startY = canvasHeight / 2;
const baseLeft = canvasWidth * 0.05;
for(let i = 0; i < 5; i++){
let hexNumber = (i % 4 == 0)? 2: 1;
for(let j = 0; j < hexNumber; j++){
hexagons.push({
startX: startX,
startY: startY,
endX: baseLeft + (2 * j) + ((i % 2 == 0)? (hexProps.width * j) : (hexProps.width/2)),
endY: ((i + 1) * hexProps.height) - ((i) * hexProps.height * hexProps.facShort) + (i* 2),
stroke: true,
color: ( i % 2 == 0 && j % 2 == 0)? COLOR_DARK : COLOR_LIGHT,
type: TYPE_PRIMARY
});
}
}
}
And here is where Im calling the isPointInPath function.
window.onload = function (){
const c = document.getElementById('canvas');
const canvasWidth = c.width = window.innerWidth,
canvasHeight = c.height = window.innerHeight,
ctx = c.getContext('2d');
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
console.log(canvasWidth);
let hexProps = {
width: canvasWidth * 0.075,
get height () {
return this.width/Math.sqrt(3) + (1.5)*(this.width/Math.sqrt(2)/2);
} ,
facShort: 0.225,
get facLong () {
return 1 - this.facShort;
}
};
populateLeftHex(canvasWidth, canvasHeight, hexProps);
let pct = 0;
const fps = 200;
animate();
function animate () {
setTimeout(function () {
// increment pct towards 100%
pct += .03;
// if we're not done, request another animation frame
if (pct < 1.00) {
requestAnimFrame(animate);
} else { //if pct is no longer less than 1.00, then the movement animation is over.
hexagons.forEach(function (hex) {
if(hex.type === TYPE_PRIMARY) {
console.info(hex.path);
c.onclick = function(e) {
let x = e.clientX - c.offsetLeft,
y = e.clientY - c.offsetTop;
console.info(ctx.isPointInPath(hex.path, (e.clientX - c.offsetLeft), (e.clientY - c.offsetTop) ));
};
}
})
}
ctx.clearRect(0, 0, c.width, c.height);
// draw all hexagons
for ( let i = 0; i < hexagons.length; i++) {
// get reference to next shape
let hex = hexagons[i];
// note: dx/dy are fixed values
// they could be put in the shape object for efficiency
let dx = hex.endX - hex.startX;
let dy = hex.endY - hex.startY;
let nextX = hex.startX + dx * pct;
let nextY = hex.startY + dy * pct;
hex = hexagons[i];
ctx.fillStyle = hex.color;
hex.path = drawHex(ctx, nextX, nextY, hexProps, hex.stroke, hex.color);
}
}, 1000 / fps);
}
Can you help me figure out what I'm doing wrong? Maybe I misunderstood how Path2D works? Thanks in advance.
Had to do a bit of work to build a test page as your example is incomplete, but this is working for me - though my hexagon is concave...
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var hexProps = {width:100, height:100, facShort:-2, facLong:10};
var hexagons = [];
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
hexagons.push({type:0, path:drawHex(ctx,100,100,hexProps,false,"#0f0")});
hexagons.forEach(function (hex) {
if(hex.type === 0) {
console.info(hex.path);
myCanvas.onclick = function(e) {
let x = e.clientX - myCanvas.offsetLeft,
y = e.clientY - myCanvas.offsetTop;
console.info(x,y);
console.info(ctx.isPointInPath(hex.path, (e.clientX -
myCanvas.offsetLeft), (e.clientY - myCanvas.offsetTop) ));
};
}
})
<canvas width=500 height=500 id=myCanvas style='border:1px solid red'></canvas>
Test clicks give true and false where expected:
test.htm:48 165 168
test.htm:49 true
test.htm:48 151 336
test.htm:49 false
test.htm:48 124 314
test.htm:49 true
test.htm:48 87 311
test.htm:49 false
I need to draw a circle and i have only two points.Now i need to find center point and radius of the circle? You can form the circle in clock wise direction.
Thanks in advance
Here is a Brute Force approach to the problem.
EDIT
Added a max iterations limit to cut off calculations if the line between the two points is almost straight along x (meaning a radius would be nearing Infinity)
Also animations, because that makes everything better :)
var canvas = document.body.appendChild(document.createElement("canvas"));
var ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 1000;
var points = [
{ x: parseInt(prompt("x1", "110")), y: parseInt(prompt("y1", "120")), r: 5 },
{ x: parseInt(prompt("x2", "110")), y: parseInt(prompt("y2", "60")), r: 5 },
];
function calculateRemainingPoint(points, x, precision, maxIteration) {
if (x === void 0) { x = 0; }
if (precision === void 0) { precision = 0.001; }
if (maxIteration === void 0) { maxIteration = 100000; }
var newPoint = {
x: x,
y: (points[0].y + points[1].y) / 2,
r: 50
};
var d0 = distance(points[0].x, points[0].y, x, newPoint.y);
var d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var iteration = 0;
//Bruteforce approach
while (Math.abs(d0 - d1) > precision && iteration < maxIteration) {
var oldDiff = Math.abs(d0 - d1);
var oldY = newPoint.y;
iteration++;
newPoint.y += oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var diff_1 = Math.abs(d0 - d1);
if (diff_1 > oldDiff) {
newPoint.y = oldY - oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
}
}
var diff = (points[0].x + points[1].x) / points[0].x;
newPoint.r = d0;
return newPoint;
}
points.push(calculateRemainingPoint(points));
function distance(x1, y1, x2, y2) {
var a = x1 - x2;
var b = y1 - y2;
return Math.sqrt(a * a + b * b);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(-canvas.width, canvas.height / 2);
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(canvas.width / 2, -canvas.height);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.stroke();
ctx.closePath();
for (var pointIndex = 0; pointIndex < points.length; pointIndex++) {
var point = points[pointIndex];
ctx.beginPath();
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, point.r, 0, Math.PI * 2);
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, 2, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
}
setInterval(function () {
points = points.slice(0, 2);
points[Math.floor(Math.random() * points.length) % points.length][Math.random() > 0.5 ? 'x' : 'y'] = Math.random() * canvas.width - canvas.width / 2;
setTimeout(function () {
points.push(calculateRemainingPoint(points));
requestAnimationFrame(draw);
}, 1000 / 60);
}, 1000);
draw();
No that is impossible.
Create two circles with the same radius at centerpoints A + B. At the intersection of these two circles create an circle with the same radius....
Then make the same with an other radius....
I have been creating a clone of agar.io and I don't understand why the circles start vibrating when they touch each other. Below is my code:
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var dist = Math.sqrt(Math.pow(blob2.x - blob1.x, 2) + Math.pow(blob2.y - blob1.y, 2));
if (dist < blob1.mass + blob2.mass) {
if (this.blobs[i].x < this.blobs[j].x) {
this.blobs[i].x--;
} else if (this.blobs[i].x > this.blobs[j].x) {
this.blobs[i].x++;
}
if (this.blobs[i].y < this.blobs[j].y) {
this.blobs[i].y--;
} else if ((this.blobs[i].y > this.blobs[j].y)) {
this.blobs[i].y++;
}
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
Separating circles
Your separation code was not correct. Use the vector between them to get the new pos.
The vector between them
To find if two circles are intercepting find the length of the vector from one to the next
The two circles.
var cir1 = {x : 100, y : 100, r : 120}; // r is the radius
var cir2 = {x : 250, y : 280, r : 150}; // r is the radius
The vector from cir2 to cir1
var vx = cir2.x - cir1.x;
var vy = cir2.y - cir1.y;
The length of the vector
var len = Math.sqrt(x * x + y * y);
// or use the ES6 Math.hypot function
/* var len = Math.hypot(x,y); */
The circles overlap if the sum of the radii is greater than the length of the vector between them
if(cir1.r + cir2.r > len){ // circles overlap
Normalise the vector
If they overlap you need to move one away from the other. There are many ways to do this, the simplest way is to move one circle along the line between them.
First normalise the vector from cir1 to cir2 by dividing by its (vector) length.
vx \= len;
vy \= len;
Note that the length could be zero. If this happens then you will get NaN in further calculations. If you suspect you may get one circle at the same location as another the easiest way to deal with the zero move one circle a little.
// replace the two lines above with
if(len === 0){ // circles are on top of each other
vx = 1; // move the circle (abstracted into the vector)
}else{
vx \= len; // normalise the vector
vy \= len;
}
Move circle/s to just touch
Now you have the normalised vector which is 1 unit long you can make it any length you need by multiplying the two scalars vx, vy with the desired length which in this case is the sum of the two circles radii.
var mx = vx * (cir1.r + cir2.r); // move distance
var my = vy * (cir1.r + cir2.r);
.Only use one of the following methods.
You can now position one of the circles the correct distance so that they just touch
// move cir1
cir1.x = cir2.x - mx;
cir1.y = cir2.y - my;
Or move the second circle
cir2.x = cir1.x + mx;
cir2.y = cir1.y + my;
Or move both circles but you will have to first find the proportional center between the two
var pLen = cir1.r / (cir1.r + cir2.r); // find the ratio of the radii
var cx = cir1.x + pLen * vx * len; // find the proportional center between
var cy = cir1.y + pLen * vy * len; // the two circles
Then move both circles away from that point by their radii
cir1.x = cx - vx * cir1.r; // move circle 1 away from the shared center
cir1.y = cy - vy * cir1.r;
cir2.x = cx + vx * cir2.r; // move circle 2 away from the shared center
cir2.y = cy + vy * cir2.r;
DEMO
Copy of OP's snippet with mods to fix problem by moving the the first circle blob1 away from the second blob2 and assuming they will never be at the same spot (no divide by zero)
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x; // get the vector from blob1 to blob2
var y = blob2.y - blob1.y; //
var dist = Math.sqrt(x * x + y * y); // get the distance between the two blobs
if (dist < blob1.mass + blob2.mass) { // if the distance is less than the 2 radius
// if there is overlap move blob one along the line between the two the distance of the two radius
x /= dist; // normalize the vector. This makes the vector 1 unit long
y /= dist;
// multiplying the normalised vector by the correct distance between the two
// and subtracting that distance from the blob 2 give the new pos of
// blob 1
blob1.x = blob2.x - x * (blob1.mass + blob2.mass);
blob1.y = blob2.y - y * (blob1.mass + blob2.mass);
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var dist = Math.sqrt(Math.pow(blob2.x - blob1.x, 2) + Math.pow(blob2.y - blob1.y, 2));
if (dist < blob1.mass + blob2.mass) {
if (this.blobs[i].x < this.blobs[j].x) {
this.blobs[i].x--;
} else if (this.blobs[i].x > this.blobs[j].x) {
this.blobs[i].x++;
}
if (this.blobs[i].y < this.blobs[j].y) {
this.blobs[i].y--;
} else if ((this.blobs[i].y > this.blobs[j].y)) {
this.blobs[i].y++;
}
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
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>