Here is the code:
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>
This code works fine. BUT each of my polgon appears at wrong place. For example I call
polygon(context, 120,120,50,12);
and
polygon(context, 120,220,50,12);
and the second polygon appears at x=220, y=220
I mean, they moves in the right side, but they should appear one under another with the same x coordinates.
After drawing the shape you need to translate it back to the original position so the next shape is drawn from the same relative location as the first.
ctx.translate(-x, -y);
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
polygon(context, 120,220,50,12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
ctx.translate(-x, -y);
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>
Reset the translation matrix to the identity matrix before drawing each shape:
context.setTransform(1, 0, 0, 1, 0, 0);
I think it happens because of:
ctx.translate(x, y);
If you look closely at HTML canvas translate() Method Definition and Usage you will see that translate() method remaps the (0,0) position of the canvas instead of setting starting point for your drawing. So if you do function call like this:
ctx.translate(120, 120);
ctx.translate(120, 220);
You actually moving registration point of canvas twice. First time it will be moved by (120,120) and later it will be moved by (120,220), so your first polygon will be drawed correctly but the second will be drawed on position (240,340), because coordinates of both starting points will eventually be summed.
You are using the same 2d context over and over for your polygons. The context will save the state of your translations, so consecutive translations add up. You could "revert" the effect of translations by translating with same-negative-values at the end of each polygon call.
$(document).ready(function() {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
canvas.width = window.innerWidth
canvas.height = window.innerHeight
polygon(context, 120, 120, 50, 12);
context.stroke();
polygon(context, 120, 220, 50, 12);
context.stroke();
})
function polygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
var a = ((Math.PI * 2) / sides);
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(radius, 0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i));
}
ctx.closePath();
ctx.translate(-1 * x, -1 * y);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<canvas id="canvas">OOPS.. Upgrade your Browser</canvas>
Related
I need to apply several matrix transformations before drawing a shape, however (if on somewhere) I use rotate() the coordinates are inverted and/or reversed and cannot continue without knowing if the matrix was previously rotated.
How can solve this problem?
Example:
<canvas width="300" height="300"></canvas>
<script>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
// move the red rectangle 100px to the left (top-left)
// but instead is moved on y axis (right-bottom)
ctx.translate(-100, 0);
// more matrix transformations
// ....
// ....
// now finally draw the shape
ctx.fillStyle = "red";
ctx.fillRect(150, 150, 100, 50);
</script>
Can be this Translation after rotation the solution?
It looks like you aren't resetting the canvas matrix each time you make a new transformation.
The Canvas API has the save() and restore() methods. Canvas states are stored on a stack. Every time the save() method is called, the current drawing state is pushed onto the stack. A drawing state consists of transformations that have been applied along with the attributes of things like the fillStyle. When you call restore(), the previous settings are restored.
// ...
ctx.save(); // save the current canvas state
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
ctx.restore(); // restore the last saved state
// now the rectangle should move the correct direction
ctx.translate(-100, 0);
Check out this link for more information on the save and restore methods.
OK finally, i solved the problem by rotating the translation point before applying it. This function does the trick:
function helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return { x: point.x * c - point.y * s, y: point.x * s + point.y * c};
}
rotating the translation point using the inverted angle I obtain the corrected translation
helperRotatePoint(translation_point, -rotation_angle);
working code:
let canvas = document.querySelector("canvas");
// proper size on HiDPI displays
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width = Math.floor(canvas.width * window.devicePixelRatio);
canvas.height = Math.floor(canvas.height * window.devicePixelRatio);
let ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillStyle = "whitesmoke";
ctx.fillRect(0, 0, canvas.width, canvas.height);
class UIElement {
constructor(x, y, width, height, color) {
// PoC
this.draw_pos = {x, y};
this.draw_size = {width, height};
this.color = color;
this.rotate = 0;
this.scale = {x: 1, y: 1};
this.translate = {x: 0, y: 0};
this.skew = {x: 0, y: 0};
this.childs = [];
}
addChild(uielement) {
this.childs.push(uielement);
}
helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return {
x: point.x * c - point.y * s,
y: point.x * s + point.y * c
};
}
draw(cnvs_ctx, parent_x, parent_y) {
// backup current state
cnvs_ctx.save();
let elements_drawn = 1;// "this" UIElement
// step 1: calc absolute coordinates
let absolute_x = parent_x + this.draw_pos.x;
let absolute_y = parent_y + this.draw_pos.y;
// step 2: apply all transforms
if (this.rotate != 0) {
cnvs_ctx.translate(absolute_x, absolute_y)
cnvs_ctx.rotate(this.rotate);
cnvs_ctx.translate(-absolute_x, -absolute_y);
// rotate translate point before continue
let tmp = this.helperRotatePoint(this.translate, -this.rotate);
// apply rotated translate
cnvs_ctx.translate(tmp.x, tmp.y);
} else {
cnvs_ctx.translate(this.translate.x, this.translate.y);
}
cnvs_ctx.scale(this.scale.x, this.scale.y);
cnvs_ctx.transform(1, this.skew.y, this.skew.x, 1, 0, 0);
// step 3: self draw (aka parent element)
cnvs_ctx.fillStyle = this.color;
cnvs_ctx.fillRect(absolute_x, absolute_y, this.draw_size.width, this.draw_size.height);
// step 4: draw childs elements
for (let i = 0; i < this.childs.length ; i++) {
elements_drawn += this.childs[i].draw(
cnvs_ctx, absolute_x, absolute_y
);
}
// done, restore state
cnvs_ctx.restore();
return elements_drawn;
}
}
// spawn some ui elements
var ui_panel = new UIElement(120, 50, 240, 140, "#9b9a9e");
var ui_textlabel = new UIElement(10, 10, 130, 18, "#FFF");
var ui_image = new UIElement(165, 25, 90, 60, "#ea9e22");
var ui_textdesc = new UIElement(17, 46, 117, 56, "#ff2100");
var ui_icon = new UIElement(5, 5, 10, 10, "#800000");
ui_panel.addChild(ui_textlabel);
ui_panel.addChild(ui_image);
ui_panel.addChild(ui_textdesc);
ui_textdesc.addChild(ui_icon);
// add some matrix transformations
ui_textdesc.skew.x = -0.13;
ui_textdesc.translate.x = 13;
ui_image.rotate = -90 * 0.017453292519943295;
ui_image.translate.y = ui_image.draw_size.width;
ui_panel.rotate = 15 * 0.017453292519943295;
ui_panel.translate.x = -84;
ui_panel.translate.y = -50;
// all ui element elements
ui_panel.draw(ctx, 0, 0);
<canvas width="480" height="360"></canvas>
I want draw something like this in javascript:
Example:
The bigger circle: r = 5
What do I want to do? to position circles by forming a circle
My questions are (I would like to know):
How much points can I draw on a circle if I know the ray,
the distance between each circle and the radius of each circle?
How can I find the position of each circle (to draw automatically)?
Like the question 1 but the circles do not have the same radius.
Thank you!
Displace points around a circle edge equidistantly
Using HTML Canvas and a bit of trigonometry
Create a reusable circles() function and pass the desired arguments for Number of circles, Size, Radius, Color:
const ctx = document.getElementById("canvas").getContext("2d");
const cvsSize = 400;
ctx.canvas.width = cvsSize;
ctx.canvas.height = cvsSize;
function circles(tot, rad, dist, color) {
const arc = Math.PI * 2 / tot; // Arc in Radians
let ang = 0; // Start at angle 0 (East)
for (let i = 0; i < tot; i++) {
const x = dist * Math.cos(ang) + (cvsSize / 2);
const y = dist * Math.sin(ang) + (cvsSize / 2);
ctx.beginPath();
ctx.arc(x, y, rad, 0, Math.PI * 2, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
ang += arc;
}
}
// Circles, Radius, Distance, Color
circles(3, 5, 10, "#f0b");
circles(10, 8, 50, "#0bf");
circles(17, 10, 90, "#bf0");
circles(21, 15, 140, "#b0f");
<canvas id="canvas"></canvas>
Can you please take a look at this demo and let me know how I can draw multiple circles in a canvas with different coordinate without repeating bunch of codes?
As you can see on Demo and following code
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
ctx.beginPath();
ctx.arc(150, 50, 5, 0, Math.PI * 2, true);
ctx.arc(20, 85, 5, 0, Math.PI * 2, true);
ctx.arc(160, 95, 5, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
I tried to have them under ctx but it is not correct so I tried to use for loop to create 50 points but I have issue on repeating and adding code like ctx.fill(); for all of them.
Can you please let me know how I can fix this?
Thanks
Constantly creating and closing new paths is not good advice.
You should batch together all fills / strokes of the same style, and execute them in a single draw call. The performance difference between these approaches becomes apparent very quickly with increasing polygon count.
The way to go about it is to move the pen and make the path construction call for each circle; stroke/fill in the end in one shot. However, there's a quirk involved here. When you have the point moved to the center and draw the circle, you'd still see a horizontal radius line, drawn from the center of the circle, to the circumference.
To avoid this artefact, instead of moving to the center, we move to the circumference. This skips the radius drawing. Essentially all these commands are for tracing a path and there's no way to describe a discontinuity without calling closePath; usually moveTo does it but HTML5 canvas API it doesn't. This is a simple workaround to counter that.
const pi2 = Math.PI * 2;
const radius = 5;
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i],
x = p.x,
y = p.y;
ctx.moveTo( x + radius, y ); // This was the line you were looking for
ctx.arc( x, y, radius, 0, pi2 );
}
// Finally, draw outside loop
ctx.stroke();
ctx.fill();
Also worth considering, is using transformations, and drawing everything relative to a local frame of reference.
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i];
ctx.save();
ctx.translate( p.x + radius, p.y );
ctx.moveTo( 0, 0 );
ctx.arc( 0, 0, radius, 0, pi2 );
ctx.restore();
}
ctx.stroke();
ctx.fill();
This is because you are not closing the path, either using fill() or closePath() will close the path so it does not try and connect all the items. fill() fills in the circles and closes the path so we can just use that. Also you need to use beginPath(), so that they are separate from each other. Here is your three circles:
var coords = [ [150,50], [20,85], [160,95] ];
for(var i = 0; i < coords.length; i++){
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
ctx.fill();
}
To not repeat a bunch of code and have unique coordinates store your X and Y position in an array and use a for loop to go through it.
Update:
A more efficient way to do which achieves the same effect this would be to only use a single path and use moveTo() instead of creating a new path when drawing each circle:
ctx.beginPath();
for(var i = 0; i < coords.length; i++){
ctx.moveTo(coords[i][0], coords[i][1]);
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
}
ctx.fill();
ctx.beginPath();
points.forEach(point => {
ctx.moveTo( point.x, point.y );
ctx.arc(point.x,point.y,1,0,Math.PI*2,false);
});
ctx.fill();
You can easily create several circles with a for loop. You really just need to draw an arc and fill it each time. Using your example, you could do something like this.
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
for (var i = 0; i < 3; i++) {
ctx.arc(50 * (i+1), 50 + 15 * i, 5, 0, Math.PI * 2, true);
ctx.fill();
}
Example Fiddle of lots of circles in different locations drawn in a loop.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext( '2d' );
var cx = canvas.width/2;
var cy = canvas.height/2;
ctx.fillStyle = "#00A308";
var total_circles = 50;
var radius = 100;
for(i = 0; i < total_circles; i++){
var angle = i * 2 * Math.PI/total_circles;
var x = cx + Math.cos(angle) * radius;
var y = cy + Math.sin(angle) * radius;
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
I have this animation, but i cant get over the logic. I hope someone can help me here.
Basicly i need this: http://jsfiddle.net/PDE85/9/ but without the arrow doing such crazy moves. It should be attached to the front of the open circle to simulate an expanding arrow.
I got the triangle to turn right here but it doesnt work when i mix it with position logic as seen in the first example.
Here is the code for reference
(function() {
var size = ($(window).height()/5)*4;
$("#intro-container").css('width',size);
$("#intro-canvas").css('width',size);
$("#intro-canvas").css('height',size);
var interval = window.setInterval(draw, 30);
var degrees = 0.0;
var offset = 20;
var rotate = 0;
var canvas = document.getElementById('intro-canvas');
var ctx = canvas.getContext('2d');
canvas.width = size;
canvas.height = size;
draw();
function draw() {
if (canvas.getContext) {
ctx.fillStyle="white";
ctx.strokeStyle="white";
ctx.clearRect(0, 0, size, size);
ctx.save();
ctx.translate(size/2, size/2);
ctx.rotate(-90 * Math.PI / 180);
ctx.beginPath();
ctx.lineWidth = size/8;
ctx.arc(0, 0, size/3, 0, rotate * Math.PI / 180);
//ctx.shadowBlur=1;
//ctx.shadowColor="black";
ctx.stroke();
ctx.restore();
ctx.beginPath();
ctx.save();
// moving logic
ctx.translate(size/2, size/2);
ctx.rotate(-Math.PI / 180 * -rotate+1);
ctx.translate(-size/3, -size/3);
// rotating logic
ctx.translate(size/2, size/2);
ctx.rotate((rotate * Math.PI + 420) / 180);
ctx.moveTo(0,0);
ctx.lineTo(size/6,0);
ctx.lineTo(0,size/6);
ctx.lineTo(0,0);
ctx.fill();
ctx.restore();
rotate += 1;
if(rotate > 360){
window.clearInterval(interval)
}
}
}
})();
I believe you are looking for this : http://jsfiddle.net/PDE85/12/
The rotation comes from, the rotate call which is unnecessary.
Plus you need an inverted triangle, hence the coordinates needed an update:
...
// ctx.rotate((rotate * Math.PI + 420) / 180);
ctx.moveTo(0,0);
ctx.lineTo(-size/6,0);
ctx.lineTo(0,-size/6);
...
i have following code with newest craftyjs:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin:0;
}
</style>
<script type="text/javascript" src="crafty.js"></script>
<script>
Crafty.c("Planet", {
Planet: function(radius, map) {
this.radius = radius;
this.map = map;
return this;
},
draw: function() {
var ctx = Crafty.canvas.context;
var x = 100;
var y = 100;
var offsetX = 0;
var map = this.map;
ctx.save();
ctx.beginPath();
ctx.arc(x, y, this.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.clip();
var scale = (this.radius * 2) / map.height;
ctx.drawImage(map, 0, 0, map.width, map.height, x - this.radius - offsetX * scale, y - this.radius, map.width * scale, map.height * scale);
ctx.beginPath();
ctx.arc(x, y, this.radius * 1.04, Math.PI * 0.70, Math.PI * 1.30, false);
ctx.shadowColor = "black";
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 5;
ctx.stroke();
ctx.beginPath();
ctx.arc(x, y, this.radius * 1.04, -Math.PI * 0.30, Math.PI * 0.30, false);
ctx.shadowColor = "black";
ctx.shadowBlur = 5;
ctx.shadowOffsetX = -5;
ctx.stroke();
ctx.restore();
console.log('drawing');
}
});
Game = {
// Initialize and start our game
start: function() {
// Start crafty and set a background color so that we can see it's working
Crafty.init(500,500);
Crafty.scene('Game', function() {
Crafty.load(["1.jpg", "ship.png"],
function() {
Crafty.sprite("ship.png", {player_spr:[0,0, 48,48]});
Crafty.background("url('space.jpg')");
Crafty.e("2D, Canvas, Planet")
.Planet(40, Crafty.asset('1.jpg'));
var b2d = Crafty.e("2D, Canvas, player_spr, Actor, Fourway")
.multiway({W: -90, S: 90, D: 0, A: 180})
.attr({z: 4});
Crafty.viewport.clampToEntities = false;
Crafty.viewport.follow(b2d,0,0);
});
});
Crafty.scene('Game');
}
}
window.onload = Game.start;
</script>
</head>
<body>
</body>
</html>
Textures is:
File: 1.jpg
File: ship.png
The result canvas rendered image:
This is a strange bug! What i can to do with it?
Crafty sprite conflict with custom drawings... (it shows sprite transparency)
CraftyJS Github version fix this issue!
Just compile that with grunt.
Thank you for your answers!