Nodejs - prevent socket.io from dropping the frame rates - javascript

I'm trying to program some animations in html5 canvas. I need the animations to be reproduced on any other client connected to my server. So what I do is I send the function to be called and the arguments as a string and call eval() on the client side. That way the animation logic needs to be done only on one canvas, while the function calls that actually render things are executed by all clients.
However, my frame rates drop drastically when I do this. I use socket.emit() to send signals to the server which in turn calls socket.broadcast.emit() to send the string to all clients. This is the rendering loop :
var rf = function()
{
// clear background
context.fillStyle = "#000";
context.fillRect(0, 0, width, height);
socket.emit('action', 'clearScreen', "{}");
// mouse position to head towards
var cx = (mousex - width / 2) + (width / 2),
cy = (mousey - height / 2) + (height / 2);
// update all stars
var sat = Floor(Z * 500); // Z range 0.01 -> 0.5
if (sat > 100) sat = 100;
for (var i=0; i<units; i++)
{
var n = stars[i], // the star
xx = n.x / n.z, // star position
yy = n.y / n.z,
e = (1.0 / n.z + 1) * 2; // size i.e. z
if (n.px !== 0)
{
// hsl colour from a sine wave
context.strokeStyle = "hsl(" + ((cycle * i) % 360) + "," + sat + "%,80%)";
context.lineWidth = e;
context.beginPath();
socket.emit('action', 'context.beginPath', "{}");
context.moveTo(xx + cx, yy + cy);
socket.emit('action', 'context.moveTo', "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}");
context.lineTo(n.px + cx, n.py + cy);
socket.emit('action', 'context.lineTo', "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}");
context.stroke();
socket.emit('action', 'context.stroke', "{}");
}
// update star position values with new settings
n.px = xx;
n.py = yy;
n.z -= Z;
// reset when star is out of the view field
if (n.z < Z || n.px > width || n.py > height)
{
// reset star
resetstar(n);
}
}
// colour cycle sinewave rotation
cycle += 0.01;
requestAnimFrame(rf);
};
requestAnimFrame(rf);
The above snippet was taken from here.
Can you suggest ways to prevent the frame rates from dropping? I guess this can be done if socket.emit was non-blocking.
Sending strings that reproduces the frame is the lightest way to accomplish what i want. Sending pixels are even more heavy. Sending strings works fine when the frame is easy to draw - for example a simple circle moving up and down renders fine.

I've been down the road you're traveling now. It's an interesting & fun road, but sometimes a frustrating road. Have fun and be patient!
OK...here are a few hints:
All your drawing commands must be atomic. Each command sent to any client must define a complete drawing operation (a complete path operation from beginPath through stroke).
Make your clients smart. Give each client at least enough javascript smarts to draw each primitive shape after being given a primitive definition. Client side functions: drawLine(), drawRect(), drawCircle(), drawBCurve(), drawQCurve(), drawText(), etc.
Emits can be lost. This is why it's not a good idea to just send raw context commands. Include a serial index# in each command object and, after processing each command array, append that new array to a master array (the master array contains all arrays ever received). This way the client can recognize and request replacements of missing packets. Hint: you can even use this master array to replay the entire animation ;-)
Here are some example snippets which expand my hints (not complete or tested!):
On the computer issuing the drawing commands
// emits can be lost
// include an index# with each command so clients
// can request replacements for lost commands
var nextCommand=1;
// an array to hold all commands to be sent with the next emit
var commands=[];
// Example: push 1 atomic line command into the commands array
commands.push({
op:'line',
x0:xx+cx,
y0:yy+cy,
x1:n.px+cx,
y1:n.py+cy,
stroke:"hsl("+((cycle*i)%360)+","+sat+"%,80%)",
fill:null,
index:(nextCommand++),
});
// Continue adding commands to be sent in this emit
// Push any more atomic drawing commands into commands[]
// You will probably generate many commands through a loop
// serialize the commands array and emit it
socket.emit(JSON.stringify(commands));
On each client
// context color changes are expensive
// Don't change the color if the context is already that color
// These vars hold the current context color for comparison
var currentStroke='';
var currentFill='';
// deserialize the incoming commands back into an array of JS command objects
var commands=JSON.parse(receivedCommands);
// process each command in the array
for(var i=0;i<commands.length;i++){
var cmd=commands[i];
// execute each command's atomic drawing based on op-type
switch(cmd.op){
case 'line':
drawLine(cmd);
break;
// ... also 'circle','rect',etc.
}
}
// draw a line defined by an atomic line drawing command
function drawLine(cmd){
if(stroke){
// execute primitive line commands
context.beginPath();
context.moveTo(cmd.x0,cmd.y0);
context.lineTo(cmd.x1,cmd.y1);
// Don't change the color if the context is already that color
if(stroke!==currentStroke){
context.strokeStyle=stroke;
currentStroke=stroke;
}
// stroke the line (this completes an atomic line draw--beginPath thru stroke)
context.stroke();
}
}

Emit only once per frame, sending an object actions containing all the informations
var rf = function()
{
var actions = {};//prepare the object to be sent
// clear background
context.fillStyle = "#000";
context.fillRect(0, 0, width, height);
//socket.emit('action', 'clearScreen', "{}");
actions['clearScreen'] = "{}";
// mouse position to head towards
var cx = (mousex - width / 2) + (width / 2),
cy = (mousey - height / 2) + (height / 2);
// update all stars
var sat = Floor(Z * 500); // Z range 0.01 -> 0.5
if (sat > 100) sat = 100;
actions['stars'] = [];//push all the star related actions in this array
for (var i=0; i<units; i++)
{
var n = stars[i], // the star
xx = n.x / n.z, // star position
yy = n.y / n.z,
e = (1.0 / n.z + 1) * 2; // size i.e. z
if (n.px !== 0)
{
// hsl colour from a sine wave
context.strokeStyle = "hsl(" + ((cycle * i) % 360) + "," + sat + "%,80%)";
context.lineWidth = e;
context.beginPath();
//socket.emit('action', 'context.beginPath', "{}");
actions['stars'].push({
'context.beginPath' : "{}"
});
context.moveTo(xx + cx, yy + cy);
//socket.emit('action', 'context.moveTo', "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}");
actions['stars'].push({
'context.moveTo' : "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}"
});
context.lineTo(n.px + cx, n.py + cy);
//socket.emit('action', 'context.lineTo', "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}");
actions['stars'].push({
'context.lineTo' : "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}"
});
context.stroke();
//socket.emit('action', 'context.stroke', "{}");
actions['stars'].push({
'context.stroke' : "{}"
});
}
// update star position values with new settings
n.px = xx;
n.py = yy;
n.z -= Z;
// reset when star is out of the view field
if (n.z < Z || n.px > width || n.py > height)
{
// reset star
resetstar(n);
}
}
// colour cycle sinewave rotation
cycle += 0.01;
//emit only once
socket.emit('actions',actions);
requestAnimFrame(rf);
};
//requestAnimFrame(rf);
rf();//call directly

Related

Javascript create SVG path bottom up vs top down

I have a page that shows a grid of job positions and I am showing the progression from one to another by using SVG + paths to draw the connection between boxes.
My code is working just fine when I am connecting an element at the top to one at the bottom. It is finding the XY of the top box and the XY of the bottom box and connects the two.
My issue is I want to flip this code and go from the bottom up. This means I need the top XY of the bottom element and the bottom XY of the top element and draw the path.
I have been trying to flip offsets around and basically do the opposite of what is working but I think my math is wrong somewhere.
Here is what the top down approach looks like. Works just fine.
The bottom up approach however is not correct. Theres some math errors somewhere and the calculations are causing the SVG to be cut off.
I believe the answer lies within the connectElements() function as that is where the coordinates are determined.
Any thoughts on how I can get these calculations corrected?
Fiddle: http://jsfiddle.net/Ly59a2hf/2/
JS Code:
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, startX, startY, endX, endY) {
// get the path's stroke width (if one wanted to be really precize, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
// check if the svg is big enough to draw the path, if not, set heigh/width
if (svg.getAttribute("height") < endY) svg.setAttribute("height", endY);
if (svg.getAttribute("width") < (startX + stroke)) svg.setAttribute("width", (startX + stroke));
if (svg.getAttribute("width") < (endX + stroke * 3)) svg.setAttribute("width", (endX + stroke * 3));
var deltaX = (endX - startX) * 0.15;
var deltaY = (endY - startY) * 0.15;
// for further calculations which ever is the shortest distance
var delta = deltaY < absolute(deltaX) ? deltaY : absolute(deltaX);
// set sweep-flag (counter/clock-wise)
// if start element is closer to the left edge,
// draw the first arc counter-clockwise, and the second one clock-wise
var arc1 = 0;
var arc2 = 1;
if (startX > endX) {
arc1 = 1;
arc2 = 0;
}
// draw tha pipe-like path
// 1. move a bit down, 2. arch, 3. move a bit to the right, 4.arch, 5. move down to the end
path.setAttribute("d", "M" + startX + " " + startY +
" V" + (startY + delta) +
" A" + delta + " " + delta + " 0 0 " + arc1 + " " + (startX + delta * signum(deltaX)) + " " + (startY + 2 * delta) +
" H" + (endX - delta * signum(deltaX)) +
" A" + delta + " " + delta + " 0 0 " + arc2 + " " + endX + " " + (startY + 3 * delta) +
" V" + (endY - 30));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer'),
svgTop = getOffset(svgContainer).top,
svgLeft = getOffset(svgContainer).left,
startX,
startY,
endX,
endY,
startCoord = startElem,
endCoord = endElem;
console.log(svg, path, startElem, endElem, type, direction)
/**
* bottomUp - This means we need the top XY of the starting box and the bottom XY of the destination box
* topDown - This means we need the bottom XY of the starting box and the top XY of the destination box
*/
switch (direction) {
case 'bottomUp': // Not Working
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
case 'topDown': // Working
// If first element is lower than the second, swap!
if (startElem.offsetTop > endElem.offsetTop) {
var temp = startElem;
startElem = endElem;
endElem = temp;
}
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
}
// Call function for drawing the path
drawPath(svg, path, startX, startY, endX, endY, type);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line', direction);
}
//connectAll('topDown'); // Works fine. Path goes from the bottom of box_1 to the top of box_20
connectAll('bottomUp'); // Doesn't work. I expect path to go from top of box_20 to the bottom of box_1
IMO, you can simplify things by making the SVG the exact right size. Ie. fit it between the two elements vertically, and have it start at the leftmost X coord.
If you do that, the path starts and ends at either:
X: 0 or svgWidth
Y: 0 or svgHeight.
Then as far as drawing the path goes, it's just a matter of using the relative directions (startX -> endX and startY -> endY) in your calculations. I've called these variables xSign and ySign. If you are consistent with those, everything works out correctly.
The last remaining complication is working out which direction the arcs for the rounded corners have to go - clockwise or anticlockwise. You just have to work out the first one, and the other one is the opposite.
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, start, end) {
// get the path's stroke width (if one wanted to be really precise, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
var arrowHeadLength = stroke * 3;
var deltaX = (end.x - start.x) * 0.15;
var deltaY = (end.y - start.y) * 0.15;
// for further calculations which ever is the shortest distance
var delta = Math.min(Math.abs(deltaX), Math.abs(deltaY));
var xSign = Math.sign(deltaX);
var ySign = Math.sign(deltaY);
// set sweep-flag (counter/clock-wise)
// If xSign and ySign are opposite, then the first turn is clockwise
var arc1 = (xSign !== ySign) ? 1 : 0;
var arc2 = 1 - arc1;
// draw tha pipe-like path
// 1. move a bit vertically, 2. arc, 3. move a bit to the horizontally, 4.arc, 5. move vertically to the end
path.setAttribute("d", ["M", start.x, start.y,
"V", start.y + delta * ySign,
"A", delta, delta, 0, 0, arc1, start.x + delta * xSign, start.y + 2 * delta * ySign,
"H", end.x - delta * xSign,
"A", delta, delta, 0, 0, arc2, end.x, start.y + 3 * delta * ySign,
"V", end.y - arrowHeadLength * ySign].join(" "));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer');
// Calculate SVG size and position
// SVG is sized to fit between the elements vertically, start at the left edge of the leftmost
// element and end at the right edge of the rightmost element
var startRect = getOffset(startElem),
endRect = getOffset(endElem),
pathStartX = startRect.left + startRect.width / 2,
pathEndX = endRect.left + endRect.width / 2,
startElemBottom = startRect.top + startRect.height,
svgTop = Math.min(startElemBottom, endRect.top + endRect.height),
svgBottom = Math.max(startRect.top, endRect.top),
svgLeft = Math.min(pathStartX, pathEndX),
svgHeight = svgBottom - svgTop;
// Position the SVG
svg.style.left = svgLeft + 'px';
svg.style.top = svgTop + 'px';
svg.style.width = Math.abs(pathEndX - pathStartX) + 'px';
svg.style.height = svgHeight + 'px';
// Call function for drawing the path
var pathStart = {x: pathStartX - svgLeft, y: (svgTop === startElemBottom) ? 0 : svgHeight};
var pathEnd = {x: pathEndX - svgLeft, y: (svgTop === startElemBottom) ? svgHeight : 0};
drawPath(svg, path, pathStart, pathEnd);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line');
}
//connectAll('topDown');
connectAll('bottomUp');
http://jsfiddle.net/93Le85tk/3/

Canvas fill color bug in chrome

Here's a picture of the issue. I'm drawing this color wheel, and using
var context = canvas.getContext("2d");
...
context.fillStyle = "rgba(" + r + ", " + g + ", " + b + ", 1)";
context.fill();
for each little differently colored section in the wheel.
the order of the browsers here are:
Chrome | Firefox | IE
And for whatever reason it looks all messed up in Chrome. I'm not even really sure how to properly describe this issue so it's hard to look to see if this is a known issue.
edit: here's a fiddle https://jsfiddle.net/mattlokk/7whrmo8r/3/
edit 2: seems to only be happening in Chrome version 58.x, and it seems to work fine on some machines no matter what Chrome version.
Workaround
Yes there does seam to be a bug with the arc function in chrome (I used Canary 60)
1st workaround
As a bug is not something you can solve you need to use a workaround. In this case a simple solution is to create a shadow function for arc.
// x,y center of arc
// r is radius
// start and end are in radians
// dir (optional) if truthy true draw path anti clock wise.
// returns undefined;
function arcFix (x, y, r, start, end, dir) {
var ang;
var step = 1 / r;
if (dir) {
end -= step / 2; // end bumped down by half a step to ensure rounding error does not miss end
for (ang = start; ang > end; ang -= step) {
context.lineTo(
Math.cos(ang) * r + x,
Math.sin(ang) * r + y,
);
}
} else {
end += step / 2; // end bumped up half a step to ensure rounding error does not miss end
for (ang = start; ang < end; ang += step) {
context.lineTo(
Math.cos(ang) * r + x,
Math.sin(ang) * r + y,
);
}
}
}
Add the above function and in the code where you render each colour segment replace the two ctx.arc calls calling the above function
context.beginPath();
context.moveTo(innerX1, innerY1);
context.lineTo(outerX1, outerY1);
// use shadow function
arcFix(centerX, centerY, (outerRadius - 1), startRadians, endRadians);
context.lineTo(innerX2, innerY2);
// use shadow function
arcFix(centerX, centerY, (innerRadius + 1), endRadians, startRadians, true);
Please note that the arcFix function is not a complete replacement for arc and has only been tested for this particular situation. You should test it (as you should test all code) if you use it else where.
2nd workaround
Removing the +1 and -1 from the radius fixes the problem as well. Not that you have incorrectly used the 2D context as the effect is creating a alpha gradient and that should not happen no matter where you add path segments.
context.lineTo(outerX1, outerY1);
context.arc(centerX, centerY, outerRadius , startRadians, endRadians);
// ^ remove - 1
context.lineTo(innerX2, innerY2);
context.arc(centerX, centerY, innerRadius , endRadians, startRadians, true);
// ^ remove + 1

Making a dataplot in HTML 5's Canvas

I'm working in canvas to take information and plot it onto a graph. Eventually it will go to php and retrieve the data from a server, but for now I just need it to plot any data correctly.
I have the canvas drawn out how I'd like it and began plotting the data, but when I do it doesn't give me a thin path, it's more like a giant blob that covers everything. When looking at my code, it's important to know that it is mostly just initialization of the canvas, but I need to post mostly all of it in order to give context for what is happening in the program.
var canvas ;
var context ;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Apple = [10.25,10.30,10.10,10.20];
function init() {
Val_max = 10.5;
Val_min = 10;
var stepSize = .049999999999999; //.5 results in inaccurate results
var columnSize = 50;
var rowSize = 20;
var margin = 20;
var xAxis = [" "," "," ", " ", " ", "10AM", " ", " ", " ", " ", " ", "11AM", " ", " ", " ", " ", " ", "12PM", " ", " ", " ", " ", " ", "1PM", " ", " ", " ", " ", " ", "2PM", " ", " ", " ", " ", " ", "3PM", " ", " ", " ", " ", " ", "4PM"];
sections = xAxis.length-1;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#808080";
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min); // Total height of the graph/range of graph
xScale = (canvas.width - rowSize - margin) / sections; // Total width of the graph/number of ticks on the graph
context.strokeStyle="#808080"; // color of grid lines
context.beginPath();
// print Parameters on X axis, and grid lines on the graph
context.moveTo(xScale+margin, columnSize - margin);
context.lineTo(xScale+margin, columnSize + (yScale * 10 * stepSize) - margin); //draw y axis
for (i=1;i<=sections;i++) {
var x = i * xScale;
context.moveTo(x + margin, columnSize + (yScale * 10 * stepSize) - margin);
context.lineTo(x + margin, columnSize + (yScale * 10 * stepSize) - margin - 5); //draw ticks along x-axis
context.fillText(xAxis[i], x,canvas.height - margin); //Time along x axis
}
// print row header and draw horizontal grid lines
var count = 0;
context.moveTo(xScale+margin, 260);
context.lineTo(canvas.width - margin, 260); // draw x axis
for (scale=Val_max;scale>=Val_min;scale = scale - stepSize) {
scale = scale.toFixed(2);
var y = columnSize + (yScale * count * stepSize) - margin;
context.fillText(scale, margin - 20,y);
context.moveTo(xScale+margin, y);
context.lineTo(xScale+margin+5, y); //Draw ticks along y-axis
count++;
}
context.stroke();
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#FF0066";
//plotData(Apple);
context.strokeStyle="#9933FF";
//plotData(Samsung);
context.strokeStyle="#000";
//plotData(Nokia);
}
Ok that's the initialization of the canvas, I know it's messy but I think I'll have to reference something from it for the next function.
function plotData(dataSet) {
var margin = 20;
context.beginPath();
context.moveTo(xScale+margin, dataSet[0]);
for (i=0;i<sections;i++) {
context.lineTo(i * xScale + margin, dataSet[i]);
}
context.stroke();
}
This function is supposed to take the data from the array and plot it on the graph. I can get it to draw, but it's not a thin line. Here's a picture of the blob that I'm getting.
It also doesn't seem to be accurately plotting the coordinates from my array either.
I know this question is pretty in depth, but any help would be very appreciated!
The translate and scale are applied to the current transform. Each time you call them you translate and scale a little more.
Use save and restore to get back the original transform.
context.save(); // <--------------------- added
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#FF0066";
//plotData(Apple);
context.strokeStyle="#9933FF";
//plotData(Samsung);
context.strokeStyle="#000";
//plotData(Nokia);
context.restore(); // <-------------------- added

HTML5 Canvas - Javascript -how to calculate a straight path between any coordinates

I recognize various forms of this question have been asked and generally answered in this forum. However I find I am still at a loss as to how to make this work.
The scenario:
I have an HTML5 Canvas where the 0,0 coordinate is top left.
I have an object (player) that can move around the canvas with using
wasd.
I want to be able to have the player shoot(cast, throw, whatever)
something from their current x,y coordinate in the direction of where
the mouse is clicked.
I want to animate an object(bullet, fireball, whatever) in a straight line between the origin (player location when mouse clicked) and destination (xy where mouse is clicked)
I can get all the origin and destination coords and I try to put them into a vector function to determine what the next x,y coordinate is to animate the projectile at but am failing at understanding how to do this.
One important note (possibly important): I do not want to use other libraries or jquery I just want to write it in javascript.
I am basically working from the Lost Decades simple HTML5 game example and trying to extend it
I create a projectile object like so:
spl = {
ox: 0,
oy: 0,
dx: 0,
dy: 0,
speed: 256,
vector: {
len: 0
}
};
on a mouseclick event listener I do this:
addEventListener("mousedown", function(e){
spl.ox = mvo.x;
spl.oy = mvo.y;
spl.dx = e.x;
spl.dy = e.y;
spl.vector = doVector(spl.ox, spl.oy, spl.dx, spl.dy);
}, false);
Where
spl.ox, oy is the position of the player at the click event
spl.dx, dy is the vector of the mouse click
my doVector function is me just trying to work out my linear algebra math like this (it doesn't seem to work out logically to me):
function doVector(ox, oy, dx, dy){
var diffX = (dx - ox);
var diffY = (dy - oy);
var length = Math.round(Math.sqrt(diffX*diffX+diffY*diffY));
var normX = Math.round(dx/length);
var normY = Math.round(dy/length);
var normProof = (normX*normX+normY*normY);
var dotProd = (ox*dx)+(oy*dy);
return{
len: length,
dist: dist,
normX: normX,
normY: normY,
normProof: normProof,
dotProd: dotProd
}
}
My update function (which I presume is where I should put my incrementing vector for the spl object) just handles the player movement with wasd at the moment:
//update objects --/////////////dddw
var update = function(modifier){
if(87 in keysDown){ //up
mvo.y -= Math.round(mvo.speed * modifier);
}
if(83 in keysDown){ //down
mvo.y += Math.round(mvo.speed * modifier);
}
if(65 in keysDown){ //left
mvo.x -= Math.round(mvo.speed * modifier);
}
if(68 in keysDown){ //right
mvo.x += Math.round(mvo.speed * modifier);
}
}// END update objects --/////////////
my render function is bloated as I am trying to figure out the math for the vector/velocity thing:
// render everything
var render = function (){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.font = "10px verdana";
ctx.fillStyle = "#000088";
ctx.fillText("MVO", mvo.x, mvo.y);
ctx.fillText(mvo.x + ", " + mvo.y, mvo.x-9, mvo.y+11);
ctx.fillStyle = "#008800";
ctx.fillText("OXY", spl.ox, spl.oy);
ctx.fillText(spl.ox + "," + spl.oy, spl.ox-9, spl.oy+11);
ctx.fillStyle = "#880000";
ctx.fillText("DXY", spl.dx-18, spl.dy-18);
ctx.fillText(spl.dx + "," + spl.dy, spl.dx-29, spl.dy-7);
ctx.font = "12px verdana";
ctx.fillStyle = "#bbbbbb";
ctx.fillText("mvo x,y: " + mvo.x + ", " + mvo.y, 32, 32);
ctx.fillText("thing: ", 32, 44);
ctx.fillText("thing: ", 32, 56);
ctx.fillText("thing: ", 32, 68);
ctx.fillText("thing:" , 32, 80);
ctx.fillText("spl origin: " + spl.ox + "," + spl.oy, 525, 32);
ctx.fillText("spl destination: " + spl.dx + "," + spl.dy, 525, 44);
ctx.fillText("vector length: " + spl.vector.len, 525, 56);
ctx.fillText("spl normalized: " + spl.vector.normX + "," + spl.vector.normY, 525, 68);
ctx.fillText("spl norm proof: " + spl.vector.normProof, 525, 80);
ctx.fillText("spl norm dotProd: " + spl.vector.dotProd, 525, 92);
}
and finally my main loop looks like so
// Main loop
var main = function(){
var now = Date.now();
var delta = now - then;
update(delta/1000);
render();
then = now;
requestAnimationFrame(main);
};
Now, first, if you have read and followed all this, thank you very much for your brain cycles.
Secondly, all I want to do is determine how to recalculate the vector for an object moving between two coordinates and update the x,y position to draw at accordingly.
I may be entirely stupid becuase I read this great thing on linear algebra for games but seem unable to make it work. Any help would be really greatly appreciated.
Don't round your direction vector or your bullet direction will be off and it will miss its target.
And as enhzflep said use diffX and diffY insted of dx and dy for normalization or it will be completely wrong.
var length = Math.sqrt(diffX*diffX+diffY*diffY);
var normX = diffX/length;
var normY = diffY/length;
To actually move the bullet, you must update its position in your update function. You need to update bullets position by adding its direction multiplied with speed and time delta (modifier).
spl.ox += spl.vector.normX * spl.speed * modifier;
spl.oy += spl.vector.normY * spl.speed * modifier;
You can then draw it in your render function
ctx.beginPath();
ctx.arc(spl.ox, spl.oy, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "#000000";
ctx.fill();
ctx.fillText(spl.ox.toFixed(2) + "," + spl.oy.toFixed(2), spl.ox-40, spl.oy+6);
JSFiddle

Handdrawn circle simulation in HTML 5 canvas

The following code creates a circle in HTML 5 Canvas using jQuery:
Code:
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
DrawCircle(75, 75, 20);
//draw a circle
function DrawCircle(x, y, radius)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.fillStyle = 'transparent';
ctx.lineWidth = 2;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.closePath();
ctx.fill();
}
I am trying to simulate any of the following types of circles:
I have researched and found this article but was unable to apply it.
I would like for the circle to be drawn rather than just appear.
Is there a better way to do this? I'm sensing there's going to be a lot of math involved :)
P.S. I like the simplicity of PaperJs, maybe this would be the easiest approach using it's simplified paths?
There are already good solutions presented here. I wanted to add a variations of what is already presented - there are not many options beyond some trigonometry if one want to simulate hand drawn circles.
I would first recommend to actually record a real hand drawn circle. You can record the points as well as the timeStamp and reproduce the exact drawing at any time later. You could combine this with a line smoothing algorithm.
This here solution produces circles such as these:
You can change color, thickness etc. by setting the strokeStyle, lineWidth etc. as usual.
To draw a circle just call:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
(callback is provided as the animation makes the function asynchronous).
The code is separated into two segments:
Generate the points
Animate the points
Initialization:
function handDrawCircle(ctx, cx, cy, r, rounds, callback) {
/// rounds is optional, defaults to 3 rounds
rounds = rounds ? rounds : 3;
var x, y, /// the calced point
tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
dx = Math.random() * tol * 0.75, /// "bouncer" values
dy = Math.random() * tol * 0.75,
ix = (Math.random() - 1) * (r * 0.0044), /// speed /incremental
iy = (Math.random() - 1) * (r * 0.0033),
rx = r + Math.random() * tol, /// radius X
ry = (r + Math.random() * tol) * 0.8, /// radius Y
a = 0, /// angle
ad = 3, /// angle delta (resolution)
i = 0, /// counter
start = Math.random() + 50, /// random delta start
tot = 360 * rounds + Math.random() * 50 - 100, /// end angle
points = [], /// the points array
deg2rad = Math.PI / 180; /// degrees to radians
In the main loop we don't bounce around randomly but increment with a random value and then increment linearly with that value, reverse it if we are at bounds (tolerance).
for (; i < tot; i += ad) {
dx += ix;
dy += iy;
if (dx < -tol || dx > tol) ix = -ix;
if (dy < -tol || dy > tol) iy = -iy;
x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);
points.push(x, y);
}
And in the last segment we just render what we have of points.
The speed is determined by da (delta angle) in the previous step:
i = 2;
/// start line
ctx.beginPath();
ctx.moveTo(points[0], points[1]);
/// call loop
draw();
function draw() {
ctx.lineTo(points[i], points[i + 1]);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(points[i], points[i + 1]);
i += 2;
if (i < points.length) {
requestAnimationFrame(draw);
} else {
if (typeof callback === 'function')
callback();
}
}
}
Tip: To get a more realistic stroke you can reduce globalAlpha to for example 0.7.
However, for this to work properly you need to draw solid to an off-screen canvas first and then blit that off-screen canvas to main canvas (which has the globalAlpha set) for each frame or else the strokes will overlap between each point (which does not look good).
For squares you can use the same approach as with the circle but instead of using radius and angle you apply the variations to a line. Offset the deltas to make the line non-straight.
I tweaked the values a little but feel free to tweak them more to get a better result.
To make the circle "tilt" a little you can first rotate the canvas a little:
rotate = Math.random() * 0.5;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);
and when the loop finishes:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
}
(included in the demo linked above).
The circle will look more like this:
Update
To deal with the issues mentioned (comment fields too small :-) ): it's actually a bit more complicated to do animated lines, especially in a case like this where you a circular movement as well as a random boundary.
Ref. comments point 1: the tolerance is closely related to radius as it defined max fluctuation. We can modify the code to adopt a tolerance (and ix/iy as they defines how "fast" it will variate) based on radius. This is what I mean by tweaking, to find that value/sweet-spot that works well with all sizes. The smaller the circle the smaller the variations. Optionally specify these values as arguments to the function.
Point 2: since we're animating the circle the function becomes asynchronous. If we draw two circles right after each other they will mess up the canvas as seen as new points are added to the path from both circles which then gets stroked criss-crossed.
We can get around this by providing a callback mechanism:
handDrawCircle(context, x, y, radius [, rounds] [, callback]);
and then when the animation has finished:
if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
if (typeof callback === 'function')
callback(); /// call next function
}
Another issues one will run into with the code as-is (remember that the code is meant as an example not a full solution :-) ) is with thick lines:
When we draw segment by segment separately canvas does not know how to calculate the butt angle of the line in relation to previous segment. This is part of the path-concept. When you stroke a path with several segments canvas know at what angle the butt (end of the line) will be at. So here we to either draw the line from start to current point and do a clear in between or only small lineWidth values.
When we use clearRect (which will make the line smooth and not "jaggy" as when we don't use a clear in between but just draw on top) we would need to consider implementing a top canvas to do the animation with and when animation finishes we draw the result to main canvas.
Now we start to see part of the "complexity" involved. This is of course because canvas is "low-level" in the sense that we need to provide all logic for everything. We are basically building systems each time we do something more with canvas than just draw simple shapes and images (but this also gives the great flexibility).
Here are some basics I created for this answer:
http://jsfiddle.net/Exceeder/TPDmn/
Basically, when you draw a circle, you need to account for hand imperfections. So, in the following code:
var img = new Image();
img.src="data:image/png;base64,...";
var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
ctx.drawImage(img, x, y);
}
for (var i=0; i<500; i++) {
var radiusError = +10 - i/20;
var d = 2*Math.PI/360 * i;
draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}
Pay attention how vertical radiusError changes when the angle (and the position) grows. You are welcome to play with this fiddle until you get a "feel" what component does what. E.g. it would make sense to introduce another component to radiusError that emulates "unsteady" hand by slowly changing it my random amounts.
There are many different ways to do this. I choose trig functions for the simplicity of the simulation, as speed is not a factor here.
Update:
This, for example, will make it less perfect:
var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);
Obviously, the center of the circle is at (200,200), as the formula for drawing a circle (rather, ellipsis with vertical radius RY and horizontal radius RX) with trigonometric functions is
x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )
Your task seems to have 3 requirements:
A hand-drawn shape.
An “organic” rather than “ultra-precise” stroke.
Revealing the circle incrementally instead of all-at-once.
To get started, check out this nice on-target demo by Andrew Trice.
This amazing circle is hand drawn by me (you can laugh now...!)
Andrew's demo does steps 1 and 2 of your requirements.
It lets you hand draw a circle (or any shape) using an organic looking “brush effect” instead of the usual ultra-precise lines normally used in canvas.
It achieves the “brush effect” by by repeated drawing a brush image between hand drawn points
Here’s the demo:
http://tricedesigns.com/portfolio/sketch/brush.html#
And the code is available on GitHub:
https://github.com/triceam/HTML5-Canvas-Brush-Sketch
Andrew Trice’s demo draws-and-forgets the lines that make up your circle.
Your task would be to impliment your third requirement (remembering strokes):
Hand draw a circle of your own,
Save each line segment that makes up your circle in an array,
“Play” those segements using Andrew’s stylized brush technique.
Results: A hand-drawn and stylized circle that appears incrementally instead of all at once.
You have an interesting project…If you feel generous, please share your results!
See live demo here. Also available as a gist.
<div id="container">
<svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>
#container {
width:500px;
height:300px;
}
path.ln {
stroke-width: 3px;
stroke: #666;
fill: none;
vector-effect: non-scaling-stroke;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
-webkit-animation: dash 5s ease-in forwards;
-moz-animation:dash 5s ease-in forwards;
-o-animation:dash 5s ease-in forwards;
animation:dash 5s ease-in forwards;
}
#keyframes dash {
to { stroke-dashoffset: 0; }
}
function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {
var c = 0.551915024494;
var atan = Math.atan(c)
var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
var path = 'M';
path += [r * Math.sin(el), r * Math.cos(el)];
path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];
for (var i = 0; i < 4; i++) {
el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
}
return path;
}
function cX(λ_min, λ_max, el_min, el_max) {
var el = (el_min + Math.random()*(el_max - el_min));
return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}
function canvasArea() {
var width = Math.floor((Math.random() * 500) + 450);
var height = Math.floor((Math.random() * 300) + 250);
$('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));
setTimeout(function() { location = '' } ,5000)

Categories