I am new to canvas and I want my stickman to move his legs as if he is walking. My knowledge is very limited. I've received this code as a template to use, the only task I had to do was add a sword to him. As a challenge, I want him to walk. I have tried many solutions but they all required CSS which is not the case for my code here. Other sources weren't as helpful such as Youtube videos because they did not explain what they were doing.
function drawSpartacus(el, x, y, facing) {
const c = el.getContext("2d");
// set our drawing style
c.lineWidth = 2;
c.lineCap = "round";
c.lineJoin = "round"
c.strokeStyle = "#000";
if (x == null) x = 100;
if (y == null) y = 150;
// the arms and the legs look the same
drawLimbs(c, x, y) // legs
drawLimbs(c, x, y - 40) // arms
// body is just a line
line(c, x, y - 40, x, y - 80) // body
// head is a circle with eyes and a smile
circle(c, x, y - 100, 20) // head
drawFace(c, x, y - 100, facing) // face
// helpful functions start here
function drawLimbs(c, x, y) {
line(c, x - 20, y, x, y - 40)
line(c, x + 20, y, x, y - 40)
}
function drawFace(c, x, y, facing) {
// if the `facing` parameter is not given, the stick figure will face towards us
if (facing == null) facing = 90;
// make sure the `facing` parameter is between 0 and 360
facing = facing % 360; // that's like the mathematical remainder after a division
if (facing < 0) facing += 360;
if (facing > 180) return; // facing away from us, don't draw a face
// we'll fake the turning of the face by shifting the eyes and the smile by an offset of up to 10 pixels
let faceOffset = 0;
if (facing <= 180) {
faceOffset = (facing - 90) / 9;
}
circle(c, x - 7 - faceOffset, y - 5, 1) // 7 is distance from center, 5 is how high the eyes are from the head's center, 1 is eye size
circle(c, x + 7 - faceOffset, y - 5, 1)
// decrease the smile size here
const smileSize = 70; // size of smile in degrees of angle; 360 would be a full circle
const startAngle = rad(90 - smileSize / 2 - 2 * faceOffset)
const endAngle = rad(90 + smileSize / 2 - 2 * faceOffset)
arc(c, x - faceOffset, y, 12, startAngle, endAngle) // 12 is the radius of the smile circle
}
// draw a line on canvas context `c`, from point x1,y1 to point x2,y2
function line(c, x1, y1, x2, y2) {
c.beginPath();
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
}
// draw a circle on canvas context `c`, centered on x,y, with radius r
// also fill the circle with white (so it's not transparent)
function circle(c, x, y, r) {
c.beginPath()
c.fillStyle = "#fff"
c.arc(x, y, r, 0, 6.3, false); // 6.3 is bigger than 2π so the arc will be a whole circle
c.fill()
c.stroke()
}
// draw an arc on canvas context `c`, cenetered on x,y, with radius r, from angleStart to angleEnd
function arc(c, x, y, r, angleStart, angleEnd) {
c.beginPath();
c.arc(x, y, r, angleStart, angleEnd, false)
c.stroke();
}
// convert from degrees to radians
function rad(x) {
return x * Math.PI / 180;
}
//grip
c.beginPath();
c.moveTo(115, 110);
c.lineTo(125, 110);
c.lineTo(125, 115);
c.lineTo(115, 115);
c.closePath();
c.stroke();
c.fillStyle = "#654321";
c.fill();
//blade
c.beginPath();
c.moveTo(125, 110);
c.lineTo(145, 109);
c.lineTo(148, 112.5);
c.lineTo(145, 116);
c.lineTo(125, 115);
c.closePath();
c.stroke();
c.fillStyle = "#c0c0c0"; //This fills colour on the closed path above.
c.fill();
//cross guard
c.beginPath();
c.moveTo(125, 105);
c.lineTo(125, 120);
c.stroke();
}
I am using forced layout to create directed graph .
Its rendered on canvas . My sample example is at http://jsbin.com/vuyapibaqa/1/edit?html,output
Now I am inspired from
https://bl.ocks.org/mattkohl/146d301c0fc20d89d85880df537de7b0#index.html
Few Resources in d3 svg , something similar i am trying to get in canvas.
http://jsfiddle.net/zhanghuancs/a2QpA/
http://bl.ocks.org/mbostock/1153292 https://bl.ocks.org/ramtob/3658a11845a89c4742d62d32afce3160
http://bl.ocks.org/thomasdobber/9b78824119136778052f64a967c070e0
Drawing multiple edges between two nodes with d3.
Want to add elliptical arc connecting edge with arrow . How to achieve this in canvas.
My Code :
<!DOCTYPE html>
<html>
<head>
<title>Sample Graph Rendring Using Canvas</title>
<script src="https://rawgit.com/gka/randomgraph.js/master/randomgraph.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var graph = {}//randomgraph.WattsStrogatz.beta(15, 4, 0.06);
graph.nodes = [{"label":"x"} , {"label":"y"}];
graph.edges = [{source:0,target:1},{source:0,target:1},
{source:1,target:0}]
var canvas = null
var width = window.innerWidth,
height = window.innerHeight;
canvas = d3.select("body").append("canvas").attr("width",width).attr("height",height);
var context = canvas.node().getContext("2d");
force = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.index;
})).force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
force.nodes(graph.nodes);
force.force("link").links(graph.edges).distance(200);
var detachedContainer = document.createElement("custom");
dataContainer = d3.select(detachedContainer);
link = dataContainer.selectAll(".link").data(graph.edges)
.enter().append("line").attr("class", "link")
.style("stroke-width", 2)
node = dataContainer.selectAll(".node").data(graph.nodes)
.enter().append("g");
var circles = node.append("circle")
.classed("circle-class", true)
.attr("class", function (d){ return "node node_" + d.index;})
.attr("r", 5)
.attr("fill", 'red')
.attr("strokeStyle", 'black');
d3.timer(function(){
context.clearRect(0, 0, width, height);
// draw links
link.each(function(d) {
context.strokeStyle = "#ccc";
/***** Elliptical arcs *****/
context.stroke(new Path2D(linkArc(d)));
/***** Elliptical arcs *****/
});
context.lineWidth = 2;
node.each(function(d) {
context.beginPath();
context.moveTo(d.x, d.y);
var r = d3.select(this).select("circle").node().getAttribute('r');
d.x = Math.max(30, Math.min(width - 30, d.x));
d.y = Math.max(30, Math.min(height - 30, d.y));
context.closePath();
context.arc(d.x, d.y, r, 0, 2 * Math.PI);
context.fillStyle = d3.select(this).select("circle").node().getAttribute('fill');
context.strokeStyle = d3.select(this).select("circle").node().getAttribute('strokeStyle');
context.stroke();
context.fill();
context.beginPath();
context.arc(d.x + 15, d.y-20, 5, 0, 2 * Math.PI);
context.fillStyle = "orange";
context.strokeStyle = "orange";
var data = d3.select(this).data();
context.stroke();
context.fill();
context.font = "10px Arial";
context.fillStyle = "black";
context.strokeStyle = "black";
context.fillText(parseInt(data[0].index),d.x + 10, d.y-15);
});
});
circles.transition().duration(5000).attr('r', 20).attr('fill', 'orange');
canvas.node().addEventListener('click',function( event ){
console.log(event)
// Its COMING ANY TIME INSIDE ON CLICK OF CANVAS
});
/***** Elliptical arcs *****/
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
/***** Elliptical arcs *****/
</script>
</body>
</html>
Draw arc from circle to circle with arrow heads
The basic problem
The two points need to be random(from anywhere to anywhere) x1,y1 and x2,y2. You will want to control the amount of bending that is invariant to the distance between the points (ie the same amount of bending if the distance between points is 100 pixels or 10 pixels)
Thus inputs are
x1,y1 // as start
x2,y2 // as end
bend // as factor of distance between points
// negative bends up (to right)
// positive bends down (to left of line)
arrowLen // in pixels
arrowWidth // in pixels,
arrowStart // boolean if arrow at start
arrowEnd // boolean if arrow at end.
Basic method steps
Find the mid-point between the two end points.
Get distance between points
Get the normalised vector from start to end.
Rotate norm 90deg
Multiply distance by bend by rotated norm and add to mid point to find mid point on arc
With the 3 points find the radius of a circle that will fit all 3 points.
Use the radius to find the center of the arc
From the center find the direction to the start and end
Use the arrow len to find angular length of arrows now we have the radius
Draw the arc from inside arrows or start / end (depending if arrows shown)
Draw arrow from point with flat side along line from arc center
Additional problems.
I assume you want the lines to be from one circle to the next. Thus you want to specify the circle centers and the radius of the circles. This will require two additional arguments one for the start circle radius and one for the end.
There is also the problem of what to do when the two points are two close (ie they overlap). There is not real solution apart from not to draw the lines and arrows if they don't fit.
The Solution as a Demo
The demo has to circles that change size over time, there are 6 arcs with different bend values of 0.1,0.3, 0.6 and -0.1, -0.3, -0.6. Move the mouse to change end circles position.
The function that does it all is called drawBend and I have put a lot of comments in there, There is also some commented lines that let you change how the arcs change when the distance between start and end changes. If you uncomment one, setting the variable b1 (where you assign to x3,y3 the mid point on the arc) you MUST comment out the other assignments
The solution to finding the arc radius and center is complicated and there is most likely a better solution due to the symmetry. That part will find a circle to fit any 3 points, (if not all on a line) so may have other uses for you.
Update I have found a much better method of finding the arc radius and thus center point. The symmetry provided a very convenient set of similar triangles and and thus I could shorten the function by 9 lines. I have updated the demo.
The arc is draw as a stroke, and the arrowheads as a fill.
Its reasonably quick, but if you plan to draw many 100's in real-time you can optimise by having the arc from and then back share some calcs. The arc from start to end will bend the other way if you swap the start and end, and there are many values that remain unchanged, so you can get two arcs for about a 75% CPU load of drawing 2
const ctx = canvas.getContext("2d");
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// x1,y1 location of a circle start
// x2,y2 location of the end circle
// bend factor. negative bends up for, positive bends down. If zero the world will end
// aLen is Arrow head length in pixels
// aWidth is arrow head width in pixels
// sArrow boolean if true draw start arrow
// eArrow boolean if true draw end arrow
// startRadius = radius of a circle if start attached to circle
// endRadius = radius of a circle if end attached to circle
function drawBend(x1, y1, x2, y2, bend, aLen, aWidth, sArrow, eArrow, startRadius, endRadius){
var mx, my, dist, nx, ny, x3, y3, cx, cy, radius, vx, vy, a1, a2;
var arrowAng,aa1,aa2,b1;
// find mid point
mx = (x1 + x2) / 2;
my = (y1 + y2) / 2;
// get vector from start to end
nx = x2 - x1;
ny = y2 - y1;
// find dist
dist = Math.sqrt(nx * nx + ny * ny);
// normalise vector
nx /= dist;
ny /= dist;
// The next section has some optional behaviours
// that set the dist from the line mid point to the arc mid point
// You should only use one of the following sets
//-- Uncomment for behaviour of arcs
// This make the lines flatten at distance
//b1 = (bend * 300) / Math.pow(dist,1/4);
//-- Uncomment for behaviour of arcs
// Arc bending amount close to constant
// b1 = bend * dist * 0.5
b1 = bend * dist
// Arc amount bend more at dist
x3 = mx + ny * b1;
y3 = my - nx * b1;
// get the radius
radius = (0.5 * ((x1-x3) * (x1-x3) + (y1-y3) * (y1-y3)) / (b1));
// use radius to get arc center
cx = x3 - ny * radius;
cy = y3 + nx * radius;
// radius needs to be positive for the rest of the code
radius = Math.abs(radius);
// find angle from center to start and end
a1 = Math.atan2(y1 - cy, x1 - cx);
a2 = Math.atan2(y2 - cy, x2 - cx);
// normalise angles
a1 = (a1 + Math.PI * 2) % (Math.PI * 2);
a2 = (a2 + Math.PI * 2) % (Math.PI * 2);
// ensure angles are in correct directions
if (bend < 0) {
if (a1 < a2) { a1 += Math.PI * 2 }
} else {
if (a2 < a1) { a2 += Math.PI * 2 }
}
// convert arrow length to angular len
arrowAng = aLen / radius * Math.sign(bend);
// get angular length of start and end circles and move arc start and ends
a1 += startRadius / radius * Math.sign(bend);
a2 -= endRadius / radius * Math.sign(bend);
aa1 = a1;
aa2 = a2;
// check for too close and no room for arc
if ((bend < 0 && a1 < a2) || (bend > 0 && a2 < a1)) {
return;
}
// is there a start arrow
if (sArrow) { aa1 += arrowAng } // move arc start to inside arrow
// is there an end arrow
if (eArrow) { aa2 -= arrowAng } // move arc end to inside arrow
// check for too close and remove arrows if so
if ((bend < 0 && aa1 < aa2) || (bend > 0 && aa2 < aa1)) {
sArrow = false;
eArrow = false;
aa1 = a1;
aa2 = a2;
}
// draw arc
ctx.beginPath();
ctx.arc(cx, cy, radius, aa1, aa2, bend < 0);
ctx.stroke();
ctx.beginPath();
// draw start arrow if needed
if(sArrow){
ctx.moveTo(
Math.cos(a1) * radius + cx,
Math.sin(a1) * radius + cy
);
ctx.lineTo(
Math.cos(aa1) * (radius + aWidth / 2) + cx,
Math.sin(aa1) * (radius + aWidth / 2) + cy
);
ctx.lineTo(
Math.cos(aa1) * (radius - aWidth / 2) + cx,
Math.sin(aa1) * (radius - aWidth / 2) + cy
);
ctx.closePath();
}
// draw end arrow if needed
if(eArrow){
ctx.moveTo(
Math.cos(a2) * radius + cx,
Math.sin(a2) * radius + cy
);
ctx.lineTo(
Math.cos(aa2) * (radius - aWidth / 2) + cx,
Math.sin(aa2) * (radius - aWidth / 2) + cy
);
ctx.lineTo(
Math.cos(aa2) * (radius + aWidth / 2) + cx,
Math.sin(aa2) * (radius + aWidth / 2) + cy
);
ctx.closePath();
}
ctx.fill();
}
/** SimpleUpdate.js begin **/
// short cut vars
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime = new Date().valueOf(); // global to this
// main update function
function update(timer){
globalTime = timer;
if(w !== innerWidth || h !== innerHeight){ // resize if needed
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
var startRad = (Math.sin(timer / 2000) * 0.5 + 0.5) * 20 + 5;
var endRad = (Math.sin(timer / 7000) * 0.5 + 0.5) * 20 + 5;
ctx.lineWidth = 2;
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(cw,ch,startRad,0,Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(mouse.x,mouse.y,endRad,0,Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.lineWidth = 2;
ctx.fillStyle = "black";
ctx.strokeStyle = "black";
drawBend(cw,ch,mouse.x,mouse.y,-0.1,10,10,true,true,startRad + 1,endRad + 1);
drawBend(cw,ch,mouse.x,mouse.y,-0.3,10,10,true,true,startRad + 1,endRad + 1);
drawBend(cw,ch,mouse.x,mouse.y,-0.6,10,10,true,true,startRad + 1,endRad + 1);
drawBend(cw,ch,mouse.x,mouse.y,0.1,10,10,true,true,startRad + 1,endRad + 1);
drawBend(cw,ch,mouse.x,mouse.y,0.3,10,10,true,true,startRad + 1,endRad + 1);
drawBend(cw,ch,mouse.x,mouse.y,0.6,10,10,true,true,startRad + 1,endRad + 1);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
I wrote this algorithm to draw a regular polygon:
var sides = 6,
radius = 50;
ctx.beginPath();
ctx.moveTo(x, y - radius);
for(n = 1; n <= sides; n++)
ctx.lineTo(
x + (radius * Math.sin(n * 2 * Math.PI / sides)),
y + (-1 * radius * Math.cos(n * 2 * Math.PI / sides))
);
ctx.stroke();
It works wonderfully, but I need to incorporate a way to rotate the polygon, without using the ctx.rotate() function.
Thanks for your help. If you down vote, please tell me why so I can improve this question.
Here's code to generate a regular polygon with the first vertex at zero-angle-right-of-center:
The code uses trigonometry to rotate the polygon instead of context.rotate.
function regularPolygon(cx,cy,sides,radius,radianRotation){
var deltaAngle=Math.PI*2/sides;
var x=function(rAngle){return(cx+radius*Math.cos(rAngle-radianRotation));}
var y=function(rAngle){return(cy+radius*Math.sin(rAngle-radianRotation));}
ctx.beginPath();
ctx.moveTo(x(0),y(0));
for(n = 1; n <= sides; n++){
var angle=deltaAngle*n;
ctx.lineTo(x(angle),y(angle));
}
ctx.closePath();
ctx.stroke();
}
The title kind of says it all, I am trying to move an object forward depending on the angle it is at.
Here is my relevant code:
xView = this.x-this.width/2;
yView = this.y-this.height/2;
playerCtx.drawImage(imgSprite, 0, 0, this.width, this.height, xView, yView, this.width, this.height);
playerCtx.save();
playerCtx.clearRect(0, 0, game.width, game.height);
playerCtx.translate(xView, yView);
playerCtx.rotate(angle *Math.PI/180);
playerCtx.drawImage(imgSprite, 0, 0, this.width, this.height, -xView, -yView, this.width, this.height);
playerCtx.restore();
}
if(Game.controls.left) {
angle-=1;
if(Game.controls.up){
this.x += this.speed * Math.cos(angle * Math.PI / 180);
this.y -= this.speed * Math.sin(angle * Math.PI / 180);
}
The object doesn't move corresponding to the var angle.
EDIT
I couldn't figure out why my code wasn't working so I instead used a sprite sheet containing 36 different angles. This works, however the rotation is too fast. If anyone could tell me why the above isn't working properly, or how I would go about making the following function go slower:
if(Game.controls.right) {
currentFrame++;
angle+=10;
}
By slower I mean when the left key is held down, angle+10; currentFrame++; are raising to fast, and adding more Frames may take too long.
EDIT
Added a Jfiddle for my original question, the angle variable moves with the rotation, for an example if the object is facing Right, angle will equal 90, but the object still doesn't look like its moving to the right, although the camera does.
Try to change cos and sin around as well as the sign:
this.x += this.speed * Math.cos(angle * Math.PI / 180);
this.y += this.speed * Math.sin(angle * Math.PI / 180);
To make your rotation (in the edited part of the question) you need to reduce the steps basically as well as provide more sprites. More sprites won't be slower but will use more memory and increase initial load time a tad.
UPDATE
Ok, there are a few things you need to correct (the above is one of them and they are corrected in the fiddle).
Modified fiddle
The other things are in:
In your update() method:
// Add both here as angle with correctly use neg/pos
if (Game.controls.up) {
this.x += this.speed * Math.cos(angle * Math.PI / 180);
this.y += this.speed * Math.sin(angle * Math.PI / 180);
}
/// Sub both here as angle with correctly use neg/pos
if (Game.controls.down) {
this.x -= this.speed * Math.cos(angle * Math.PI / 180);
this.y -= this.speed * Math.sin(angle * Math.PI / 180);
}
As the angle will determine the negative or positive value you just need to add or subtract depending on the intention, so for up add both values, for down subtract both value. The angle will make sure the delta is correctly signed.
In your draw function there are a few things:
Player.prototype.draw = function (context, xView, yView) {
// Add the half width instead of subtract it
xView = this.x + this.width / 2;
yView = this.y + this.height / 2;
// not needed as you clear the canvas in the next step
//playerCtx.drawImage(imgSprite, 0, 0, ....
playerCtx.save();
playerCtx.clearRect(0, 0, game.width, game.height);
// make sure pivot is moved to center before rotation
playerCtx.translate(xView, yView);
// rotate, you should make new sprite where direction
// points to the right. I'm add 90 here to compensate
playerCtx.rotate((angle + 90) * Math.PI / 180);
// translate back before drawing the sprite as we draw
// from the corner and not the center of the image
playerCtx.translate(-xView, -yView);
playerCtx.drawImage(imgSprite, 0, 0, this.width, this.height, this.x, this.y, this.width, this.height);
playerCtx.restore();
}
Now you will see the things works.
You images should always be drawn pointing right. This is because that is always angle at 0 degrees. This will save you some trouble. I compensated for this is the draw function by adding 90 degrees to the angle but this should rather be adjusted in the sprite itself.
In addition I modified your key handler (see demo for details).