When I rotate an object how do I rotate back? - actionscript-3

I'm on a code/maths task where I need to rotate a shape to 0 degrees and then back to whatever degrees it was at in the first place.
The following method:
function rotateAroundOrigin(xPos:Number, yPos:Number, angle:Number, origin:Point):Point {
var point:Point = new Point();
angle *= Math.PI / 180;
point.x = Math.cos(angle) * (xPos - origin.x) + Math.sin(angle) * (yPos - origin.y) + origin.x;
point.y = Math.sin(angle) * (xPos - origin.x) - Math.cos(angle) * (yPos - origin.y) + origin.y;
return point;
}
Is invoked twice like so:
var rotatePoint:Point = rotateAroundOrigin(30, 30, -90, midPoint);
var rotatePointBack:Point = rotateAroundOrigin(30, 30, -90, midPoint);
It works fine but it doesn't make sense that it works because the degrees value is "-90" both times it's rotated. It should be "-90" to rotate one way and then "90" to rotate back to where it was in the first place.
Anyone have any ideas how I can modify the "rotateAroundOrigin" method so you can invoke with "-90" then with "90" like so?
var rotatePoint:Point = rotateAroundOrigin(30, 30, -90, midPoint);
var rotatePointBack:Point = rotateAroundOrigin(30, 30, 90, midPoint);

var rotatePoint:Point = rotateAroundOrigin(30, 30, -90, midPoint);
var rotatePointBack:Point = rotateAroundOrigin(30, 30, -90, midPoint);
It looks like you're just doing the same thing twice: rotating the point (30, 30) around midPoint. Consider this:
var rotatePoint:Point = rotateAroundOrigin(30, 30, -90, midPoint);
var rotatePointBack:Point = rotateAroundOrigin(rotatePoint.x, rotatePoint.y, 90, midPoint);
EDIT: I also noticed that you mixed up some signs in the rotation method. Try this:
function rotateAroundOrigin(xPos:Number, yPos:Number, angle:Number, origin:Point):Point {
var point:Point = new Point();
angle *= Math.PI / 180;
point.x = Math.cos(angle) * (xPos - origin.x) - Math.sin(angle) * (yPos - origin.y) + origin.x;
point.y = Math.sin(angle) * (xPos - origin.x) + Math.cos(angle) * (yPos - origin.y) + origin.y;
return point;
}

Related

Draw Line Arrowhead Without Rotating in Canvas

Most code to drawing arrowheads in html canvas involves rotating the canvas context and drawing the lines.
My use case is to draw them using trigonometry without rotating the canvas. or is that vector algorithm you call it? Help is appreciated.
This is what I have (forgot where I got most of the code). Draws 2 arrowheads on start and end based on the last 2 parameters arrowStart and arrowEnd which are boolean.
drawLineArrowhead: function(context, arrowStart, arrowEnd) {
// Place start end points here.
var x1 = 0;
var y1 = 0;
var x2 = 0;
var y2 = 0;
var distanceFromLine = 6;
var arrowLength = 9;
var dx = x2 - x1;
var dy = y2 - y1;
var angle = Math.atan2(dy, dx);
var length = Math.sqrt(dx * dx + dy * dy);
context.translate(x1, y1);
context.rotate(angle);
context.beginPath();
context.moveTo(0, 0);
context.lineTo(length, 0);
if (arrowStart) {
context.moveTo(arrowLength, -distanceFromLine);
context.lineTo(0, 0);
context.lineTo(arrowLength, distanceFromLine);
}
if (arrowEnd) {
context.moveTo(length - arrowLength, -distanceFromLine);
context.lineTo(length, 0);
context.lineTo(length - arrowLength, distanceFromLine);
}
context.stroke();
context.setTransform(1, 0, 0, 1, 0, 0);
},
See the code below, just a bit of trigonometry.
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.lineCap = "round";
ctx.lineWidth = 5;
function drawLineArrowhead(p1, p2, startSize, endSize) {
ctx.beginPath()
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
if (startSize > 0) {
lineAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
delta = Math.PI/6
for (i=0; i<2; i++) {
ctx.moveTo(p1.x, p1.y);
x = p1.x + startSize * Math.cos(lineAngle + delta)
y = p1.y + startSize * Math.sin(lineAngle + delta)
ctx.lineTo(x, y);
delta *= -1
}
}
if (endSize > 0) {
lineAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x);
delta = Math.PI/6
for (i=0; i<2; i++) {
ctx.moveTo(p2.x, p2.y);
x = p2.x + endSize * Math.cos(lineAngle + delta)
y = p2.y + endSize * Math.sin(lineAngle + delta)
ctx.lineTo(x, y);
delta *= -1
}
}
ctx.stroke();
}
drawLineArrowhead({x:10, y:10}, {x:100, y:20}, 0, 30)
drawLineArrowhead({x:20, y:25}, {x:140, y:120}, 20, 20)
drawLineArrowhead({x:140, y:20}, {x:80, y:50} , 20, 0)
drawLineArrowhead({x:150, y:20}, {x:150, y:90}, 20, 5)
drawLineArrowhead({x:180, y:90}, {x:180, y:20}, 20, 5)
drawLineArrowhead({x:200, y:10}, {x:200, y:140}, 10, 10)
drawLineArrowhead({x:220, y:140}, {x:220, y:10}, 10, 20)
<canvas id="canvas">
If you run it you should see a few samples.
The drawLineArrowhead has 4 parameters (p1, p2, startSize, endSize)
the first two are the starting-point and end-point of the line, the last two are arrow size, just to give some control to the final user over how big are those arrows at the end, if we want to remove them we set to 0.

Pulse animation in canvas

I am trying to make various shapes have a pulse like effect in canvas and managed to do it with a circle,
function drawCircle() {
// color in the background
context.fillStyle = "#EEEEEE";
context.fillRect(0, 0, canvas.width, canvas.height);
// draw the circle
context.beginPath();
var radius = 25 + 20 * Math.abs(Math.cos(angle)); //radius of circle
context.arc(25, 25, radius, 0, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the circle
context.fillStyle = "#006699";
context.fill();
//'pulse'
angle += Math.PI / 220;
requestAnimationFrame(drawCircle);
}
drawCircle();
but I'm not sure how to go about doing any other shape. What I have so far for my triangle is
function drawTriangle() {
// draw the triangle
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.fill();
context.rect(215, 100, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the triangle
context.fillStyle = "#3f007f";
context.fill();
//'pulse'
angle += Math.PI / 280;
requestAnimationFrame(drawTriangle);
}
drawTriangle();
Any insight would be appreciated.
This can be simply achieved by changing the scale of the context matrix.
All you need to find is the position of the scaling anchor of your shape so that you can translate the matrix to the correct position after the scale has been applied.
In following example, I'll use the center of the shape as scaling anchor, since it seems it is what you wanted.
The extended version of the matrix transformations would be
ctx.translate(anchorX, anchorY);
ctx.scale(scaleFactor, scaleFactor);
ctx.translate(-anchorX, -anchorY);
which in below example has been reduced to
ctx.setTransform(
scale, 0, 0,
scale, anchorX - (anchorX * scale), anchorY - (anchorY * scale)
);
var ctx = canvas.getContext('2d');
var angle = 0;
var scale = 1;
var img = new Image();
img.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png';
anim();
function anim() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateScale();
drawCircle();
drawTriangle();
drawImage();
ctx.setTransform(1, 0, 0, 1, 0, 0);
requestAnimationFrame(anim);
}
function updateScale() {
angle += Math.PI / 220;
scale = 0.5 + Math.abs(Math.cos(angle));
}
function drawCircle() {
ctx.beginPath();
var cx = 75,
cy = 50,
radius = 25;
// for the circle, centerX and centerY are given
var anchorX = cx,
anchorY = cy;
// with these anchorX, anchorY and scale,
// we can determine where we need to translate our context once scaled
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
// then we apply the matrix in one go
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
// and we draw normally
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
}
function drawTriangle() {
ctx.beginPath();
// for the triangle, we need to find the position between minX and maxX,
// and between minY and maxY
var anchorX = 175 + (200 - 175) / 2,
anchorY = 25 + (75 - 25) / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.moveTo(175, 50);
ctx.lineTo(200, 75);
ctx.lineTo(200, 25);
ctx.fill();
}
function drawImage() {
if (!img.naturalWidth) return;
// for rects, it's just pos + (length / 2)
var anchorX = 250 + img.naturalWidth / 2,
anchorY = 25 + img.naturalHeight / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.drawImage(img, 250, 25);
}
<canvas id="canvas" width="500"></canvas>

Chart Label text rotation

I am using very similar code to create a pie chart using canvas as per this article:
http://wickedlysmart.com/how-to-make-a-pie-chart-with-html5s-canvas/
As you can see from this image, there are cases where the labels are upside down:
Here is the code that writes the labels to the graph:
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var degrees = sumTo(data, i);
var angle = degreesToRadians(degrees);
context.translate(x, y);
context.rotate(angle);
context.textAlign = 'right';
var fontSize = Math.floor(canvas.height / 32);
context.font = fontSize + 'pt Helvetica';
var dx = Math.floor(canvas.width * 0.3) - 20;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};
I am trying to rectify this so the text is always readable and not upside down but cant work out how to do it!
Here's my solution! (A little kludgey but seems to work on the basic example, I haven't tested in on edge cases...)
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var angle;
var angleD = sumTo(data, i);
var flip = (angleD < 90 || angleD > 270) ? false : true;
context.translate(x, y);
if (flip) {
angleD = angleD-180;
context.textAlign = "left";
angle = degreesToRadians(angleD);
context.rotate(angle);
context.translate(-(x + (canvas.width * 0.5))+15, -(canvas.height * 0.05)-10);
}
else {
context.textAlign = "right";
angle = degreesToRadians(angleD);
context.rotate(angle);
}
var fontSize = Math.floor(canvas.height / 25);
context.font = fontSize + "pt Helvetica";
var dx = Math.floor(canvas.width * 0.5) - 10;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};
To display the text in the correct way you have to check if the rotation angle is between 90 and 270 degree. If it is then you know the text will be display upside down.
To switch it correctly you then have to rotate you canvas of planed rotation - 180 degree and then to align it in left not right :
var drawSegmentLabel = function(canvas, context, i) {
context.save();
var x = Math.floor(canvas.width / 2);
var y = Math.floor(canvas.height / 2);
var degrees = sumTo(data, i);
var angle = 0;
if (degree > 90 && degree < 270)
angle = degreesToRadians(degrees - 180);
else
angle = degreesToRadians(degrees);
context.translate(x, y);
context.rotate(angle);
context.textAlign = 'right';
var fontSize = Math.floor(canvas.height / 32);
context.font = fontSize + 'pt Helvetica';
var dx = Math.floor(canvas.width * 0.3) - 20;
if (degree > 90 && degree < 270)
dx = 20;
var dy = Math.floor(canvas.height * 0.05);
context.fillText(labels[i], dx, dy);
context.restore();
};

How to divide a circle into three equal parts with HTML5 canvas?

How can I divide a circle into three equal parts with HTML5 canvas 2D context API like above figure?
I was trying this
Can somebody suggest a better way? probably with percentages (or in degrees) instead of hard-coded coordinates?
var can = document.getElementById('mycanvas');
var ctx = can.getContext('2d');
ctx.fillStyle = "#BD1981";
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "#FFC8B2";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(350, 200);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.stroke();
Here is a function (demo) that allows you to specify a starting point, the length and the angle in degrees:
var drawAngledLine = function(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
Putting it all together (using #phant0m's drawAngledLine):
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 70;
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
drawCircle(140, 140, RADIUS);
drawAngledLine(140, 140, RADIUS, 1 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 2 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 3 * (360 / 3));
Demo here:
http://jsfiddle.net/My8eX/
I know you probably got your answer but I found Wayne's jsfiddle helpful so I'm adding my contribution which lets you set a custom number of sections you want to divide the circle into.
http://jsfiddle.net/yorksea/3ef0y22c/2/
(also using #phant0m's drawAngledLine)
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 300;
var num_sections = 19; //set this for number of divisions
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
//draw circle outline
drawCircle(320, 320, RADIUS);
//loop the number of sections to draw each
for (i = 1; i <= num_sections; i++) {
drawAngledLine(320, 320, RADIUS, i * (360 / num_sections));
}
<canvas id="canvas" width="650" height="650"></canvas>

canvas.onmousedown function to add a shape won't work

I have some code, which can be seen below. At the bottom is a block of code to add a shape. For some reason it won't work unless the very first lines of code are different. Up until I added the 'addShape' code, it was all working fine, so I wandered if anyone on here could have a look and perhaps figure out a solution?
Cheers
Jon
EDIT Also available on jsFiddle http://jsfiddle.net/pukster/mfNq4/1/
$(document).ready(function() {
var canvas = $('#myCanvas');
var ctx = canvas.get(0).getContext("2d");
var context = new webkitAudioContext();
var canvasWidth = canvas.width();
var canvasHeight = canvas.height();
$(window).resize(resizeCanvas);
function resizeCanvas() {
canvas.attr("width", $(window).get(0).innerWidth - 2);
canvas.attr("height", $(window).get(0).innerHeight - 2);
canvasWidth = canvas.width();
canvasHeight = canvas.height();
};
resizeCanvas();
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.lineWidth = 2;
var playAnimation = true;
var Ring = function(x, y, radius, vx, vy) {
this.x = x;
this.y = y;
this.radius = radius;
this.vx = vx;
this.vy = vy;
};
var rings = [];
for (var i = 0; i < 10; i++) {
var x = Math.random()*ctx.canvas.width;
var y = Math.random()*ctx.canvas.height;
var vx = Math.random()*6-3;
var vy = Math.random()*6-3;
rings.push(new Ring(x, y, 40, vx, vy));
};
function animate() {
var ringsLength = rings.length;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i = 0; i < ringsLength; i++) {
var tmpRing = rings[i];
for (var j = i+1; j < ringsLength; j++) {
var tmpRingB = rings[j];
var dx = tmpRingB.x - tmpRing.x;
var dy = tmpRingB.y - tmpRing.y;
var dist = Math.sqrt((dx * dx) + (dy * dy));
if(dist < tmpRing.radius + tmpRingB.radius) {
var sinewave = new SineWave(context);
var angle = Math.atan2(dy, dx);
var sine = Math.sin(angle);
var cosine = Math.cos(angle);
var x = 0;
var y = 0;
var xb = dx * cosine + dy * sine;
var yb = dy * cosine - dx * sine;
var vx = tmpRing.vx * cosine + tmpRing.vy * sine;
var vy = tmpRing.vy * cosine - tmpRing.vx * sine;
var vxb = tmpRingB.vx * cosine + tmpRingB.vy * sine;
var vyb = tmpRingB.vy * cosine - tmpRingB.vx * sine;
vx *= -1;
vxb *= -1;
xb = x + (tmpRing.radius + tmpRingB.radius);
tmpRing.x = tmpRing.x + (x * cosine - y * sine);
tmpRing.y = tmpRing.y + (y * cosine + x * sine);
tmpRingB.x = tmpRing.x + (xb * cosine - yb * sine);
tmpRingB.y = tmpRing.y + (yb * cosine + xb * sine);
tmpRing.vx = vx * cosine - vy * sine;
tmpRing.vy = vy * cosine + vx * sine;
tmpRingB.vx = vxb * cosine - vyb * sine;
tmpRingB.vy = vyb * cosine + vxb * sine;
tmpRing.loop = true;
};
};
tmpRing.x += tmpRing.vx;
tmpRing.y += tmpRing.vy;
if (tmpRing.x - tmpRing.radius < 0) {
var sinwave = new SinWave(context);
tmpRing.x = tmpRing.radius;
tmpRing.vx *= -1;
} else if (tmpRing.x + tmpRing.radius > ctx.canvas.width) {
var sinwave = new SinWave(context);
tmpRing.x = ctx.canvas.width - tmpRing.radius;
tmpRing.vx *= -1;
};
if (tmpRing.y - tmpRing.radius < 0) {
var sinwave = new SinWave(context);
tmpRing.y = tmpRing.radius;
tmpRing.vy *= -1;
} else if (tmpRing.y + tmpRing.radius > ctx.canvas.height) {
var sinwave = new SinWave(context);
tmpRing.y = ctx.canvas.height - tmpRing.radius;
tmpRing.vy *= -1;
};
ctx.beginPath();
ctx.arc(tmpRing.x, tmpRing.y, 40, 0, Math.PI*2, false);
ctx.closePath();
ctx.stroke();
//-------------------- The addRing Function Code --------------------//
var mx, my;
var offsetX, offsetY;
//canvas.onmousedown = sglClick;
function addRing(x, y, radius, vx, vy) {
var x = mx-5;
var y = my-5;
var vx = Math.random()*6-3;
var vy = Math.random()*6-3;
rings.push(new Ring(x, y, 40, vx, vy));
}
function sglClick(e) {
getMouse(e);
addRing();
}
function getMouse(e) {
var element = ctx, offsetX = 0, offsetY = 0;
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
}
};
if(playAnimation) {
setTimeout(animate, 33);
};
};
animate();
});
I noticed a couple of problems.
First, you misspelled SineWave in a few places. Second, you are trying to bind an event to the canvas using canvas.onmousedown = sglClick;. You should try canvas.bind('mousedown', sglClick); instead and you shouldn't do the binding inside of your animate method. It will add a new event each iteration of the animation.