Related
I'm trying to create a JavaScript object that has a method which allows a rectangle to rotate around its own origin during a rAF callback.
Things I have done:
Calculating the origin of an object within the canvas space.
Using ctx.save() and ctx.restore() - this is where my issues arise.
When I use the save() and restore() methods to push and pop the saved states within method calls for different objects it either doesn't change anything, or stops the entire animation.
The rotation in my example appears to be applied globally to the canvas (which is how the functionality is specified on MDN). I'm trying to translate around origin around multiple instances. I've spent hours on this.
Is there something going on with the inheritance mechanism in JavaScript that's not resetting my transforms for different instances of the rectangle objects in the code example?
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
// draws a rectangle at given coordinates
this.draw = function() {
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
ctx.restore();
}
// rotates the rectangle around it's center relative to a given xy position
this.rotateRect = function() {
ctx.save();
ctx.translate(this.x + this.w * 0.5, this.y + this.h * 0.5);
ctx.rotate(this.radians);
ctx.translate(-this.x -this.w * 0.5, -this.y - this.h * 0.5);
//ctx.restore()
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
redRectangle.rotateRect();
}
mainAnimationLoop();
I have tried rotating multiple rectangles around their own origin at different positions without animation using save() and restore() - which worked.
Additionally, I have tried moving the rotate method inside of the draw method and the results were the same. My rationale was that the rotation would be applied as a function call within draw() - the rationale was clearly wrong.
Any insight towards a solution would be greatly helpful. I have included a link to the pen on codepen to see the concept in motion.
Instead of drawing the rects at (this.x, this.y) you may draw them at 0,0 and translate them to (this.x, this.y);
// author: Nicholas Fazzolari
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var xCenterCanvas = innerWidth/2;
var yCenterCanvas = innerHeight/2;
// custom rectangle object
function RectangleCustom(x, y, w, h, color) {
this.w = w;
this.h = h;
this.x = x;
this.y = y;
this.color = color;
this.radians = (Math.PI/180) * 2; // change the last value to change speed
this.rotation = 0;
// draws a rectangle at given coordinates
this.draw = function() {
this.rotation += this.radians;
ctx.save();
ctx.fillStyle = this.color;
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillRect(0,0, this.w, this.h);
ctx.restore();
}
this.update = function() {
// animation updates
}
}
// singleton rectangles
var bkgRectangle = new RectangleCustom(0, 0, innerWidth, innerHeight, "#212121");
var redRectangle = new RectangleCustom(xCenterCanvas - 64, yCenterCanvas - 64, 128, 128, "#F44336");
// main animation loop
function mainAnimationLoop() {
// runs animation and clears the canvas each call
requestAnimationFrame(mainAnimationLoop);
ctx.clearRect(0, 0, innerWidth, innerHeight);
bkgRectangle.draw();
redRectangle.draw();
}
mainAnimationLoop();
<canvas></canvas>
I want to have it so that when i create a "component" i can set its radius to make it curved. Below is my code for component create:
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.color = color;
this.update = function() {
ctx = GameArena.context;
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
as you can see it specifies the width height, color and x and y positions but i can't find a way to give it a radius. The other end of my code that uses this component function is here:
PaintBrush = new component(30, 30, "Blue", 30, 320);
Help would be appreciated!
Drawing a rectangle with rounded corners can be done using arcs instead:
The arc takes the arguments:
arc(x, y, radius, startAngle, endAngle [,ccw]); // we won't need counter-clockwise
For example:
var pi2 = Math.PI * 2; // 360 deg.
var r = this.radius, w = this.width, h = this.height;
// ...
// draw rounded rectangle
ctx.beginPath();
ctx.arc(r, r, r, pi2*0.5, pi2*0.75); // top-left
ctx.arc(r+w-r*2, r, r, pi2*0.75, pi2); // top-right
ctx.arc(r+w-r*2, r+h-r*2, r, 0, pi2*0.25); // bottom-right
ctx.arc(r, r+h-r*2, r, pi2*0.25, pi2*0.5); // bottom-left
This simply draws four arc in each corner using radius and start and end angle. Since we use a single path lines will be drawn between each arc from the end of the previous arc to the beginning of the new one - which is why the order matters.
Simply fill() to close the path and fill the shape. If you want to stroke() it as well remember to use closePath() first. If you have paths added later via other objects etc., also remember to use beginPath() before you add them.
The line setting radius will also clamp it to the minimum size possible:
this.radius = Math.min(radius, Math.min(width, height)/2);
First the minimum of height and width is used divided on two. Then the minimum of radius and this result. This makes sure the radius can't be larger then half of the shortest side which would be "impossible".
A note on the setTransform() usage below - if you don't have accumulated transforms this should work fine. If you do and can't easily change it replace the setTransform()s with ctx.translate(this.x, this.y) and after finished reverse it by calling ctx.translate(-this.x, -this.y);. I would recommend using setTransforms for all your objects hover if they are transformed (rotated, scaled etc.) somehow.
Demo
var GameArena = {context: c.getContext("2d")}; // dummy
var PaintBrush = new component(200, 100, "#37f", 10, 10, 16);
PaintBrush.update();
function component(width, height, color, x, y, radius) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.radius = Math.min(radius, Math.min(width, height)/2); // clamp radius
this.color = color;
this.update = function() {
var pi2 = Math.PI * 2; // 360 deg.
var r = this.radius, w = this.width, h = this.height;
ctx = GameArena.context;
ctx.fillStyle = this.color;
ctx.setTransform(1,0,0,1,this.x, this.y); // transform (absolute here)
// draw rounded rectangle
ctx.beginPath();
ctx.arc(r , r , r, pi2*0.5 , pi2*0.75); // top-left
ctx.arc(w-r, r , r, pi2*0.75, pi2); // top-right
ctx.arc(w-r, h-r, r, 0 , pi2*0.25); // bottom-right
ctx.arc(r , h-r, r, pi2*0.25, pi2*0.5); // bottom-left
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
}
<canvas id=c></canvas>
I'm trying to use a gradient to fill an area of a canvas, but I would like to be able to set the angle of the gradient.
I know this is possible by using different values in the creation of the gradient (ctx.createLinearGradient(x1, y1, x2, y2)) as seen here:
But I can't seem to get my head around the maths needed to convert an angle (radians) to a gradient size that will produce the same angle (The angle I'm referring to is perpendicular to the direction of the gradient, so a 0 radian angle would show the gradient on the right)
In short, how can I convert (quantity) of radians into an X by Y shape?
$(document).ready(function(){
var canvas = document.getElementById("test");
var ctx = canvas.getContext("2d");
var angle = 0.5;
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.arc(100, 100, 100, 0, -angle, true);
ctx.lineTo(100, 100);
ctx.closePath();
// Convert angle into coordinates to tilt the grad
// grad should be perpendicular to the top edge of the arc
var grad = ctx.createLinearGradient(0, 0, 0, 100);
grad.addColorStop(0, "rgba(0,0,0,0)");
grad.addColorStop(1, "rgba(0,0,0,0.8)");
ctx.fillStyle = grad;
ctx.fill();
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="test" width="500" height="500"></canvas>
(So no one wastes their time: I specifically don't want to use a context.rotate() in this case)
You can use the angle with cos and sin to define the line that gives the gradient. The only thing left then is to give the length:
var length = 100, angle = 0;
ctx.createLinearGradient(x, y, x + Math.cos(angle) * length, y + Math.sin(angle) * length);
The gradient will be rendered along (perpendicular) to the line given.
Not stated, but if you need to calculate the length of the line depending on the angle and box you can use the law of sines to do so (used in this way). The example below uses a fixed radius. You can also use max length from (x1, x2) by calculating the hypotenuse: length = Math.sqrt(diffX*diffX + diffY*diffY);.
Example
var ctx = c.getContext("2d"),
x1 = 150, y1 = 150, x2, y2, angle,
length = 150;
render();
cAngle.oninput = render;
function render() {
angle = +cAngle.value / 180 * Math.PI;
// calculate gradient line based on angle
x2 = x1 + Math.cos(angle) * length;
y2 = y1 + Math.sin(angle) * length;
// create and render gradient
ctx.fillStyle = ctx.createLinearGradient(x1, y1, x2, y2);
ctx.fillStyle.addColorStop(0, "#fff");
ctx.fillStyle.addColorStop(1, "#07f");
ctx.fillRect(0, 0, 300, 300);
// show definition line
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
<label>Angle: <input id=cAngle max=359 type=range value=0></label><br>
<canvas id=c height=300></canvas>
Am simulating a shape click and trying to do drag/drop inside canvas using Sahi framework.I have found some references that by looping through an array containing that shapes position/sizes , we get whether shape is clicked.While testing other website using sahi framework/native jaascript , how can it be achieved ? I wanted to select that shape inside canvas and do drag and drop inside canvas.This is my goal.
In html5 canvas, A "Shape" is defined and drawn using a Path.
You define a shape by:
Declaring that you a beginning a new path: context.beginPath
Using one or more of the path definition commands: moveTo, lineTo, arc, etc.
// define a triangle path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
// see not below about .closePath()
context.closePath();
Note: context.closePath is NOT used to close a context.beginPath command -- it is NOT like a closing parenthesis! Instead, it is used to draw a line from the last coordinate to the starting coordinate -- to "enclose" the path. In our example, it draws an "enclosing" line from [x+0,y+100] to [x+50,y+50].
Just defining a path will not cause it to be drawn onto the canvas. To actually draw the shape onto the canvas you can:
Stroke the outline of the path, and / or
Fill the inside of the path.
context.stroke();
context.fill();
For example, here's how to define and draw a triangle. You can also use offset variables ([x,y] in the example) to reposition the triangle anywhere on the canvas.
// set the offset of the triangle
var x=30;
var y=40;
// define the path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
// stroke the path
context.stroke();
// if desired, you can also fill the inside of the path
context.fill();
To drag a shape, you must test whether the mouse is over that shape. You can "hit-test" the most recently defined shape using context.isPointInPath.
Be sure you read that carefully!
You can hit-test the most recently "defined" path. If you define and draw multiple paths then isPointInPath will only hit-test the last defined path.
if(context.isPointInPath(mouseX,mouseY)){
console.log('Yes, the mouse is in the triangle.');
}
Also note that you don't have to re-stroke the path being tested so your drawings won't be altered by the hit-testing process. So you hit-test multiple paths by:
Define one of the paths
Hit test with isPointInPath(mouseX,mouseY)
Repeat step#1 for the next path (path==shape)
Nothing drawn to the canvas can be moved -- everything is as permanent as dried paint. So to "move" a shape on canvas you clear the entire canvas and redraw the shape in its moved position:
// clear the canvas
context.clearRect(canvas.width,canvas.height);
// move the canvas by changing it's offsets
x+=20;
y+=30;
// redefine and restroke the shape
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
context.stroke();
To make redefining and re-stroking the shape more reusable you can put the code in a function:
function myTriangle(alsoStroke){
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
if(alsoStroke){
context.stroke();
}
}
You can read more about dragging a shape in this previous post. Since you can't move a shape you can't "drag" a shape either. You must again clear the canvas and redraw it in its newly dragged position.
To drag a shape you need to listen to 4 mouse events.
In mousedown: Check if the mouse is over the shape, and, if yes, set a flag indicating a drag has begun. To check if the mouse is over the shape, you can use canvas context's isPointInPath method which tests if an [x,y] point is inside the most recently drawn path.
In mousemove: If the dragging flag is set (indicating a drag is in process), change the position of the selected text by the distance the user has dragged and redraw the shape in its new position
In mouseup or in mouseout: The drag is over so clear the dragging flag.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var poly={
x:0,
y:0,
points:[{x:50,y:50},{x:75,y:25},{x:100,y:50},{x:75,y:125},{x:50,y:50}],
}
ctx.fillStyle='skyblue';
ctx.strokeStyle='gray';
ctx.lineWidth=3;
draw();
// listen to mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
function draw(){
ctx.clearRect(0,0,cw,ch);
define();
ctx.fill();
ctx.stroke()
}
function define(){
ctx.beginPath();
ctx.moveTo(poly.points[0].x+poly.x,poly.points[0].y+poly.y);
for(var i=0;i<poly.points.length;i++){
ctx.lineTo(poly.points[i].x+poly.x,poly.points[i].y+poly.y);
}
ctx.closePath();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
define();
if(ctx.isPointInPath(startX,startY)){
isDown=true;
}
}
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
poly.x+=dx;
poly.y+=dy;
draw();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag the polygon</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Addition: Discovering Rectangles using context.getImageData ]
If you don't have the shape's position/size and you instead have an image of a shape then you must get the position/size by searching the pixels. Here's an example showing how to isolate rectangles by searching pixels:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw, ch;
// background definition
// OPTION: look at the top-left pixel and assume == background
// then set these vars automatically
var isTransparent = false;
var bkColor = {
r: 255,
g: 255,
b: 255
};
var bkFillColor = "rgb(" + bkColor.r + "," + bkColor.g + "," + bkColor.b + ")";
cw = canvas.width;
ch = canvas.height;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTestRect(30, 30, 50, 50, "1");
drawTestRect(100, 30, 50, 30, "2");
drawTestRect(170, 30, 30, 50, "3");
function drawTestRect(x, y, w, h, label) {
ctx.fillStyle = "black";
ctx.fillRect(x, y, w, h);
ctx.fillStyle = "white";
ctx.font = "24px verdana";
ctx.fillText(label, x + 10, y + 25);
}
function clipBox(data) {
var pos = findEdge(data);
if (!pos.valid) {
return;
}
var bb = findBoundary(pos, data);
alert("Found target at "+bb.x+"/"+bb.y+", size: "+bb.width+"/"+bb.height);
clipToImage(bb.x, bb.y, bb.width, bb.height);
if (isTransparent) {
// clear the clipped area
// plus a few pixels to clear any anti-aliasing
ctx.clearRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
} else {
// fill the clipped area with the bkColor
// plus a few pixels to clear any anti-aliasing
ctx.fillStyle = bkFillColor;
ctx.fillRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
}
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var start = (y * cw + x) * 4;
if (isTransparent) {
return (data[start + 3] > 25);
} else {
var r = data[start + 0];
var g = data[start + 1];
var b = data[start + 2];
var a = data[start + 3]; // pixel alpha (opacity)
var deltaR = Math.abs(bkColor.r - r);
var deltaG = Math.abs(bkColor.g - g);
var deltaB = Math.abs(bkColor.b - b);
return (!(deltaR < 5 && deltaG < 5 && deltaB < 5 && a > 25));
}
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if (xyIsInImage(data, x, y)) {
return ({
x: x,
y: y,
valid: true
});
}
}
}
return ({
x: -100,
y: -100,
valid: false
});
}
function findBoundary(pos, data) {
var x0 = x1 = pos.x;
var y0 = y1 = pos.y;
while (y1 <= ch && xyIsInImage(data, x1, y1)) {
y1++;
}
var x2 = x1;
var y2 = y1 - 1;
while (x2 <= cw && xyIsInImage(data, x2, y2)) {
x2++;
}
return ({
x: x0,
y: y0,
width: x2 - x0,
height: y2 - y0 + 1
});
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = "red";
ctx.lineWidth = 0.50;
ctx.stroke();
}
function clipToImage(x, y, w, h) {
// don't save anti-alias slivers
if (w < 3 || h < 3) {
return;
}
// save clipped area to an img element
var tempCanvas = document.createElement("canvas");
var tempCtx = tempCanvas.getContext("2d");
tempCanvas.width = w;
tempCanvas.height = h;
tempCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
var image = new Image();
image.width = w;
image.height = h;
image.src = tempCanvas.toDataURL();
$("#clips").append(image);
}
$("#unbox").click(function () {
var imgData = ctx.getImageData(0, 0, cw, ch);
var data = imgData.data;
clipBox(data);
});
body {
background-color: ivory;
}
canvas {
border:1px solid red;
}
#clips {
border:1px solid blue;
padding:5px;
}
img {
margin:3px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="unbox">Clip next sub-image</button><br>
<canvas id="canvas" width=300 height=150></canvas><br>
<h4>Below are images clipped from the canvas above.</h4><br>
<div id="clips"></div>
[Addition: discovering the bounds of a reddishness stroked rectangle]
You can test for "reddishness" by checking if the red component value of a pixel is much larger than the green & blue component values.
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 && // this pixel is mostly opaque
data[n]-data[n+1]>180 && // this pixel is more reddish than green
data[n]-data[n+2]>180 // this pixel is more reddish then blue
);
Then use this reddishness test to find the bounds of the reddish stroked rectangle:
Find the theoretical top-left pixel of the red rect. The discovered pixel is probably on the top of the rect. It might not be on the left of the rect because your image shows the rect's corner pixels are much less reddish. So declare the y value as the top of the rect.
Move towards the center of the rect by a few pixels.
Test each vertical pixel downward until you find the bottom reddish border of the stroked rect.
Test each horizontal pixel rightward until you find the right reddish border of the stroked rect.
Test each horizontal pixel leftward until you find the left reddish border of the stroked rect.
Now you have found the top-left and bottom-right bounds of the rect.
Here's example code and a demo using your image:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/raw.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
var data=ctx.getImageData(0,0,cw,ch).data;
var edge=findEdge(data);
var top=edge.y;
var x,y,left,bottom,right;
var off=25;
for(var y=edge.y+off;y<ch;y++){if(xyIsInImage(data,edge.x+off,y)){bottom=y; break;}}
for(var x=edge.x+off;x<cw;x++){if(xyIsInImage(data,x,edge.y+off)){right=x;break;}}
for(var x=edge.x+off;x>=0;x--){if(xyIsInImage(data,x,edge.y+off)){left=x;break;}}
dot({x:left,y:top});
dot({x:right,y:bottom});
}
//
function dot(pt){
ctx.beginPath();
ctx.arc(pt.x,pt.y,4,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='gold';
ctx.lineWidth=2;
ctx.stroke();
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 &&
data[n]-data[n+1]>180 &&
data[n]-data[n+2]>180
);
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if(xyIsInImage(data, x, y)){ return ({x:x,y:y,valid:true}); }
}
}
return ({x:-100,y:-100,valid:false});
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Red-gold dots are on top-left & bottom-right of target rect.</h4>
<canvas id="canvas" width=300 height=300></canvas>
How can i rotate this function by 45 degrees so it is a diamond. I am making a game where this function is the player and you can move it with the arrow keys, but i want it to be in the shape of a diamond and not a square.
function morty() {
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0,0,cv.width,cv.height);
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(x1,y1,50,50);
ctx.fillStyle = "rgb(175,80,247)";
ctx.fillRect(x1,y1,45,45);
ctx.fillStyle = "rgb(50,100,150)";
ctx.fillRect(x1,y1,40,40);
ctx.fillStyle = "rgb(0,255,255)";
ctx.fillRect(x1,y1,32,32);
ctx.fillStyle = "rgb(250,0,250)";
ctx.fillRect(x1,y1,27,27);
ctx.fillStyle = "rgb(47,47,47)";
ctx.fillRect(x1,y1,20,20);
ctx.fillStyle = "rgb(255,255,0)";
ctx.fillRect(x1,y1,10,10);
ctx.fillStyle = "rgb(0,200,180)";
ctx.fillRect(x1,y1,5,5);
}
Your square flying diagonal is because that when you move (1, 0) after rotate, you're actually moving about (0.707, 0.707) on the canvas, because after the coordinate system is rotated by ctx.rotate.
So, what you desire is to make (1, 0) translate real have the effect of move (1, 0) on the canvas, which should not be rotated, then it's better to move x1 and y1 out. The logic becomes,
draw some squares on (0, 0) (because ctx.rotate is center at (0
0))
rotate it
Move the square to (x1, y1).
Convert to scripts, we should know that in canvas, the ealier you apply a transform, the later it'll effect. Also, we want to restore the coordinate back after the drawing is complete, so scripts would be:
use ctx.save to save current, unaltered coordinate, for later we can back to it.
We want move x1, y1 be not affect by rotate, so we first call ctx.translate(x1, y1);, that it'll move anything rotated by (x1, y1) on canvas.
Then call ctx.rotate(45 / 180 * Math.PI); to make following drawing operation to rotate by 45 degree.
Draw the squares, but this time, instead drawing on x1, y1, we draw them centered on (0, 0).
Call ctx.restore, so if you have other thing to draw, it will still drawn on the normal coordinate instead of the rotated-shifted coordinate.
var cv = document.getElementById('test');
var ctx = cv.getContext('2d');
var x1 = cv.width >> 1;
var y1 = cv.height >> 1;
function morty() {
// Clear canvas
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0,0,cv.width,cv.height);
ctx.save();
// This make anything below have a x1, y1 translate/
ctx.translate(x1, y1);
// This rotate any operations below 45 degree at (0, 0)
ctx.rotate(45 / 180 * Math.PI);
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(-25,-25,50,50);
ctx.fillStyle = "rgb(175,80,247)";
ctx.fillRect(-22.5,-22.5,45,45);
ctx.fillStyle = "rgb(50,100,150)";
ctx.fillRect(-20,-20,40,40);
ctx.fillStyle = "rgb(0,255,255)";
ctx.fillRect(-16, -16,32,32);
ctx.fillStyle = "rgb(250,0,250)";
ctx.fillRect(-13.5,-13.5,27,27);
ctx.fillStyle = "rgb(47,47,47)";
ctx.fillRect(-10,-10,20,20);
ctx.fillStyle = "rgb(255,255,0)";
ctx.fillRect(-5,-5,10,10);
ctx.fillStyle = "rgb(0,200,180)";
ctx.fillRect(-2.5, -2.5,5,5);
// This makes the operations after it still have a normal cordinate system.
ctx.restore();
}
// Test usage.
document.body.addEventListener('keydown', function(e) {
console.log(e.keyCode);
switch(e.keyCode) {
case 37:
x1 -= 10;
break;
case 38:
y1 -= 10;
break;
case 39:
x1 += 10;
break;
case 40:
y1 += 10;
break;
default:
return;
}
morty();
})
morty();
<canvas id="test" width="300" height="300"></canvas>