radial motion in javascript - javascript

By default HTML 5 canvas has rectangular shape, though if i do any animation under canvas, it will move into rectangular area.
What if i bound area to radial shape?It shouldn't definitely go out of radial shape.
Can we limit boundary to radial instead of default rectangular shape?
You can look at ball is going out of radial boundary- http://jsfiddle.net/stackmanoz/T4WYH/
I designed boundary in radial shape now i want to limit radial area too.
Limit area for ball-
function bounce() {
if (x + dx > 293 || x + dx < 0) {
dx = -dx;
}
if (y >= 290) {
y = 290;
}
if (y + dy > 290 || y + dy < 0) {
dx *= 0.99;
dy = -dy;
}
if (Math.abs(dx) < 0.01) {
dx = 0;
}
dy++;
}

The cartesian formula for a circle is (x − a)2 + (y − b)2 = r2
So check for this in your bounce condition:
function bounce() {
if( Math.pow(x - 150, 2) + Math.pow(y - 150, 2) > Math.pow(150, 2))
{
dx = -dx * (0.6 + (Math.random() * 0.8));
dy = -dy * (0.6 + (Math.random() * 0.8));
}
}
I am using random bouncing because I could not work out the correct bounce angle using the incident speed and the normal at the bounce point (I'm sure there is somebody ele here who can)
updated fiddle here: http://jsfiddle.net/T4WYH/1/

Few points, use requestAnimationFrame rather than setInterval
I would draw the large circle in the canvas, rather than as a border, then you can use isPointInPath(x, y) to work out if your ball is within the circle (or any other arbitrary path for that matter)
function draw() {
ctx.clearRect(0, 0, 300, 300);
ctx.beginPath()
ctx.lineWidth = 1;
ctx.strokeStyle = '#000';
ctx.arc(150, 150, 150, 0, 2 * Math.PI, true);
ctx.stroke();
ctx.closePath();
console.log(ctx.isPointInPath(x, y)); //Is (center) of ball in big circle
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.fill();
x += dx;
y += dy;
bounce();
}

I don't want to implement this particular task, but you can get ideas how to bounce from circle from this site: bbdizains.com
And coresponding function:
move = function(){
for (i=1; i<=3; i++) {
A.x[i] = A.x[i] - A.v[i]*Math.sin(A.alfa[i]);
A.y[i] = A.y[i] - A.v[i]*Math.cos(A.alfa[i]);
x = A.x[i]-175;
y = A.y[i]-233;
x2 = x*x;
y2 = y*y;
if (x2+y2>=6561) {
if (A.flag[i]==1) {
gamma = Math.atan(y/x);
A.alfa[i] = - 2*gamma - A.alfa[i];
A.flag[i] = 0;
}
} else {
A.flag[i] = 1;
}
$('#r'+i).css('left',A.x[i]+'px');
$('#r'+i).css('top',A.y[i]+'px');
if ((Math.random()>0.95) && (x2+y2<6000)) {
A.alfa[i] = A.alfa[i] + Math.PI*Math.random()*0.1;
}
}
}

Related

Fill in shape with lines at a specified angle

I need to create line segments within a shape and not just a visual pattern - I need to know start and end coordinates for those lines that are within a given boundary (shape). I'll go through what I have and explain the issues I'm facing
I have a closed irregular shape (can have dozens of sides) defined by [x, y] coordinates
shape = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
];
I calculate and draw the bounding box of this shape
I then draw my shape on the canvas
Next, I cast rays within the bounding box with a set spacing between each ray. The ray goes from left to right incrementing by 1 pixel.
Whenever a cast ray gets to a pixel with RGB values of 100, 255, 100 I then know it has entered the shape. I know when it exits the shape if the pixel value is not 100, 255, 100. Thus I know start and end coordinates for each line within my shape and if one ray enters and exits the shape multiple times - this will generate all line segments within that one ray cast.
For the most part it works but there are issues:
It's very slow. Perhaps there is a better way than casting rays? Or perhaps there is a way to optimize the ray logic? Perhaps something more intelligent than just checking for RGB color values?
How do I cast rays at a different angle within the bounding box? Now it's going left to right, but how would I fill my bounding box with rays cast at any specified angle? i.e.:
I don't care about holes or curves. The shapes will all be made of straight line segments and won't have any holes inside them.
Edit: made changes to the pixel RGB sampling that improve performance.
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 15;
shape = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
];
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
// display bounding box
ctx.fillStyle = 'rgba(255,0,0,.2)';
ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
// loop through the shape in vertical slices
for(var i = boundingBox[0][1]+lineSpacing; i <= boundingBox[1][1]; i += lineSpacing) {
// send ray from left to right
for(var j = boundingBox[0][0], start = false; j <= boundingBox[1][0]; j++) {
x = j, y = i;
pixel = y * (canvas.width * 4) + x * 4;
// if pixel is within boundary (shape)
if(canvasData[pixel] == 100 && canvasData[pixel+1] == 255 && canvasData[pixel+2] == 100) {
// arrived at start of boundary
if(start === false) {
start = [x,y]
}
} else {
// arrived at end of boundary
if(start !== false) {
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.beginPath();
ctx.moveTo(start[0], start[1]);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
start = false;
}
}
}
// show entire cast ray for debugging purposes
ctx.strokeStyle = 'rgba(0,0,0,.2)';
ctx.beginPath();
ctx.moveTo(boundingBox[0][0], i);
ctx.lineTo(boundingBox[1][0], i);
ctx.closePath();
ctx.stroke();
}
<canvas id="canvas" width="350" height="350"></canvas>
This is a pretty complex problem that I am trying to simplify as much as possible. Using the line intersection formula we can determin where the ray intersects with the shape at every edge. What we can do is loop through each side of the shape while check every rays intersection. If they intersect we push those coordinates to an array.
I have tried to make this as dynamic as possible. You can pass the shape and change the number of rays and the angle. As for the angle it doesn't take a specific degree (i.e. 45) but rather you change the start and stop y axis. I'm sure if you must have the ability to put in a degree we can do that.
It currently console logs the array of intersecting coordinates but you can output them however you see fit.
The mouse function is just to verify that the number match up. Also be aware I am using toFixed() to get rid of lots of decimals but it does convert to a string. If you need an integer you'll have to convert back.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d")
canvas.width = 300;
canvas.height = 300;
ctx.fillStyle = "violet";
ctx.fillRect(0,0,canvas.width,canvas.height)
//Shapes
let triangleish = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
]
let star = [ [ 0, 85 ], [ 75, 75 ], [ 100, 10 ], [ 125, 75 ],
[ 200, 85 ], [ 150, 125 ], [ 160, 190 ], [ 100, 150 ],
[ 40, 190 ], [ 50, 125 ], [ 0, 85 ] ];
let coords = [];
//Class that draws the shape on canvas
function drawShape(arr) {
ctx.beginPath();
ctx.fillStyle = "rgb(0,255,0)";
ctx.moveTo(arr[0][0], arr[0][1]);
for (let i=1;i<arr.length;i++) {
ctx.lineTo(arr[i][0], arr[i][1]);
}
ctx.fill();
ctx.closePath();
}
//pass the shape in here to draw it
drawShape(star)
//Class to creat the rays.
class Rays {
constructor(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.w = canvas.width;
this.h = 1;
}
draw() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(this.x1, this.y1)
ctx.lineTo(this.x2, this.y2)
ctx.stroke();
ctx.closePath();
}
}
let rays = [];
function createRays(angle) {
let degrees = angle * (Math.PI/180)
//I am currently creating an array every 10px on the Y axis
for (let i=0; i < angle + 45; i++) {
//The i will be your start and stop Y axis. This is where you can change the angle
let cx = canvas.width/2 + (angle*2);
let cy = i * 10;
let x1 = (cx - 1000 * Math.cos(degrees));
let y1 = (cy - 1000 * Math.sin(degrees));
let x2 = (cx + 1000 * Math.cos(degrees));
let y2 = (cy + 1000 * Math.sin(degrees));
rays.push(new Rays(x1, y1, x2, y2))
}
}
//enter angle here
createRays(40);
//function to draw the rays after crating them
function drawRays() {
for (let i=0;i<rays.length; i++) {
rays[i].draw();
}
}
drawRays();
//This is where the magic happens. Using the line intersect formula we can determine if the rays intersect with the objects sides
function intersectLines(coord1, coord2, rays) {
let x1 = coord1[0];
let x2 = coord2[0];
let y1 = coord1[1];
let y2 = coord2[1];
let x3 = rays.x1;
let x4 = rays.x2;
let y3 = rays.y1;
let y4 = rays.y2;
//All of this comes from Wikipedia on line intersect formulas
let d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) {
return
}
let t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
let u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
//if this statement is true then the lines intersect
if (t > 0 && t < 1 && u > 0) {
//I have currently set it to fixed but if a string does not work for you you can change it however you want.
//the first formula is the X coord of the interect the second is the Y
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return
}
//function to call the intersect function by passing in the shapes sides and each ray
function callIntersect(shape) {
for (let i=0;i<shape.length;i++) {
for (let j=0;j<rays.length;j++) {
if (i < shape.length - 1) {
intersectLines(shape[i], shape[i+1], rays[j]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rays[j]);
}
}
}
}
callIntersect(star);
//just to sort them by the Y axis so they they show up as in-and-out
function sortCoords() {
coords.sort((a, b) => {
return a[1] - b[1];
});
}
sortCoords()
console.log(coords)
//This part is not needed only added to verify number matched the mouse posit
let mouse = {
x: undefined,
y: undefined
}
let canvasBounds = canvas.getBoundingClientRect();
addEventListener('mousemove', e => {
mouse.x = e.x - canvasBounds.left;
mouse.y = e.y - canvasBounds.top;
ctx.clearRect(0, 0, canvas.width, canvas.height)
drawCoordinates();
})
function drawCoordinates() {
ctx.font = '15px Arial';
ctx.fillStyle = 'black';
ctx.fillText('x: '+mouse.x+' y: '+mouse.y, mouse.x, mouse.y)
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "violet";
ctx.fillRect(0,0,canvas.width,canvas.height)
for (let i=0;i<rays.length; i++) {
rays[i].draw();
}
drawShape(star)
drawCoordinates();
requestAnimationFrame(animate)
}
animate()
<canvas id="canvas"></canvas>
I'm not an expert, but maybe you could do something like this:
Generate the points that constitute the borders.
Organize them in a convenient structure, e.g. an object with the y as key, and an array of x as values.
2.1. i.e. each item in the object would constitute all points of all borders in a single y.
Iterate over the object and generate the segments for each y.
3.1. e.g. if the array of y=12 contains [ 10, 20, 60, 80 ] then you would generate two segments: [ 10, 12 ] --> [ 20, 12 ] and [ 60, 12 ] --> [ 80, 12 ].
To generate the borders' points (and to answer your second question), you can use the line function y = a*x + b.
For example, to draw a line between [ 10, 30 ] and [ 60, 40 ], you would:
Solve a and b by substituting x and y for both points and combining these two formulas (with standard algebra):
For point #1: 30 = a*10 + b
For point #2: 40 = a*60 + b
b = 30 - a*10
40 = a*60 + (30 - a*10)
a*60 - a*10 = 40 - 30
50*a = 10
a = 0.2
30 = a*10 + b
30 = 0.2*10 + b
b = 30 - 2
b = 28
With a and b at hand, you get the function for your specific line:
y = 0.2*x + 28
With that, you can calculate the point of the line for any y. So, for example, the x of the point right under the first point ([ 10, 30 ]) would have a y of 31, and so: 31 = 0.2*x + 28, and so: x = 15. So you get: [ 15, 31 ].
You may need a bit of special handling for:
Vertical lines, because the slope is "infinite" and calculating it would cause division by zero.
Rounding issues. For some (probably most) pixels you will get real x values (i.e. non-integer). You can Math.round() them, but it can cause issues, like:
8.1. Diagonal rays may not actually hit a border point even when they go through a border. This will probably require additional handling (like checking points around and not just exactly the pixels the ray lies on).
8.2. The points your algorithm generate may (slightly) differ from the points that appear on the screen when you use libraries or built-in browser functionality to draw the shape (depending on the implementation of their drawing algorithms).
This is a mashup of Justin's answer and code from my proposed question.
One issue was generating rays at a set angle and a set distance from each other. To have rays be equal distances apart at any angle we can use a vector at a 90 degree angle and then place a new center point for the next line.
We can start at the exact midpoint of our boundary and then spread out on either side.
Red line is the center line, green dots are the vector offset points for the next line.
Next I modified Justin's intersect algorithm to iterate by ray and not side, that way I get interlaced coordinates where array[index] is the start point of a segment and array[index+1] is the end point.
And by connecting the lines we get a shape that is filled with lines inside its boundaries at set distances apart
Issues:
I had to inflate the boundary by 1 pixel otherwise certain shapes would fail to generate paths
I'd like rays to be some what aligned. It's hard to explain, but here's an example of 6 triangles rotated at 60 degree increments that form a hexagon with their inner lines also offset by 60 degree increments. The top and bottom triangle inner lines do not join those of the outside triangles. This is an issue with the cast rays. Ideally I'd like them to join and be aligned with the outer most edge if that makes sense. Surely there is a better way to cast rays than this...
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 12;
angle = 45;
shapes = [
[[143.7,134.2], [210.4,18.7], [77.1,18.7]],
[[143.7,134.2], [77.1,18.7], [10.4,134.2]],
[[143.7,134.2], [10.4,134.2], [77.1,249.7]],
[[143.7,134.2], [77.1,249.7], [210.4,249.7]],
[[143.7,134.2], [210.4,249.7], [277.1,134.2]],
[[143.7,134.2], [277.1,134.2], [210.4,18.7]]
];
for(var i in shapes) {
lines = getLineSegments(shapes[i], 90+(-60*i), lineSpacing);
for(var i = 0; i < lines.length; i += 2) {
start = lines[i];
end = lines[i+1];
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.closePath();
ctx.stroke();
}
}
function getLineSegments(shape, angle, lineSpacing) {
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
boundingBox[1][0] += 1, boundingBox[1][1] += 1;
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
rayPaths = [];
path = getPathCoords(boundingBox, 0, 0, angle);
rayPaths.push(path);
/*ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
getPaths:
for(var i = 0, lastPaths = [path, path]; true; i++) {
for(var j = 0; j < 2; j++) {
pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;
pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];
pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);
pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);
offsetX = pathOffsetPointX-boundingMidX;
offsetY = pathOffsetPointY-boundingMidY;
path = getPathCoords(boundingBox, offsetX, offsetY, angle);
if(
path[0][0] < boundingBox[0][0] ||
path[1][0] > boundingBox[1][0] ||
path[0][0] > boundingBox[1][0] ||
path[1][0] < boundingBox[0][0]
) break getPaths;
/*ctx.fillStyle = 'green';
ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
rayPaths.push(path);
lastPaths[j] = path;
}
}
coords = [];
function intersectLines(coord1, coord2, rays) {
x1 = coord1[0], x2 = coord2[0];
y1 = coord1[1], y2 = coord2[1];
x3 = rays[0][0], x4 = rays[1][0];
y3 = rays[0][1], y4 = rays[1][1];
d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) return;
t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return;
}
function callIntersect(shape) {
for (var i = 0; i < rayPaths.length; i++) {
for (var j = 0; j< shape.length; j++) {
if (j < shape.length - 1) {
intersectLines(shape[j], shape[j+1], rayPaths[i]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
}
}
}
}
callIntersect(shape);
return coords;
}
function getPathCoords(boundingBox, offsetX, offsetY, angle) {
coords = [];
// add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
angle = angle + 0.0000000000001 - 90;
boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
x = boundingMidX + offsetX, y = boundingMidY + offsetY;
dx = Math.cos(Math.PI * angle / 180);
dy = Math.sin(Math.PI * angle / 180);
for(var i = 0; i < 2; i++) {
bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];
if(dx == 0) ix = x, iy = by;
if(dy == 0) iy = y, ix = bx;
tx = (bx - x) / dx;
ty = (by - y) / dy;
if(tx <= ty) {
ix = bx, iy = y + tx * dy;
} else {
iy = by, ix = x + ty * dx;
}
coords.push([ix, iy]);
dx = -dx;
dy = -dy;
}
return coords;
}
<canvas id="canvas" width="500" height="500"></canvas>
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 10;
angle = 45;
shape = [
[200,10], // x, y
[10,300],
[200,200],
[400,300]
];
lines = getLineSegments(shape, angle, lineSpacing);
for(var i = 0; i < lines.length; i += 2) {
start = lines[i];
end = lines[i+1];
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.closePath();
ctx.stroke();
}
function getLineSegments(shape, angle, lineSpacing) {
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
boundingBox[1][0] += 1, boundingBox[1][1] += 1;
// display bounding box
ctx.fillStyle = 'rgba(255,0,0,.2)';
ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
rayPaths = [];
path = getPathCoords(boundingBox, 0, 0, angle);
rayPaths.push(path);
/*ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
getPaths:
for(var i = 0, lastPaths = [path, path]; true; i++) {
for(var j = 0; j < 2; j++) {
pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;
pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];
pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);
pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);
offsetX = pathOffsetPointX-boundingMidX;
offsetY = pathOffsetPointY-boundingMidY;
path = getPathCoords(boundingBox, offsetX, offsetY, angle);
if(
path[0][0] < boundingBox[0][0] ||
path[1][0] > boundingBox[1][0] ||
path[0][0] > boundingBox[1][0] ||
path[1][0] < boundingBox[0][0]
) break getPaths;
/*ctx.fillStyle = 'green';
ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
rayPaths.push(path);
lastPaths[j] = path;
}
}
coords = [];
function intersectLines(coord1, coord2, rays) {
x1 = coord1[0], x2 = coord2[0];
y1 = coord1[1], y2 = coord2[1];
x3 = rays[0][0], x4 = rays[1][0];
y3 = rays[0][1], y4 = rays[1][1];
d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) return;
t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return;
}
function callIntersect(shape) {
for (var i = 0; i < rayPaths.length; i++) {
for (var j = 0; j< shape.length; j++) {
if (j < shape.length - 1) {
intersectLines(shape[j], shape[j+1], rayPaths[i]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
}
}
}
}
callIntersect(shape);
return coords;
}
function getPathCoords(boundingBox, offsetX, offsetY, angle) {
coords = [];
// add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
angle = angle + 0.0000000000001 - 90;
boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
x = boundingMidX + offsetX, y = boundingMidY + offsetY;
dx = Math.cos(Math.PI * angle / 180);
dy = Math.sin(Math.PI * angle / 180);
for(var i = 0; i < 2; i++) {
bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];
if(dx == 0) ix = x, iy = by;
if(dy == 0) iy = y, ix = bx;
tx = (bx - x) / dx;
ty = (by - y) / dy;
if(tx <= ty) {
ix = bx, iy = y + tx * dy;
} else {
iy = by, ix = x + ty * dx;
}
coords.push([ix, iy]);
dx = -dx;
dy = -dy;
}
return coords;
}
<canvas id="canvas" width="500" height="500"></canvas>

Calculate 2D rotation javascript HTML canvas

Implemented a canvas, Drawing a square there and get the calculated coordinates,
Can see on the following pic the drawing:
I'm calculating and getting the upleft point X and Y coordinates,
And for the down right coordinates that i need, I'm adding the height and width, as follows:
{ upLeft: { x: position.x, y: position.y }, downRight: { x: position.x + position.width, y: position.y + position.height } },
Now i want to get the same dimensions when i'm rotating the canvas clockwise or anti-clockwise.
So i have the angle, And i try to calculate via the following function:
function getRotatedCoordinates(cx, cy, x, y, angle) {
let radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) - (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) + (sin * (x - cx)) + cy;
return [nx, ny];
}
And i'm calling the function via the following args and using it.
let newCoords = getRotatedCoordinates(0, 0, position.x, position.y, angle);
position.x = newCoords[0];
position.y = newCoords[1];
So firstly, I'm not sure that the cx and cy points are correct, I'm always entering 0 for both of them.
Secondly, I'm not getting the desired results, They are getting changed but i'm pretty sure that something is wrong with the x and y, So i guess that the function is wrong.
Thanks.
Here is how I would do it:
function getRectangeCoordinates(x, y, width, height, angle) {
let points = [ [x, y] ]
let radians = (Math.PI / 180) * angle;
for (let i = 0; i < 3; i++) {
x += Math.cos(radians) * ((i == 1) ? height : width);
y += Math.sin(radians) * ((i == 1) ? height : width);
points.push([x, y])
radians += Math.PI / 2
}
return points
}
let canvas = document.createElement("canvas");
canvas.width = canvas.height = 140
let ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
function draw(coords, radius) {
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], radius, 0, 8);
ctx.moveTo(coords[i][0], coords[i][1]);
let next = (i + 1) % 4
ctx.lineTo(coords[next][0], coords[next][1]);
ctx.stroke();
}
}
let coords = getRectangeCoordinates(20, 10, 120, 40, 15)
console.log(JSON.stringify(coords))
draw(coords, 3)
ctx.strokeStyle = "red";
coords = getRectangeCoordinates(60, 40, 40, 50, 65)
draw(coords, 5)
ctx.strokeStyle = "blue";
coords = getRectangeCoordinates(120, 3, 20, 20, 45)
draw(coords, 2)
In the getRectangeCoordinates I'm returning all corners of a rectangle and the paraments of the function are the top left corner (x, y) the height and width of the rectangle and last the angle.
I'm drawing a few rectangles with different shapes and angles to show how it looks like
The calculations in the function are simple trigonometry here is a visual representation that could help you remember it the next time you need it:

Is there a better way of calling these balls with different color, x and speed each time?

I started learning JavaScript a few weeks ago and I decided to try and make my first game from scratch, in the code below I am trying to make the game so the balls fall each time with different position, color and speed and after that there will be another ball that ill be able to move with my mouse and will try to dodge the balls, so my question is is there a better way than the one I did to spawn more balls , because if I want to spawn like 5-6 more the code will look so bad and I am sure there is a better way of doing that, I am still learning so if you can hit me with a simple solution and explain it.
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
var x = random(1, 801);
var x2 = random(1, 801);
var y = 10;
var y2 = 10;
var ballRadius = random(2, 51);
var ballRadius2 = random(2, 51);
var color = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var color2 = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var dy = random(1, 6);
var dy2 = random(1, 6);
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function drawBall2() {
ctx.beginPath();
ctx.arc(x2, y2, ballRadius2, 0, Math.PI * 2);
ctx.fillStyle = color2;
ctx.fill();
ctx.closePath();
}
function draw() {
drawBall();
drawBall2();
y += dy;
y2 += dy2;
}
setInterval(draw, 10);
I want to know methods to simplify my code so I know for future projects.
Consider the following code:
The main changes I made are the addition of the function createBall, allowing the creation of multiple new balls per tick, and the removal of balls that are out of view. I tried to add comments to the new parts, if you have any questions, let me know!
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
const desiredNumberOfBalls = 10;
let i = 0;
let balls = []; // Track each individual ball
const viewLimit = canvas.height; // The vertical distance a ball has when leaving sight
// For now we create a static paddle
const paddle = {
x: 200,
y: viewLimit - 10,
width: 50,
height: 10,
}
// This is a very rough calculation based on https://math.stackexchange.com/a/2100319 by checking first the corners and then the top edge
paddle.hitsBall = function(ball) {
let hit = false;
if (ball.y + ball.radius >= paddle.y) {
// the ball is at the same level as the paddle
if (Math.sqrt((ball.x - paddle.x) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper left part of the paddle touches the ball
hit = true;
} else if (Math.sqrt((ball.x - paddle.x - paddle.width) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper right part of the paddle touches the ball
hit = true;
} else if (ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
// the top edge of the paddle touches the ball
hit = true;
}
}
if (hit) console.log("Hit!");
return hit;
}
function createBall() {
let ball = {
id: i++,
x: random(1, canvas.width),
y: 10,
radius: random(2, 51),
color: "#" + ((1 << 24) * Math.random() | 0).toString(16),
speed: random(1, 6)
};
return ball;
}
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall(ball) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.fillStyle = 'darkred';
ctx.fill();
ctx.closePath();
}
function clearView() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function draw() {
clearView();
// Move all existing balls by their designated speed
balls.forEach((ball) => {
ball.y += ball.speed;
});
// Implicitly delete all balls that have exited the viewport
// by only retaining the balls that are still inside the viewport
balls = balls.filter((ball) => ball.y <= viewLimit);
// Implicitly delete all balls that touch the paddle
// by only retaining the balls that don't
balls = balls.filter((ball) => !paddle.hitsBall(ball));
// Create newBallsPerTick new balls
while (balls.length < desiredNumberOfBalls) {
balls.push(createBall());
}
// Draw the paddle
drawPaddle();
// Draw the position of every ball - both the pre-existing ones
// and the ones we just generated
balls.forEach((ball) => {
drawBall(ball);
});
}
setInterval(draw, 1000 / 60);
canvas {
width: 600px;
height: 400px;
}
<canvas id="Canv" height="400" width="600"></canvas>

Canvas - How to draw the grid of the dots with the light source in the center?

I think it's usual question, but I have some problems with displaying dots in canvas. The first thing I'd like to know is how to draw dot like this (please zoom it).
The second thing is, how to draw a shadow to each element of the grid of this dots, with the light source in the center.
What I have at this moment right here:
the part of my code:
context.fillStyle = "#ccc";
context.shadowColor = '#e92772';
context.shadowOffsetX = 15;
context.shadowOffsetY = 15;
while (--e >= 1) {
x -= z;
if(x < 0) {
x = z*w;
y -= z;
}
context.moveTo(x, y);
context.fillRect( x, y, 1, 1 );
outs = a[e];
}
Also, I've tried to use "context.arc();", but I think "context.fillRect();" is more easier. And one else moment, when I use "while (--e >= 0)" instead of "while (--e >= 1)" I have two more dots, on the top. Why?
If you know some articles or tutorials, would you give me the link to them. Preferably without the use of the frameworks. Thanks.
You can use some trigonometry to simulate 3D dots with a light source.
HERE IS AN ONLINE DEMO
This is one way you can do it, there are of course others (this was the first that came to mind):
Draw the grid with some dots on the main canvas
Render a radial gradient to an off-screen canvas
Change composition mode so anything is draw on already existing pixels
Calculate distance and angle to light source and draw the dot to each grid point offset with the angle/dist.
Here is some code from the demo that does this.
Draw the grid with dots
We skip one grid point as we will fill each dot later with the gradient dot which otherwise would paint over the neighbor dot.
/// draw a grid of dots:
for (y = 0; y < ez.width; y += gridSize * 2) {
for (x = 0; x < ez.height; x += gridSize * 2) {
ctx.beginPath();
ctx.arc(x + offset, y + offset, radius, 0, arcStop);
ctx.closePath();
ctx.fill();
}
}
Create a light "reflection"
Prepare the gradient dot to an off-screen canvas (dctx = dot-context). I am using easyCanvas for the setup and to give me an off-screen canvas with center point already calculated, but one can setup this manually too of course:
grd = dctx.createRadialGradient(dot.centerX, dot.centerY, 0,
dot.centerX, dot.centerY, gridSize);
grd.addColorStop(0, '#fff');
grd.addColorStop(0.2, '#777'); // thighten up
grd.addColorStop(1, '#000');
dctx.fillStyle = grd;
dctx.fillRect(0, 0, gridSize, gridSize);
Do the math
Then we do all the calculation and offsetting:
/// change composition mode
ctx.globalCompositeOperation = 'source-atop';
/// calc angle and distance to light source and draw each
/// dot gradient offset in relation to this
for (y = 0; y < ez.width; y += gridSize) {
for (x = 0; x < ez.height; x += gridSize) {
/// angle
angle = Math.atan2(lightY - y, lightX - x);
//if (angle < 0) angle += 2;
/// distance
dx = lightX - x;
dy = lightY - y;
dist = Math.sqrt(dx * dx + dy * dy);
/// map distance to our max offset
od = dist / maxLength * maxOffset * 2;
if (od > maxOffset * 2) od = maxOffset * 2;
/// now get new x and y position based on angle and offset
offsetX = x + od * Math.cos(angle) - maxOffset * 0.5;
offsetY = y + od * Math.sin(angle) - maxOffset * 0.5;
/// draw the gradient dot at offset
ctx.drawImage(dot.canvas, x + offsetX, y + offsetY);
}
}
Shadow
For the shadow you just inverse the offset while using the composition mode destination-over which will draw outside the already drawn pixels:
/// Shadow, same as offsetting light, but in opposite
/// direction and with a different composite mode
ctx.globalCompositeOperation = 'destination-over';
for (y = 0; y < ez.width; y += gridSize) {
for (x = 0; x < ez.height; x += gridSize) {
/// angle
angle = Math.atan2(lightY - y, lightX - x);
//if (angle < 0) angle += 2;
/// distance
dx = lightX - x;
dy = lightY - y;
dist = Math.sqrt(dx * dx + dy * dy);
/// map distance to our max offset
od = dist / maxLength * maxOffset * 2;
if (od > maxOffset * 4) od = maxOffset * 4;
/// now get new x and y position based on angle and offset
offsetX = x - od * Math.cos(angle) + gridSize * 0.5;
offsetY = y - od * Math.sin(angle) + gridSize * 0.5;
ctx.beginPath();
ctx.arc(x + offsetX, y + offsetY, radius, 0, arcStop);
ctx.fill();
}
}
This can all be optimized of-course into a single loop pair but for overview the code is separated.
Additional
In the demo I added mouse tracking so the mouse becomes the light-source and you can see the dot-reflection changes while you move the mouse. For best performance use Chrome.
To match your need just scale down the values I am using - or - draw to a big off-screen canvas and use drawImage to scale it down to a main canvas.

HTML5 Animating a dashed line

Being relatively new to the HTML5 game, just wanted to ask if anyone knew if it was possible to animate a dashed line along a path? Think snake from Nokia days, just with a dashed line...
I've got a dashed line (which represents electrical current flow), which I'd like to animate as 'moving' to show that current is flowing to something.
Thanks to Rod's answer on this post, I've got the dashed line going, but am not sure where to start to get it moving. Anyone know where to begin?
Thanks!
Got it working here, based on this post by phrogz.
What i did:
Add a start parameter which is a number between 0 and 99
Calculate the dashSize summing the contents of the dash array
Calculate dashOffSet as a fraction of dashSize based on start percent
Subtracted the offset from x, y and added to dx, dy
Only started drawying after the offset been gone (it´s negative, remember)
Added a setInterval to update the start from 0 to 99, step of 10
Update
The original algorithm wasn't working for vertical or negative inclined lines. Added a check to use the inclination based on the y slope on those cases, and not on the x slope.
Demo here
Updated code:
if (window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype.lineTo) {
CanvasRenderingContext2D.prototype.dashedLine = function(x, y, x2, y2, dashArray, start) {
if (!dashArray) dashArray = [10, 5];
var dashCount = dashArray.length;
var dashSize = 0;
for (i = 0; i < dashCount; i++) dashSize += parseInt(dashArray[i]);
var dx = (x2 - x),
dy = (y2 - y);
var slopex = (dy < dx);
var slope = (slopex) ? dy / dx : dx / dy;
var dashOffSet = dashSize * (1 - (start / 100))
if (slopex) {
var xOffsetStep = Math.sqrt(dashOffSet * dashOffSet / (1 + slope * slope));
x -= xOffsetStep;
dx += xOffsetStep;
y -= slope * xOffsetStep;
dy += slope * xOffsetStep;
} else {
var yOffsetStep = Math.sqrt(dashOffSet * dashOffSet / (1 + slope * slope));
y -= yOffsetStep;
dy += yOffsetStep;
x -= slope * yOffsetStep;
dx += slope * yOffsetStep;
}
this.moveTo(x, y);
var distRemaining = Math.sqrt(dx * dx + dy * dy);
var dashIndex = 0,
draw = true;
while (distRemaining >= 0.1 && dashIndex < 10000) {
var dashLength = dashArray[dashIndex++ % dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
if (slopex) {
var xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
x += xStep
y += slope * xStep;
} else {
var yStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
y += yStep
x += slope * yStep;
}
if (dashOffSet > 0) {
dashOffSet -= dashLength;
this.moveTo(x, y);
} else {
this[draw ? 'lineTo' : 'moveTo'](x, y);
}
distRemaining -= dashLength;
draw = !draw;
}
// Ensure that the last segment is closed for proper stroking
this.moveTo(0, 0);
}
}
var dashes = '10 20 2 20'
var c = document.getElementsByTagName('canvas')[0];
c.width = 300;
c.height = 400;
var ctx = c.getContext('2d');
ctx.strokeStyle = 'black';
var drawDashes = function() {
ctx.clearRect(0, 0, c.width, c.height);
var dashGapArray = dashes.replace(/^\s+|\s+$/g, '').split(/\s+/);
if (!dashGapArray[0] || (dashGapArray.length == 1 && dashGapArray[0] == 0)) return;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.dashedLine(10, 0, 10, c.height, dashGapArray, currentOffset);
ctx.dashedLine(0, 10, c.width, 10, dashGapArray, currentOffset);
ctx.dashedLine(0, 0, c.width, c.height, dashGapArray, currentOffset);
ctx.dashedLine(0, c.height, c.width, 0, dashGapArray, currentOffset);
ctx.closePath();
ctx.stroke();
};
window.setInterval(dashInterval, 500);
var currentOffset = 0;
function dashInterval() {
drawDashes();
currentOffset += 10;
if (currentOffset >= 100) currentOffset = 0;
}
You can create the dashed line animation using SNAPSVG library.
Please check the tutorial here DEMO

Categories