user2963022
user2963022

Reputation: 175

HTML5 canvas divide arc into 7 equal parts and rotate

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

Answers (2)

Blindman67
Blindman67

Reputation: 54089

Creating animation entities.

Thought the given answer works it is not the best way to solve the problem.

The entity AKA Object.

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.

Use the canvas transform.

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.

In practice.

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

traktor
traktor

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

Related Questions