Trying to create a function that can dynamically oscillate properties of an object. I've already managed to create it outside of a function, but I can't make it work in one. It's because of the angle variable, which increases each frame. Here's an example to make an object oscillate x and y props in a circle.
Example fiddle.
Initialization...
var obj = new Ball(arguments...),
target = {
x: 100,
y: 100
},
angle = 0,
radius = 50,
speed = 0.1;
Loop...
// clear canvas
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
// rAF
That works just fine, but when I try to make it reusable and turn it into a function, it doesn't work.
function oscillate(obj, target, angle, radius, speed) {
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
}
How do I make it work in a function?
That's because angle is not passed by reference. JS does not have pass by reference.
You can just modify the outer variable by not declare a local one:
var map = document.getElementById('map'),
fx = map.getContext('2d');
var ball = {
x: 50,
y: 50,
radius: 50
},
target = {
x: map.width / 2,
y: map.height / 2,
},
angle = 0,
radius = 50,
speed = 0.1;
ball.draw = function(fx) {
fx.beginPath();
fx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
fx.fill();
};
function oscillate(obj, target, radius, speed) {
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
}
(function update() {
fx.clearRect(0, 0, map.width, map.height);
oscillate(ball, target, radius, speed)
ball.draw(fx);
requestAnimationFrame(update);
}());
<canvas id='map'></canvas>
Alternatively, pass an object:
var map = document.getElementById('map'),
fx = map.getContext('2d');
var ball = {
x: 50,
y: 50,
radius: 50
},
data = {
obj: ball,
target: {
x: map.width / 2,
y: map.height / 2,
},
angle: 0,
radius: 50,
speed: 0.1
};
ball.draw = function(fx) {
fx.beginPath();
fx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
fx.fill();
};
function oscillate(data) {
data.obj.x = data.target.x + Math.cos(data.angle) * data.radius;
data.obj.y = data.target.y + Math.sin(data.angle) * data.radius;
data.angle += data.speed;
}
(function update() {
fx.clearRect(0, 0, map.width, map.height);
oscillate(data)
ball.draw(fx);
requestAnimationFrame(update);
}());
<canvas id='map'></canvas>
Related
What are the options available for plotting graphs upon selecting different columns from a Data Table. I have worked with ag-grid and I want some thing like that which will come directly out of the box without using any other graph library(like plotly or highcharts) and manually writing code.
As I said in my comment, I recommend looking into Canvas (mdn).
Here's a minimal exmaple that allows panning (mouse 1) and zooming (mouse 2) in addition to callbacks for clicks and mouse moves.
class Chart {
constructor(canvas, hoverCallback, clickCallback) {
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
this.ctx.font = '14px serif';
canvas.addEventListener('mousedown', e => {
this.dragged = false;
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mousemove', e => {
hoverCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
if (!this.mouseDown)
return;
this.dragged = true;
if (e.buttons & 1 && !e.shiftKey)
this.panRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
else if (e.buttons & 2 || e.shiftKey)
this.zoomRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mouseleave', () => hoverCallback?.());
document.addEventListener('mouseup', () => this.mouseDown = null);
canvas.addEventListener('click', e => {
if (this.dragged)
return;
clickCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
});
canvas.addEventListener('dblclick', e => {
if (this.dragged)
return;
this.resetRange(e.shiftKey);
});
this.pointSets = [];
this.resetRange();
}
set pointSets(value) {
this.pointSets_ = value;
this.draw();
}
resetRange(zeroMins = false) {
let allPoints = this.pointSets_
.flatMap(({points}) => points);
[this.minX, this.deltaX] = Chart.getRange(allPoints.map(({x}) => x), zeroMins);
[this.minY, this.deltaY] = Chart.getRange(allPoints.map(({y}) => y), zeroMins);
this.verifyRange();
this.draw();
}
panRange(x, y) {
this.minX -= x * this.deltaX / this.width;
this.minY += y * this.deltaY / this.height;
this.verifyRange();
this.draw();
}
zoomRange(x, y) {
let dx = x * this.deltaX / this.width;
let dy = -y * this.deltaY / this.height;
this.minX += dx;
this.minY += dy;
this.deltaX -= dx * 2;
this.deltaY -= dy * 2;
this.verifyRange();
this.draw();
}
verifyRange() {
this.minX = Math.max(this.minX, -this.deltaX / 10);
this.minY = Math.max(this.minY, -this.deltaY / 10);
}
draw() {
if (!this.pointSets_ || this.minX === undefined)
return;
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
this.drawPoints();
this.drawAxis();
}
drawPoints() {
this.pointSets_.forEach(({color, fill, size, points, isPath}) => {
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
if (isPath) {
this.ctx.lineWidth = size;
this.ctx.beginPath();
points.forEach((p, i) => {
let {x, y} = this.coordToPixel(p.x, p.y);
if (!i)
this.ctx.moveTo(x, y);
else
this.ctx.lineTo(x, y);
});
if (fill)
this.ctx.fill();
else
this.ctx.stroke();
} else {
points.forEach(p => {
let {x, y} = this.coordToPixel(p.x, p.y);
this.ctx[fill ? 'fillRect' : 'strokeRect'](x - size / 2, y - size / 2, size, size);
});
}
});
}
drawAxis() {
let n = 20;
let step = this.width / n;
let size = 10;
let sizeSmall = 1;
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = `rgb(0, 0, 0)`;
this.ctx.fillStyle = `rgb(0, 0, 0)`;
this.ctx.strokeRect(this.width / n, this.height * (n - 1) / n, this.width * (n - 2) / n, 0); // x axis line
this.ctx.strokeRect(this.width / n, this.height / n, 0, this.width * (n - 2) / n); // y axis line
for (let i = 2; i < n; i += 2) {
let x = i * step;
let y = (n - i) * step;
let xText = Chart.numToPrint(this.minX + i / n * this.deltaX);
let yText = Chart.numToPrint(this.minY + i / n * this.deltaY);
this.ctx.fillText(xText, x - 9, step * (n - 1) + 17); // x axis text
this.ctx.fillText(yText, step - 28, y + 4, 30); // y axis text
this.ctx.fillRect(x - sizeSmall / 2, step * (n - 1) - size / 2, sizeSmall, size); // x axis dots
this.ctx.fillRect(step - size / 2, x - sizeSmall / 2, size, sizeSmall); // y axis dots
}
}
pixelToCoord(x, y) {
return {
x: x / this.width * this.deltaX + this.minX,
y: (1 - y / this.height) * this.deltaY + this.minY,
width: 20 / this.width * this.deltaX,
height: 20 / this.height * this.deltaY
};
}
coordToPixel(x, y) {
return {
x: x === Infinity ? this.width : (x - this.minX) / this.deltaX * this.width,
y: y === Infinity ? 0 : (1 - (y - this.minY) / this.deltaY) * this.height,
};
}
static getRange(values, zeroMin = false, buffer = .1) {
let min = values.length && !zeroMin ? Math.min(...values) : 0;
let max = values.length ? Math.max(...values) : 10;
let delta = max - min + .001;
return [min - delta * buffer, delta + delta * buffer * 2]
}
static numToPrint(n) {
return Math.round(n * 10) / 10;
}
}
let canvas = document.querySelector('canvas');
let chart = new Chart(canvas);
chart.pointSets = [
// e.g. {color: 'rgb(255,0,0)', fill: true, size: 5, points: [{x: 0, y: 1}, ...], isPath: true}
{
color: 'rgb(255,0,0)',
fill: false,
size: 3,
points: [
{x: 0, y: 1},
{x: 1, y: 1},
{x: 1, y: 0},
{x: 0, y: 0},
{x: 0, y: .5},
],
isPath: true,
}, {
color: 'rgb(0,0,255)',
fill: true,
size: 10,
points: [
{x: 0, y: 1},
{x: 0, y: .5},
{x: 3, y: 1},
{x: 6, y: 2},
{x: 7, y: 4},
{x: 6, y: 5},
],
isPath: false,
},
];
chart.resetRange();
canvas {
border: 1px solid;
}
<canvas width="500" height="500"></canvas>
I'm currently learning about the html5 canvas, and I was making a program that rotates a triangle to face wherever the mouse is. it partially works but skips half of it, and i was wondering if there was a better way to do it and also what is wrong with my current code
thanks!
const ctx = document.getElementById("canvas").getContext("2d")
ctx.canvas.style.backgroundColor = "#303030"
ctx.canvas.width = 500
ctx.canvas.height = 500
const w = ctx.canvas.width
const h = ctx.canvas.height
let x = 0;
let y = 0;
let degrees = 0;
triAngle = 60
ctx.canvas.addEventListener("mousemove", mouseMove, false)
function mouseMove(evt) {
x = evt.clientX
y = evt.clientY
let diffX = x - w / 2;
let diffY = y - h / 2;
console.log(diffX, diffY)
degrees = Math.floor(Math.atan(diffY / diffX) * 57.2958);
//Math.atan(diffY/ diffX)
console.log(degrees)
}
function draw() {
debugger ;
ctx.clearRect(0, 0, w, h)
ctx.fillStyle = "#fff";
ctx.save()
ctx.translate(w / 2, h / 2)
ctx.rotate(degree(degrees + triAngle / 2))
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(0, 100)
ctx.rotate(degree(triAngle))
ctx.lineTo(0, 100)
ctx.closePath()
ctx.fill()
ctx.restore()
requestAnimationFrame(draw)
}
function degree(input) {
return Math.PI / 180 * input
}
draw()
https://jsfiddle.net/tus5nxpb/
Math.atan2
The reason that Math.atan skips half the directions is because of the sign of the fraction. The circle has 4 quadrants, the lines from {x: 0, y: 0} to {x: 1, y: 1}, {x: -1, y: 1}, {x: -1, y: -1} and {x: 1, y: -1} result in only two values (1, and -1) if you divide y by x eg 1/1 === 1, 1/-1 === -1, -1/1 === -1, and -1/-1 === 1 thus there is no way to know which one of the 2 quadrants each value 1 and -1 is in.
You can use Math.atan2 to get the angle from a point to another point in radians. In the range -Math.PI to Math.PI (-180deg to 180deg)
BTW there is no need to convert radians to deg as all math functions in JavaScript use radians
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d")
canvas.height = canvas.width = 300;
canvas.style.backgroundColor = "#303030";
const mouse = {x: 0, y: 0};
canvas.addEventListener("mousemove", e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
const shape = {
color: "lime",
x: 150,
y: 150,
size: 50,
path: [1, 0, -0.5, 0.7, -0.5, -0.7],
};
function draw(shape) {
var i = 0;
const s = shape.size, p = shape.path;
ctx.fillStyle = shape.color;
const rot = Math.atan2(mouse.y - shape.y, mouse.x - shape.x);
const xa = Math.cos(rot);
const ya = Math.sin(rot);
ctx.setTransform(xa, ya, -ya, xa, shape.x, shape.y);
ctx.beginPath();
while (i < p.length) { ctx.lineTo(p[i++] * s, p[i++] * s) }
ctx.fill();
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw(shape);
requestAnimationFrame(mainLoop);
}
body { margin: 0px; }
canvas { position: absolute; top: 0px; left: 0px; }
<canvas id="canvas"></canvas>
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 am trying to draw an annulus sector in QML using the Canvas object.
First, I have written the javascript code, and I have verified that it is correct by executing it in a browser.
Here it is:
var can = document.getElementById('myCanvas');
var ctx=can.getContext("2d");
var center = {
x: can.width / 2,
y: can.height / 2
};
var minRad = 100;
var maxRad = 250;
var startAngle = toRad(290);
var endAngle = toRad(310);
drawAxis();
drawSector();
function drawSector() {
var p1 = {
x: maxRad * Math.cos(startAngle),
y: maxRad * Math.sin(startAngle)
}
p1 = toCanvasSpace(p1);
var p2 = {
x: minRad * Math.cos(startAngle),
y: minRad * Math.sin(startAngle)
}
p2 = toCanvasSpace(p2);
var p3 = {
x: minRad * Math.cos(endAngle),
y: minRad * Math.sin(endAngle)
}
p3 = toCanvasSpace(p3);
var p4 = {
x: maxRad * Math.cos(endAngle),
y: maxRad * Math.sin(endAngle)
}
p4 = toCanvasSpace(p4);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(center.x, center.y, maxRad, startAngle, endAngle);
ctx.lineTo(p3.x, p3.y);
ctx.arc(center.x, center.y, minRad, endAngle, startAngle, true);
ctx.closePath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
}
function drawAxis() {
ctx.beginPath();
ctx.moveTo(can.width / 2, 0);
ctx.lineTo(can.width / 2, can.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, can.height / 2);
ctx.lineTo(can.width, can.height / 2);
ctx.stroke();
}
function toRad(degrees) {
return degrees * Math.PI / 180;
}
function toCanvasSpace(p) {
var ret = {};
ret.x = p.x + can.width / 2;
ret.y = p.y + can.height / 2;
return ret;
}
Here you can run the code above.
The output is this:
Next, I moved the same code into a Canvas object in Qml.
See here the main.qml containing the Canvas:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 500
height: 500
x:500
Canvas
{
id: can
anchors.fill: parent
antialiasing: true
onPaint: {
var ctx=can.getContext("2d");
var center = {
x: can.width / 2,
y: can.height / 2
};
var minRad = 100;
var maxRad = 250;
var startAngle = toRad(290);
var endAngle = toRad(310);
drawAxis();
drawSector();
function drawSector() {
var p1 = {
x: maxRad * Math.cos(startAngle),
y: maxRad * Math.sin(startAngle)
}
p1=toCanvasSpace(p1);
var p2 = {
x: minRad * Math.cos(startAngle),
y: minRad * Math.sin(startAngle)
}
p2=toCanvasSpace(p2);
var p3 = {
x: minRad * Math.cos(endAngle),
y: minRad * Math.sin(endAngle)
}
p3=toCanvasSpace(p3);
var p4 = {
x: maxRad * Math.cos(endAngle),
y: maxRad * Math.sin(endAngle)
}
p4=toCanvasSpace(p4);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(center.x, center.y, maxRad, startAngle, endAngle);
ctx.lineTo(p3.x, p3.y);
ctx.arc(center.x, center.y, minRad, endAngle, startAngle, true);
ctx.closePath();
ctx.strokeStyle="blue";
ctx.lineWidth=2;
ctx.stroke();
}
function drawAxis() {
ctx.beginPath();
ctx.moveTo(can.width / 2, 0);
ctx.lineTo(can.width / 2, can.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, can.height / 2);
ctx.lineTo(can.width, can.height / 2);
ctx.stroke();
}
function toRad(degrees) {
return degrees * Math.PI / 180;
}
function toCanvasSpace(p) {
var ret = {};
ret.x = p.x + can.width / 2;
ret.y = p.y + can.height / 2;
return ret;
}
}
}
}
In this case I get this output:
As you can see there is an imperfection at the bottom.
I really don't understand why there is that imperfection; moreover I don't understand why the same code gives different output.
Any help is appreciated!
Thanks
The lineTo p3 is not needed, because when an arc segment is drawn, the connecting line is drawn automatically, according to the Canvas specifications:
The arc() method is equivalent to the ellipse() method in the case
where the two radii are equal. [...]
When the ellipse() method is invoked, it must proceed as follows.
First, if the object's path has any subpaths, then the method must add
a straight line from the last point in the subpath to the start point
of the arc.
Also, the moveTo p1 is not needed, because it would be done as part of the first arc.
As to why the extra line is being drawn further than the start of the second arc, it could be a bug in Qt (maybe a division by 0 issue - just guessing here), or maybe you simply did not compute its position correctly.
I have 2 ellipses and I need to detect any overlap between them.
Here is an example of detecting overlap between two circles, and I am looking for something similar for ellipses:
var circle1 = {radius: 20, x: 5, y: 5};
var circle2 = {radius: 12, x: 10, y: 5};
var dx = circle1.x - circle2.x;
var dy = circle1.y - circle2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < circle1.radius + circle2.radius) {
// collision !
}
For ellipses, I have the same variable because my radius on the vertical axis is 2 times smaller as the radius on the horizontal axis:
var oval1 = {radius: 20, x: 5, y: 5};
var oval2 = {radius: 12, x: 10, y: 5};
// what comes here?
if ( /* condition ? */ ) {
// collision !
}
var result = document.getElementById("result");
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// First eclipse
var eclipse1 = { radius: 20,
x: 100,
y: 40 };
// Second eclipse
var eclipse2 = { radius: 20,
x: 120,
y: 65 };
function have_collision( element1, element2 )
{
var dx = element1.x - element2.x;
var dy = element1.y - element2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= element1.radius + element2.radius) {
return true;
}
else {
return false;
}
}
function draw( element ) {
// http://scienceprimer.com/draw-oval-html5-canvas
context.beginPath();
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = element.x - (element.radius/2 * Math.sin(i)) * Math.sin(0 * Math.PI) + (element.radius * Math.cos(i)) * Math.cos(0 * Math.PI);
yPos = element.y + (element.radius * Math.cos(i)) * Math.sin(0 * Math.PI) + (element.radius/2 * Math.sin(i)) * Math.cos(0 * Math.PI);
if (i == 0) {
context.moveTo(xPos, yPos);
} else {
context.lineTo(xPos, yPos);
}
}
context.fillStyle = "#C4C4C4";
context.fill();
context.lineWidth = 2;
context.strokeStyle = "#FF0000";
context.stroke();
context.closePath();
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(e) {
var mousePos = getMousePos(canvas, e);
eclipse2.x = mousePos.x;
eclipse2.y = mousePos.y;
result.innerHTML = 'Collision ? ' + have_collision( eclipse1, eclipse2 );
context.clearRect(0, 0, canvas.width, canvas.height);
draw( eclipse1 );
draw( eclipse2 );
}, false);
draw( eclipse1 );
draw( eclipse2 );
result.innerHTML = 'Collision ? ' + have_collision( eclipse1, eclipse2 );
#canvas {
border: solid 1px rgba(0,0,0,0.5);
}
<canvas id="canvas"></canvas>
<p id="result"></p>
<code>distance = Math.sqrt(dx * dx + dy * dy);</code>
As your ellipses are very specific, in that they are just circles shrunk along the Y axis, you can just imagine what would happen if you would stretch the plane along the Y-axis with a factor 2. You would agree that those overlapping ellipses would become overlapping circles, and those that did not overlap will also not overlap once stretched. You could imagine it as if the ellipses were drawn on a elastic material, and you just stretch the material in vertical direction: this will of course not change any overlapping condition.
So, you could write this:
var stretchedDistance = Math.sqrt(dx * dx + 2 * dy * 2 * dy);
... and continue with the condition as it stands, because it is based on the radius in the X-direction, which, after stretching, also is the radius in the Y-direction. Of course, I named the variable differently, so you should test with that variable. So to complete the code, we get:
var stretchedDistance = Math.sqrt(dx * dx + 4 * dy * dy);
if (stretchedDistance < circle1.radius + circle2.radius) {
// collision !
}
Note that the stretching is taken into account by multiplying dy with 2. In the distance formula it is equivalent and shorter to write 4 * dy * dy.
Here is the nice interactive fiddle you created, with my update to it.