Reputation: 175
I am having a problem getting a circle to split up into 7 pieces. At the moment i have a single line going through the center of the circle. It is then rotating and bouncing off of the walls of the canvas. I can't seem to figure out to draw 7 equal segments and have them rotate within the circle. Please see the snippet for what i have so far. any help with this would be greatly appreciated.
Thank's in advance.
<!DOCTYPE html>
<hmtl>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<!--change cnavas border color to black-->
<style type="text/css">
canvas{
border: 1px solid black;
}
</style>
</head>
<body>
<!-- Canvas one used as container for canvas-->
<canvas id="canvasOne" ></canvas>
<script type="text/javascript">
var canvas = document.getElementById("canvasOne");
var me = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
var animation;
var centerX = 125;
var centerY =125;
var radius = 100;
var ballDx = 2;
var ballDy = 2;
var theta = 0;
var thetaInc = 0.01;
function drawBall(){
me.clearRect(0,0,canvas.width,canvas.height);
centerX = centerX + ballDx;
centerY = centerY + ballDy;
me.beginPath();
me.arc(centerX,centerY,radius,0,Math.PI*2,false);
me.stroke();
me.fillStyle = "orange";
me.fill();
theta += thetaInc;
me.moveTo(centerX - radius*Math.cos(theta),centerY - radius*Math.sin(theta));
me.lineTo(centerX + radius*Math.cos(theta),centerY + radius*Math.sin(theta));
me.lineWidth = "2";
me.lineCap = "round";
me.strokeStyle = "black";
me.stroke();
if(centerY > canvas.height - radius || centerY - radius <0){
ballDy = -1*ballDy;
}
if(centerX > canvas.width - radius || centerX - radius < 0){
ballDx = -1*ballDx;
}
}
function animate(){
clearInterval(animation);
setInterval(drawBall,25);
}
animate();
</script>
</body>
</html>
Upvotes: 0
Views: 733
Reputation: 54089
Thought the given answer works it is not the best way to solve the problem.
What if you wanted to add some text that is rotated as well, or an image, or any other graphical content associated with the ball. What if you wanted to have the ball squash a little when it hits the walls. These things are all difficult if you include orientation, and position as part of the rendering code.
You should think of each item you draw as an independent entity (eg the ball) and create an object that describes the ball, including its behaviour update
function, style and render function draw
That entity has its own local coordinate system and is drawn around its own center (0,0).
const ballStyle = {
fillStyle : "orange",
lineWidth : "2",
lineCap : "round",
strokeStyle : "black",
};
const ball = {
x : 125,
y : 125,
radius : 100,
scale : 1,
dx : 2,
dy : 2,
rot : 0,
dRot : 0.1,
segments : 7,
style : ballStyle,
draw : drawBall,
}
function drawBall(){
var i;
const step = Math.PI * 2 / this.segments;
Object.assign(ctx,this.style);
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fill();
for(i = 0; i < this.segments; i ++){
ctx.moveTo(0,0);
ctx.lineTo(Math.cos(i * step) * this.radius, Math.sin(i * step) * this.radius);
}
ctx.stroke();
}
Thus if you call the function ball.draw()
the ball is drawn at the top left of the canvas at its own coordinate system. This is not what you want.
This is where you use the canvas transform to position and rotate the object.
So create a general purpose function that will set the position, scale, and rotation of the object you want to draw.
function drawObject(ball) {
ctx.setTransform(ball.scale, 0, 0, ball.scale, ball.x, ball.y); // set position and scale
ctx.rotate(ball.rotation);
ball.draw();
}
Now you can render the object where you want and the position, scale and rotation do not affect the rendering code.
The snippet does what is described above. I have added a rectangle to the ball just to illustrate that the rotation does not need to have extra code to add more detail to the object. There is also a second ball (copied from the original) to illustrate that once you have set up an object making copies of it is easy.
Also when you animate you should never use setInterval
as it is not in sync with the display hardware. Use requestAnimationFrame
as shown in the snippet
requestAnimationFrame(mainLoop); // start the animation at the next frame
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
const ballStyle = {
fillStyle: "orange",
lineWidth: "2",
lineCap: "round",
strokeStyle: "black",
};
const ball = {
x: 125,
y: 155,
radius: 100,
scale: 1,
dx: 2,
dy: 2.5,
rotation: 0,
dRot: 0.02,
segments: 7,
style: ballStyle,
draw: drawBall,
update: updateBall,
}
function updateBall() {
this.x += this.dx;
this.y += this.dy;
this.rotation += this.dRot;
var r = this.radius * this.scale;
if (this.x - r < 0) {
this.dx = Math.abs(this.dx);
this.x = r;
} else if (this.x + r > ctx.canvas.width) {
this.dx = -Math.abs(this.dx);
this.x = ctx.canvas.width - r;
}
if (this.y - r < 0) {
this.dy = Math.abs(this.dy);
this.y = r;
} else if (this.y + r > ctx.canvas.height) {
this.dy = -Math.abs(this.dy);
this.y = ctx.canvas.height - r;
}
}
function drawBall() {
var i;
const step = Math.PI * 2 / this.segments;
Object.assign(ctx, this.style);
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.rect(this.radius - 22, -5, 20, 10);
ctx.fill();
for (i = 0; i < this.segments; i++) {
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(i * step) * this.radius, Math.sin(i * step) * this.radius);
}
ctx.stroke();
}
// will draw any object that has the properties x,y,scale and rotation and the function draw.
function drawObject(ball) {
ctx.setTransform(ball.scale, 0, 0, ball.scale, ball.x, ball.y); // set position and scale
ctx.rotate(ball.rotation);
ball.draw();
}
// create a copy of the ball.
const ball1 = Object.assign(
{},
ball,
{
scale : 0.5,
segments : 9,
dx : -2,
dRot : - 0.02,
style : Object.assign(
{},
ballStyle,
{
lineWidth : 4,
fillStyle : "yellow"
}
),
}
);
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default transform
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ball.update();
ball1.update();
drawObject(ball);
drawObject(ball1);
requestAnimationFrame(mainLoop);
}
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
Upvotes: 0
Reputation: 19344
If I understand it correctly you are nearly there, but instead of drawing a single line from a point on the circle to one diametrically opposite, start from the center and draw seven radii starting at angle theta
with angular increments of 1/7th a circle.
Because moveTo
starts a new sub path on the canvas, you only need to stroke the radii after drawing all of them. As an example of simple modifications to achieve the result:
<!DOCTYPE html>
<hmtl>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<!--change cnavas border color to black-->
<style type="text/css">
canvas{
border: 1px solid black;
}
</style>
</head>
<body>
<!-- Canvas one used as container for canvas-->
<canvas id="canvasOne" ></canvas>
<script type="text/javascript">
var canvas = document.getElementById("canvasOne");
var me = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
var animation;
var centerX = 125;
var centerY =125;
var radius = 100;
var ballDx = 2;
var ballDy = 2;
var theta = 0;
var thetaInc = 0.01;
var seventh = (Math.PI*2)/7; // add
var theta2 = 0; // add
function drawBall(){
me.clearRect(0,0,canvas.width,canvas.height);
centerX = centerX + ballDx;
centerY = centerY + ballDy;
me.beginPath();
me.arc(centerX,centerY,radius,0,Math.PI*2,false);
me.stroke();
me.fillStyle = "orange";
me.fill();
theta += thetaInc;
/* removed:
me.moveTo(centerX - radius*Math.cos(theta),centerY - radius*Math.sin(theta));
me.lineTo(centerX + radius*Math.cos(theta),centerY + radius*Math.sin(theta));
*/
for( var n = 0; n < 7; ++n) { // add loop to draw radii
theta2 = theta + n * seventh;
me.moveTo( centerX, centerY);
me.lineTo( centerX + radius*Math.cos(theta2), centerY + radius*Math.sin(theta2));
}
me.lineWidth = "2";
me.lineCap = "round";
me.strokeStyle = "black";
me.stroke();
if(centerY > canvas.height - radius || centerY - radius <0){
ballDy = -1*ballDy;
}
if(centerX > canvas.width - radius || centerX - radius < 0){
ballDx = -1*ballDx;
}
}
function animate(){
clearInterval(animation);
setInterval(drawBall,25);
}
animate();
</script>
</body>
</html>
If, however, you need to color the segments separately, you would need to draw each segment as an individual path of two radii and an arc of 2π/7 radians before stroking or filling.
Upvotes: 1