I am trying to build a simple game for educational purposes and i am having problem with drawing object in correct coordinates.
I had svg spaceship that i wanted to utilise, so i converted it to canvas paths system using this tool SVG to Canvas converter.
Since svg was originally designed on 1000x1000 plane i had to apply scale to reach desired size
// calculate scale, if size (r) has to be 40px then 40px / original size (1000) = 0.04;
var size = r / 1000;
ctx.scale(size, size); // apply desired size
It seemed to work but then when it comes to rendering it in set coordinates (x,y), it clearly is off the mark.
In the demo you can see that ship is rotating and moving outside of where it should be, also disproportion seems to vary depending on location which clearly means something is wrong.
Maybe someone could find the cause and how i can fix it? The aim ofc is for the ship to be rendered in the center of helper box.
This is demo code:
var cvs = document.querySelector('canvas'),
ctx = cvs.getContext('2d'),
w = cvs.width = 1000,
h = cvs.height = 1000,
helper = document.querySelector('.helper');
var ship = function(x, y, r, a) {
var size = r / 1000; // calculate scale, if size (r) has to be 40px then 40px / original size (1000) = 0.04;
x -= r / 2; // go back half of its width to center ship on passed x coord
y -= r / 2; // go back half of its height to center ship on passed y coord
/* draw original ship ----> */
ctx.save();
ctx.translate(x, y);
ctx.scale(size, size); // apply desired size
ctx.rotate((a + 90) * Math.PI / 180); // rotate ship on its center
ctx.translate(-x, -y);
ctx.beginPath();
ctx.moveTo(341.4,856.1);
ctx.lineTo(173.2,881.8000000000001);
ctx.bezierCurveTo(159.79999999999998,883.8000000000001,146.5,877.7,139.39999999999998,866.2);
ctx.lineTo(76.69999999999997,764.2);
ctx.bezierCurveTo(73.19999999999997,758.5,71.59999999999998,752,71.79999999999997,745.6);
ctx.bezierCurveTo(71.79999999999997,745.1,71.69999999999997,744.5,71.69999999999997,744);
ctx.lineTo(71.69999999999997,528.2);
ctx.bezierCurveTo(71.69999999999997,509.6,86.79999999999997,494.50000000000006,105.39999999999998,494.50000000000006);
ctx.bezierCurveTo(123.99999999999997,494.50000000000006,139.09999999999997,509.6000000000001,139.09999999999997,528.2);
ctx.lineTo(139.09999999999997,617.7);
ctx.lineTo(273.79999999999995,377.80000000000007);
ctx.lineTo(341.49999999999994,493.30000000000007);
ctx.lineTo(341.49999999999994,856.1);
ctx.closePath();
ctx.moveTo(894.7,494.5);
ctx.bezierCurveTo(876.1,494.5,861,509.6,861,528.2);
ctx.lineTo(861,617.7);
ctx.lineTo(726.3,377.8);
ctx.lineTo(658.5999999999999,493.3);
ctx.lineTo(658.5999999999999,856.1);
ctx.lineTo(826.8,881.8000000000001);
ctx.bezierCurveTo(840.1999999999999,883.8000000000001,853.5,877.7,860.5999999999999,866.1);
ctx.lineTo(923.3,764.1);
ctx.bezierCurveTo(926.8,758.4,928.4,751.9,928.1999999999999,745.5);
ctx.bezierCurveTo(928.1999999999999,745,928.3,744.4,928.3,743.9);
ctx.lineTo(928.3,528.2);
ctx.bezierCurveTo(928.3,509.6,913.3,494.5,894.7,494.5);
ctx.closePath();
ctx.moveTo(591.2,857.6);
ctx.lineTo(533.7,900.5);
ctx.lineTo(533.7,956.4);
ctx.bezierCurveTo(533.7,975,518.6,990.1,500.00000000000006,990.1);
ctx.bezierCurveTo(481.40000000000003,990.1,466.30000000000007,975,466.30000000000007,956.4);
ctx.lineTo(466.30000000000007,900.5);
ctx.lineTo(408.80000000000007,857.6);
ctx.lineTo(408.80000000000007,484.2);
ctx.bezierCurveTo(408.80000000000007,478.2,407.20000000000005,472.3,404.20000000000005,467.2);
ctx.lineTo(312.00000000000006,309.79999999999995);
ctx.lineTo(470.6,27.2);
ctx.bezierCurveTo(476.6,16.6,487.8,10,500,10);
ctx.bezierCurveTo(512.2,10,523.4,16.6,529.4,27.2);
ctx.lineTo(688.0999999999999,309.8);
ctx.lineTo(595.8999999999999,467.20000000000005);
ctx.bezierCurveTo(592.8999999999999,472.40000000000003,591.2999999999998,478.20000000000005,591.2999999999998,484.20000000000005);
ctx.lineTo(591.2999999999998,857.6);
ctx.closePath();
ctx.moveTo(591,318.2);
ctx.bezierCurveTo(605.3,306.4,607.4,285.09999999999997,595.5,270.8);
ctx.bezierCurveTo(591.6,266.1,555.8,224.60000000000002,500.8,224.60000000000002);
ctx.bezierCurveTo(446.3,224.60000000000002,409.1,265.40000000000003,405,270.1);
ctx.bezierCurveTo(392.7,284.1,394.1,305.5,408.1,317.70000000000005);
ctx.bezierCurveTo(414.5,323.30000000000007,422.40000000000003,326.1,430.3,326.1);
ctx.bezierCurveTo(439.6,326.1,448.90000000000003,322.20000000000005,455.5,314.70000000000005);
ctx.bezierCurveTo(461.2,308.40000000000003,480.4,292.1,500.8,292.1);
ctx.bezierCurveTo(524.7,292.1,543.6,313.8,543.6,313.8);
ctx.bezierCurveTo(555.5,328,576.7,330.1,591,318.2);
ctx.closePath();
ctx.fillStyle = '#000';
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.fill();
ctx.stroke();
ctx.restore();
/* <---- draw original ship */
};
var c = { x: 100, y: 100, a: 270, r: 40 };
var rotator = 1;
var render = function() {
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = '#ccc';
ctx.fillRect(0, 0, w, h);
ship(c.x, c.y, c.r, c.a);
c.x += rotator;
c.y += rotator;
c.a += rotator;
if(c.x >= 950 || c.y >= 950 || c.x <= 50 || c.y <= 50) {
rotator *= -1;
}
/* helper debug section ----> */
ctx.save();
ctx.beginPath();
ctx.moveTo(c.x, 0);
ctx.lineTo(c.x, 1000);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, c.y);
ctx.lineTo(1000, c.y);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(c.x, c.y);
ctx.lineTo(c.x, c.y - 20);
ctx.lineTo(c.x - 20, c.y - 20);
ctx.lineTo(c.x - 20, c.y);
ctx.lineTo(c.x - 20, c.y + 20);
ctx.lineTo(c.x, c.y + 20);
ctx.lineTo(c.x + 20, c.y + 20);
ctx.lineTo(c.x + 20, c.y);
ctx.lineTo(c.x + 20, c.y - 20);
ctx.lineTo(c.x, c.y - 20);
ctx.closePath();
ctx.stroke();
ctx.restore();
/* <---- helper debug section */
requestAnimationFrame(render);
};
render();
canvas {
position:absolute;
top:0;
left:0;
}
<canvas></canvas>
A nice thing to know is that you can draw svg paths in canvas using Path2D as I did in the next demo. Also I've recalculated your path. The path you are using is 1000/1000 units. I've recalculated your path to begin at -500 both in x and y, putting the center in the origin: point {x:0,y:0}.
let svgPath =`M-158.60000000000002,356.1L-326.8,381.79999999999995C-340.2,383.79999999999995,-353.5,377.70000000000005,-360.6,366.20000000000005L-423.3,264.20000000000005C-426.8,258.5,-428.4,252,-428.2,245.60000000000002,-428.2,245.10000000000002,-428.3,244.5,-428.3,244L-428.3,28.200000000000045C-428.3,9.600000000000023,-413.2,-5.5,-394.6,-5.5,-376,-5.5,-360.9,9.600000000000023,-360.99,28.200000000000045L-360.9,117.70000000000005,-226.2,-122.19999999999999,-158.5,-6.699999999999989,-158.5,356.1
M394.70000000000005,-5.5C376.1,-5.5,361,9.600000000000023,361,28.200000000000045L361,117.70000000000005,226.29999999999995,-122.19999999999999,158.69000000000005,-6.699999999999989,158.60000000000002,356.1,326.79999999999995,381.79999999999995C340.20000000000005,383.79999999999995,353.5,377.70000000000005,360.6,366.1L423.29999999999995,264.1C426.79999999999995,258.4,428.4,251.89999999999998,428.20000000000005,245.5,428.20000000000005,245,428.29999999999995,244.39999999999998,428.29999999999995,243.89999999999998,428.29999999999995,9.600000000000023,413.29999999999995,-5.5,394.70000000000005,-5.5z
M91.20000000000005,357.6L33.700000000000045,400.5,33.700000000000045,456.4C33.700000000000045,475,18.600000000000023,490.1,0,490.1,-18.600000000000023,490.1,-33.69999999999999,475,-33.69999999999999,456.4L-33.69999999999999,400.5,-91.19999999999999,357.6,-91.13,-15.800000000000011C-91.19999999999999,-21.80000000000001,-92.80000000000001,-27.69999999999999,-95.80000000000001,-32.80000000000001L-188,-190.2,-29.399999999999977,-472.8C-23.399999999999977,-483.4,-12.199999999999989,-490,0,-490,12.200000000000045,-490,23.399999999999977,-483.4,29.399999999999977,-472.8L188.10000000000002,-190.2,95.89999999999998,-32.80000000000001C92.89999999999998,-27.569999999999993,91.29999999999995,-21.80000000000001,91.29999999999995,-15.800000000000011L91.29999999999995,357.6z
M91,-181.8C105.29999999999995,-193.60000000000002,107.39999999999998,-214.89999999999998,95.5,-229.2,91.60000000000002,-233.89999999999998,55.799999999999955,-275.4,0.8000000000000114,-275.4,-53.69999999999999,-275.4,-90.89999999999998,-234.60000000000002,-95,-229.89999999999998,-107.30000000000001,-215.89999999999998,-105.89999999999998,-194.5,-91.89999999999998,-182.3,-85.5,-176.7,-77.60000000000002,-173.89999999999998,-69.69999999999999,-173.89999999999998,-60.39999999999998,-173.89999999999998,-51.10000000000002,-177.8,-44.5,-185.3,-38.80000000000001,-191.60000000000002,-19.600000000000023,-207.89999999999998,0.8000000000000114,-207.89999999999998,24.700000000000045,-207.89999999999998,43.60000000000002,-186.2,43.60000000000002,-186.2,55.5,-172,76.70000000000005,-169.89999999999998,91,-181.8z`;
var cvs = document.querySelector('canvas'),
ctx = cvs.getContext('2d'),
w = cvs.width = window.innerWidth,
h = cvs.height = window.innerHeight;
ctx.fillStyle = "black";
let shuttle = new Path2D(svgPath);
let angle = 0;
let x = 0;
let y = 0;
let increment = 1
let the_scale = .1;
//helper size
//1000 is the size of the svg path
let hs = (1000 * the_scale) / 2;
function frame(){
window.requestAnimationFrame(frame);
ctx.clearRect(0, 0, w,h )
angle += increment/100;
x+=increment;
if(y < h + hs){y+=increment;}else{y = -hs; x=-hs;}
ctx.save();
ctx.fillStyle="#333"
ctx.translate(x,y);
ctx.scale(the_scale,the_scale);
ctx.rotate(angle);
ctx.fill(shuttle);
ctx.restore();
helper(x,y)
}
frame();
function helper(x,y){
ctx.strokeStyle="red"
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y - hs);
ctx.lineTo(x - hs, y - hs);
ctx.lineTo(x - hs, y);
ctx.lineTo(x - hs, y + hs);
ctx.lineTo(x, y + hs);
ctx.lineTo(x + hs, y + hs);
ctx.lineTo(x + hs, y);
ctx.lineTo(x + hs, y - hs);
ctx.lineTo(x, y - hs);
ctx.closePath();
ctx.stroke();
}
*{margin:0;padding:0}
canvas{background:#d9d9d9;}
<canvas></canvas>
Related
I'm trying to render a simple discrete-time signal using a canvas element. However, the representation seems to be inaccurate. As you can see in the code snippet the signal appears to be amplitude modulated after the frequency reaches a certain threshold. Even though it's well below the Nyquist limit of <50Hz (assuming a sampling rate of 100Hz in this example).
For very low frequencies like 5Hz it looks perfectly fine.
How would I go about rendering this properly? And does it work for more complex signals (say, the waveform of a song)?
window.addEventListener('load', () => {
const canvas = document.querySelector('canvas');
const frequencyElem = document.querySelector('#frequency');
const ctx = canvas.getContext('2d');
const renderFn = t => {
const signal = new Array(100);
const sineOfT = Math.sin(t / 1000 / 8 * Math.PI * 2) * 0.5 + 0.5;
const frequency = sineOfT * 20 + 3;
for (let i = 0; i < signal.length; i++) {
signal[i] = Math.sin(i / signal.length * Math.PI * 2 * frequency);
}
frequencyElem.innerText = `${frequency.toFixed(3)}Hz`
render(ctx, signal);
requestAnimationFrame(renderFn);
};
requestAnimationFrame(renderFn);
});
function render(ctx, signal) {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.clearRect(0, 0, w, h);
ctx.strokeStyle = 'red';
ctx.beginPath();
signal.forEach((value, i) => {
const x = i / (signal.length - 1) * w;
const y = h - (value + 1) / 2 * h;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
#media (prefers-color-scheme: dark) {
body {
background-color: #333;
color: #f6f6f6;
}
}
<canvas></canvas>
<br/>
Frequency: <span id="frequency"></span>
It looks right to me. At higher frequencies, when the peak falls between two samples, the sampled points can be a lot lower than the peak.
If the signal only has frequencies < Nyquist, then the signal can be reconstructed from its samples. That doesn't mean that the samples look like the signal.
As long as your signal is oversampled by 2x or more (or so), you can draw it pretty accurately by using cubic interpolation between the sample points. See, for example, Catmull-Rom interpolation in here: https://en.wikipedia.org/wiki/Cubic_Hermite_spline
You can use the bezierCurveTo method in HTML Canvas to draw these interpolated curves. If you need to use lines, then you should find any maximum or minimum points that occur between samples and include those in your path.
I've edited your snippet to use the bezierCurveTo method with Catmull-Rom interpolation below:
window.addEventListener('load', () => {
const canvas = document.querySelector('canvas');
const frequencyElem = document.querySelector('#frequency');
const ctx = canvas.getContext('2d');
const renderFn = t => {
const signal = new Array(100);
const sineOfT = Math.sin(t / 1000 / 8 * Math.PI * 2) * 0.5 + 0.5;
const frequency = sineOfT * 20 + 3;
for (let i = 0; i < signal.length; i++) {
signal[i] = Math.sin(i / signal.length * Math.PI * 2 * frequency);
}
frequencyElem.innerText = `${frequency.toFixed(3)}Hz`
render(ctx, signal);
requestAnimationFrame(renderFn);
};
requestAnimationFrame(renderFn);
});
function render(ctx, signal) {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.clearRect(0, 0, w, h);
ctx.strokeStyle = 'red';
ctx.beginPath();
const dx = w/(signal.length - 1);
const dy = -(h-2)/2.0;
const c = 1.0/2.0;
for (let i=0; i < signal.length-1; ++i) {
const x0 = i * dx;
const y0 = h*0.5 + signal[i]*dy;
const x3 = x0 + dx;
const y3 = h*0.5 + signal[i+1]*dy;
let x1,y1,x2,y2;
if (i>0) {
x1 = x0 + dx*c;
y1 = y0 + (signal[i+1] - signal[i-1])*dy*c/2;
} else {
x1 = x0;
y1 = y0;
ctx.moveTo(x0, y0);
}
if (i < signal.length-2) {
x2 = x3 - dx*c;
y2 = y3 - (signal[i+2] - signal[i])*dy*c/2;
} else {
x2 = x3;
y2 = y3;
}
ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3);
}
ctx.stroke();
}
#media (prefers-color-scheme: dark) {
body {
background-color: #333;
color: #f6f6f6;
}
}
<canvas></canvas>
<br/>
Frequency: <span id="frequency"></span>
Hi guys I been leanring WebGL and trying to make a Tetris game out of it.
I have a couple of questions I'd like to ask:
For this game I wanted to first draw the grid as the background. However I noticed that after I drew the line, if I use gl.clear(gl.COLOR_BUFFER_BIT ); after, it will clear all the lines I drew before. I understand that gl.clear(gl.COLOR_BUFFER_BIT ); is about clearing the color buffer (and you probably will ask why I would want to do that. Just bear with me. ). Then I tried use gl.uniform4f( uColor, 0, 0, 0, 1); to send the color again to the fragment shader but it doesn't help.
The snippet is like this
window.onload = function(){
getGLContext();
initShaders();
drawLines( 0, 0, 400,400 );
gl.clear(gl.COLOR_BUFFER_BIT );
gl.uniform4f( uColor, 0, 0, 0, 1);
}
For the game I need the grid as background and I need requestAnimationFrame for the game loop and will render Tetrominos inside the loop. Therefore after drawing the line I used this draw() to draw other Tetrominos. However it removes the line I drew before. And when I comment out gl.clear(gl.COLOR_BUFFER_BIT ); inside draw(), it will remove the line along with background color.
function draw() {
gl.clear(gl.COLOR_BUFFER_BIT );
gl.drawArrays(gl.TRIANGLES, 0, index*6);
requestAnimationFrame(draw);
}
Here is the demo: https://codepen.io/zhenghaohe/pen/LqxpjB
Hope you could answer these two questions. Thanks!
This is generally the way WebGL works.
WebGL is just draws into a rectangle of pixels. There is no memory of primitives. There is no structure. There is just code and the resulting canvas which is an rectangle of pixels.
Most WebGL programs/pages clear the entire canvas every frame and redraw 100% of the things they want to show every time they draw. For tetris the general code might be something like
function render() {
clear the canvas
draw the grid
draw all the stable pieces
draw the current piece
draw the next piece
draw the effects
draw the score
}
Any knowledge of primitives or other structure is entirely up to your code.
If you want the grid lines to be static then either set a static background with CSS or use another canvas
Using a background:
const gl = document.querySelector('#c').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
#c {
background-image: url(https://i.imgur.com/ZCfccZh.png);
}
<canvas id="c"></canvas>
Using 2 canvases
// this is the context for the back canvas. It could also be webgl
// using a 2D context just to make the sample simpler
const ctx = document.querySelector('#back').getContext('2d');
drawGrid(ctx);
// this is the context for the front canvas
const gl = document.querySelector('#front').getContext('webgl');
function render(time) {
time *= 0.001;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
drawBlocks(gl, time);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// --- below this line not important to the answer
function drawBlocks(gl, time) {
gl.enable(gl.SCISSOR_TEST);
const numBlocks = 5;
for (let i = 0; i < numBlocks; ++i) {
const u = i / numBlocks;
gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1);
const x = 150 + Math.sin(time + u * Math.PI * 2) * 130;
const y = 75 + Math.cos(time + u * Math.PI * 2) * 55;
gl.scissor(x, y, 20, 20);
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.disable(gl.SCISSOR_TEST);
}
function drawGrid(ctx) {
// draw grid
ctx.translate(-10.5, -5.5);
ctx.beginPath();
for (let i = 0; i < 330; i += 20) {
ctx.moveTo(0, i);
ctx.lineTo(330, i);
ctx.moveTo(i, 0);
ctx.lineTo(i, 300);
}
ctx.strokeStyle = "blue";
ctx.stroke();
}
#container {
position: relative; /* required so we can position child elements */
}
#front {
position: absolute;
left: 0;
top: 0;
}
<div id="container">
<canvas id="back"></canvas>
<canvas id="front"></canvas>
</div>
As for why it clears even if you didn't call clear that's because that's whqt the spec says it's supposed to do
See: Why WebGL 'clear' draw to front buffer?
I made an animation in javascript for a house with rising smoke. the smoke is 3 functions for each part of the smoke that flow upwards from the chimney. they are controlled by a slider that toggles the speed at which the smoke exists the chimney. Everything works except when the slider is toggled left to right, the smoke blinks while rising. Could anyone tell me why that is?
Thanks
html:
<!DOCTYPE html>
<html>
<head>
<title>Carrey, Justin, Myshkin, Rost</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<canvas id="canvas" width="500" height="500">Get a new Browser!</canvas>
<script src="script.js" ></script>
<form>
<input type="range" min="10" max="250" value="100" id="speedCont"/>
<p>
Rostislav Myshkin A00787633 rmyshkin#my.bcit.ca
<br />
Completed:3-D house, smoke, animation for smoke, slider for speed.
<br />
Challanges: animating the smoke.
</p>
</form>
</body>
</html>
javascript:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeLinecap = 'round';
var a = 1;
var speed = 100;
var posY = 100,
posY2 = 120,
posY3 = 140,
posX = 100,
vx = 5,
vy = 5;
function foundation() {
//grass
ctx.fillStyle = "green";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375);
ctx.lineTo(125, 325);
ctx.lineTo(471, 325);
ctx.lineTo(400, 375);
ctx.lineTo(25, 375);
ctx.fill();
ctx.stroke();
//front face ground
ctx.fillStyle = "#873600";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375); //top left
ctx.lineTo(25, 425); //bottom left
ctx.lineTo(400, 425); //bottom right
ctx.lineTo(400, 375); //top right
ctx.lineTo(25, 375); //top line
ctx.fill();
ctx.stroke();
//east face ground
ctx.fillStyle = "#872000";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(475, 325); //top right
ctx.lineTo(475, 375); //bottom right
ctx.lineTo(400, 425); //bottom line
ctx.lineTo(400, 375); //top left
ctx.lineTo(475, 325); //top right
ctx.fill();
ctx.stroke();
}
function house() {
//front face
ctx.fillStyle = "#2980B9";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(110, 365);
ctx.lineTo(110, 200);
ctx.lineTo(375, 200);
ctx.lineTo(375, 365);
ctx.lineTo(110, 365);
ctx.fill();
ctx.stroke();
//east face
ctx.fillStyle = "#1760B4";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(375, 200); //lower left
ctx.lineTo(415, 180); //
ctx.lineTo(415, 340);
ctx.lineTo(375, 365);
ctx.lineTo(375, 200);
ctx.fill();
ctx.stroke();
//roof front face
ctx.fillStyle = "#B41717";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(95, 210);
ctx.lineTo(160, 140);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.lineTo(365, 210);
ctx.lineTo(95, 210);
ctx.fill();
ctx.stroke();
//roof east face
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(365, 210);
ctx.lineTo(425, 190);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.fill();
ctx.stroke();
//door
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(300, 365);
ctx.lineTo(300, 295);
ctx.lineTo(250, 295);
ctx.lineTo(250, 365);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//doorknob
ctx.fillStyle = "yellow";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.arc(290, 335, 5, 0, 2 * Math.PI, false);
ctx.fill();
ctx.stroke();
//walkway
ctx.fillStyle = "gray";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(250, 365); //left point
ctx.lineTo(240, 375); //left side
ctx.lineTo(290, 375);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//window living room
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 347);
ctx.lineTo(143, 295);
ctx.lineTo(212, 295);
ctx.lineTo(212, 347);
ctx.lineTo(143, 347);
ctx.fill();
ctx.stroke();
//window top left
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 275);
ctx.lineTo(143, 225);
ctx.lineTo(212, 225);
ctx.lineTo(212, 275);
ctx.lineTo(143, 275);
ctx.fill();
ctx.stroke();
//window top right
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(263, 275);
ctx.lineTo(263, 225);
ctx.lineTo(332, 225);
ctx.lineTo(332, 275);
ctx.lineTo(263, 275);
ctx.fill();
ctx.stroke();
//chimney front
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(170, 180); //left side line
ctx.lineTo(200, 180); //bottom line
ctx.lineTo(200, 130); //right side line
ctx.lineTo(170, 130); //top side line
ctx.fill();
ctx.stroke();
//chimney east
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(200, 130); //top left
ctx.lineTo(215, 123); //top side line
ctx.lineTo(215, 170); //right side line
ctx.lineTo(200, 180); //
ctx.fill();
ctx.stroke();
//chimney top
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(185, 122); //left side
ctx.lineTo(210, 122); //top side
ctx.lineTo(200, 130);
ctx.fill();
ctx.stroke();
}
function smoke1(){
posY += -vy;
posX += vx;
if (posY < -15) posY = 100;
ctx.fillStyle = "aqua";
ctx.fillRect(0,0, 220, 127);
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(200, posY, 15, 0, Math.PI*2, true);
ctx.fill();
}
function smoke2(){
posY2 += -vy;
posX += vx;
if (posY2 < -13) posY2 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(185, posY2, 10, 0, Math.PI*2, true);
ctx.fill();
}
function smoke3(){
posY3 += -vy;
posX += vx;
if (posY3 < -13) posY3 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5s)";
ctx.beginPath();
ctx.arc(210, posY3, 6, 0, Math.PI*2, true);
ctx.fill();
}
function animate() {
smoke1();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate, speed);
}
function animate2() {
smoke2();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate2, speed);
}
function animate3() {
smoke3();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate3, speed);
}
/** if (a == 1) {
ctx.clearRect(0, 0, 260, 105);
smoke();
a++;
} else if (a == 2) {
ctx.clearRect(0, 0, 260, 105);
smokeMed();
a++;
} else if (a == 3) {
ctx.clearRect(0, 0, 260, 105);
smokeBig();
a = 1;
} else {
ctx.clearRect(0, 0, 260, 105);
}
window.setTimeout(animate2, speed);
}
**/
window.onload = function all() {
foundation();
house();
animate();
animate2();
animate3();
}
window.addEventListener("load", all, false);
//window.setInterval(animate2, 1000);
//window.setTimeout(animate2, speed);
css:
#canvas {
background-color: aqua;
border: 1px solid black;
margin-bottom: 10px ;
}
body {
background-color: gray;
}
input[type=range] {
-webkit-appearance: none;
border: 3px solid black;
width: 500px;
border-radius: 20px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 500px;
height: 10px;
background: #ddd;
border: none;
border-radius: 20px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: 3px solid black;
height: 30px;
width: 30px;
border-radius: 50%;
background: red;
margin-top: -8px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
The flickr has to do with having multiple setTimeout functions. Since you delete the previous state of the smoke on smoke1(), once you change the speed there is going to be a discrepancy. If you use only one setTimeout it should work fine in your particular case. Here a JSFIDDLE as an example.
Backbuffers & requestAnimationFrame
All rendering to the DOM is double buffered. Rendering is always done to the backBuffer, it is not drawn to the screen. Presenting to the screen should be done when the scene is fully rendered.
When a function exits (returns to idle) the DOM assumes that you have completed all the rendering and what to display the results. This is when the backbuffer is moved to where the screen can see it.
If you have only rendered part of the scene and exit the DOM does not know you have not yet finished rendering and will present the backbuffer as is. The screen refreshes at a much slower rate than you can write to the backbuffer. When the DOM presents the backbuffer to the screen the display hardware may be at any point of scanning out the pixels onto the display hardware. The result is inconsistent flickering and shearing.
You can fix the problem by using only one function to do all the rendering. The problem still will be that when the function exits it may be at any point of the display scan. You will still get some shearing.
To keep in sync with the display hardware and prevent any flickering and shearing use requestAnimationFrame (there are endless answers about it in SO) to do all your rendering.
The DOM treats all callback functions called by requestAnimationFrame as special and will delay any DOM visual changes being moved from the backbuffer to the screen until the display hardware is in its vertical refresh phase. At that point all changes since the last vertical refresh are moved from back buffers to the screen. (This applies to all visual FX not just the canvas).
Fixing your code
As you want a fixed update speed.
var speed = 100;
var nextUpdateTime;
function updateAll(time){
if(nextUpdateTime=== undefined){
nextUpdateTime= time - speed; // first update now
}
if(time >= nextUpdateTime){
nextUpdateTime= time + (speed - (time - nextUpdateTime)); // get time of next update
smoke1();
smoke2();
smoke3()
}
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
If you want each FX to have its own speed you will have to create a array of speed and nextUpdateTime values. Requested animation frames generally run at 60fps. Time is always the first argument of the requested callback function.
Better yet run at 60fps
I would suggest that rather than use a slow update speed (100ms is 10fps) you modify your code to slow the animation down so that it runs smoothly when sped up to 60fps
The following will modify your animation to play the same but at a higher frame rate.
var speed = 100; // the old frame update delay
var frameRate = 60; // requestAnimationFrame frame rate
// vx and vy where the update delta move vectors for the smoke
// that need to be adjusted for the new frame rate.
vx = (vx * (1000 / speed)) / frameRate;
vy = (vy * (1000 / speed)) / frameRate;
function updateAll(time){
smoke1();
smoke2();
smoke3()
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
NOTE if you present a lot of rendering work the browser may not be able to keep up. If it cant do it in 1/60th of a second and it will not display anything until the next vertical refresh the frame rate will drop from 60fps to 30fps.
If you suspect this will happen then you should monitor the time between frames and use that to calculate the new smoke position every frame.
I have an algorithm for Floodfilling a canvas. Im trying to incorporate this with fabricJS. So here is the dilemna.... I create a fabric.Canvas(). Which creates a wrapper canvas and also an upper-canvas canvas. I click on the canvas to apply my Floodfill(). This works fine and applies my color. But as soon as i go to drag my canvas objects around, or add additional objects to the canvas, the color disappears and looks like it resets of sort.
Any idea why this is?
This happen because fabricjs wipe out all canvas every frame and redraw from its internal data.
I made a JSfiddle that implements Flood Fill for Fabric JS. Check it here: https://jsfiddle.net/av01d/dfvp9j2u/
/*
* FloodFill for fabric.js
* #author Arjan Haverkamp (av01d)
* #date October 2018
*/
var FloodFill = {
// Compare subsection of array1's values to array2's values, with an optional tolerance
withinTolerance: function(array1, offset, array2, tolerance)
{
var length = array2.length,
start = offset + length;
tolerance = tolerance || 0;
// Iterate (in reverse) the items being compared in each array, checking their values are
// within tolerance of each other
while(start-- && length--) {
if(Math.abs(array1[start] - array2[length]) > tolerance) {
return false;
}
}
return true;
},
// The actual flood fill implementation
fill: function(imageData, getPointOffsetFn, point, color, target, tolerance, width, height)
{
var directions = [[1, 0], [0, 1], [0, -1], [-1, 0]],
coords = [],
points = [point],
seen = {},
key,
x,
y,
offset,
i,
x2,
y2,
minX = -1,
maxX = -1,
minY = -1,
maxY = -1;
// Keep going while we have points to walk
while (!!(point = points.pop())) {
x = point.x;
y = point.y;
offset = getPointOffsetFn(x, y);
// Move to next point if this pixel isn't within tolerance of the color being filled
if (!FloodFill.withinTolerance(imageData, offset, target, tolerance)) {
continue;
}
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
if (x < minX || minX == -1) { minX = x; }
if (y < minY || minY == -1) { minY = y; }
// Update the pixel to the fill color and add neighbours onto stack to traverse
// the fill area
i = directions.length;
while (i--) {
// Use the same loop for setting RGBA as for checking the neighbouring pixels
if (i < 4) {
imageData[offset + i] = color[i];
coords[offset+i] = color[i];
}
// Get the new coordinate by adjusting x and y based on current step
x2 = x + directions[i][0];
y2 = y + directions[i][1];
key = x2 + ',' + y2;
// If new coordinate is out of bounds, or we've already added it, then skip to
// trying the next neighbour without adding this one
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || seen[key]) {
continue;
}
// Push neighbour onto points array to be processed, and tag as seen
points.push({ x: x2, y: y2 });
seen[key] = true;
}
}
return {
x: minX,
y: minY,
width: maxX-minX,
height: maxY-minY,
coords: coords
}
}
}; // End FloodFill
var fcanvas; // Fabric Canvas
var fillColor = '#f00';
var fillTolerance = 2;
function hexToRgb(hex, opacity) {
opacity = Math.round(opacity * 255) || 255;
hex = hex.replace('#', '');
var rgb = [], re = new RegExp('(.{' + hex.length/3 + '})', 'g');
hex.match(re).map(function(l) {
rgb.push(parseInt(hex.length % 2 ? l+l : l, 16));
});
return rgb.concat(opacity);
}
function floodFill(enable) {
if (!enable) {
fcanvas.off('mouse:down');
fcanvas.selection = true;
fcanvas.forEachObject(function(object){
object.selectable = true;
});
return;
}
fcanvas.deactivateAll().renderAll(); // Hide object handles!
fcanvas.selection = false;
fcanvas.forEachObject(function(object){
object.selectable = false;
});
fcanvas.on({
'mouse:down': function(e) {
var mouse = fcanvas.getPointer(e.e),
mouseX = Math.round(mouse.x), mouseY = Math.round(mouse.y),
canvas = fcanvas.lowerCanvasEl,
context = canvas.getContext('2d'),
parsedColor = hexToRgb(fillColor),
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
getPointOffset = function(x,y) {
return 4 * (y * imageData.width + x)
},
targetOffset = getPointOffset(mouseX, mouseY),
target = imageData.data.slice(targetOffset, targetOffset + 4);
if (FloodFill.withinTolerance(target, 0, parsedColor, fillTolerance)) {
// Trying to fill something which is (essentially) the fill color
console.log('Ignore... same color')
return;
}
// Perform flood fill
var data = FloodFill.fill(
imageData.data,
getPointOffset,
{ x: mouseX, y: mouseY },
parsedColor,
target,
fillTolerance,
imageData.width,
imageData.height
);
if (0 == data.width || 0 == data.height) {
return;
}
var tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d');
tmpCanvas.width = canvas.width;
tmpCanvas.height = canvas.height;
var palette = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height); // x, y, w, h
palette.data.set(new Uint8ClampedArray(data.coords)); // Assuming values 0..255, RGBA
tmpCtx.putImageData(palette, 0, 0); // Repost the data.
var imgData = tmpCtx.getImageData(data.x, data.y, data.width, data.height); // Get cropped image
tmpCanvas.width = data.width;
tmpCanvas.height = data.height;
tmpCtx.putImageData(imgData,0,0);
fcanvas.add(new fabric.Image(tmpCanvas, {
left: data.x,
top: data.y,
selectable: false
}))
}
});
}
$(function() {
// Init Fabric Canvas:
fcanvas = new fabric.Canvas('c', {
backgroundColor:'#fff',
enableRetinaScaling: false
});
// Add some demo-shapes:
fcanvas.add(new fabric.Circle({
radius: 80,
fill: false,
left: 100,
top: 100,
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Triangle({
width: 120,
height: 160,
left: 50,
top: 50,
stroke: '#000',
fill: '#00f',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 120,
height: 160,
left: 150,
top: 50,
fill: 'red',
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 200,
height: 120,
left: 200,
top: 120,
fill: 'green',
stroke: '#000',
strokeWidth: 2
}));
/* Images work very well too. Make sure they're CORS
enabled though! */
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
fcanvas.add(new fabric.Image(img, {
left: 300,
top: 100,
angle: 30,
}));
}
img.src = 'http://misc.avoid.org/chip.png';
});
How to apply gradient colors on 5 concentric circles using html5 canvas
gradient = context.createRadialGradient(startx, starty,radAvg, xEnd, yEnd,radAvg);
gradient.addColorStop(0, startColor);
gradient.addColorStop(1.0, endColor);
Current results:
JavaScript code:
var colors = ["#B8D430", "#3AB745", "#029990", "#3501CB",
"#2E2C75", "#673A7E", "#CC0071", "#F80120",
"#F35B20", "#FB9A00", "#FFCC00", "#FEF200"];
var restaraunts = ["Wendy's", "McDonalds", "Chick-fil-a", "Five Guys",
"Gold Star", "La Mexicana", "Chipotle", "Tazza Mia",
"Panera", "Just Crepes", "Arby's", "Indian"];
var startAngle = 0;
var arc = Math.PI / 6;
var spinTimeout = null;
var spinArcStart = 10;
var spinTime = 0;
var spinTimeTotal = 0;
var ctx;
function draw() {
drawRouletteWheel();
}
function drawRouletteWheel() {
var canvas = document.getElementById("wheelcanvas");
if (canvas.getContext) {
var outsideRadius = 200;
var textRadius = 160;
var insideRadius = 125;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px sans-serif';
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 0;
ctx.shadowColor = "rgb(220,220,220)";
// ctx.fillStyle = "black";
ctx.translate(250 + Math.cos(angle + arc / 2) * textRadius, 250 + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var text = restaraunts[i];
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}
//Arrow
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(250 - 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius + 5));
ctx.lineTo(250 + 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 + 0, 250 - (outsideRadius - 13));
ctx.lineTo(250 - 9, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius - 5));
ctx.lineTo(250 - 4, 250 - (outsideRadius + 5));
ctx.fill();
}
}
function spin() {
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * 3 + 4 * 1000;
rotateWheel();
}
function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
spinTimeout = setTimeout('rotateWheel()', 30);
}
function stopRotateWheel() {
clearTimeout(spinTimeout);
var degrees = startAngle * 180 / Math.PI + 90;
var arcd = arc * 180 / Math.PI;
var index = Math.floor((360 - degrees % 360) / arcd);
ctx.save();
ctx.font = 'bold 30px sans-serif';
var text = restaraunts[index]
ctx.fillText(text, 250 - ctx.measureText(text).width / 2, 250 + 10);
ctx.restore();
}
function easeOut(t, b, c, d) {
var ts = (t/=d)*t;
var tc = ts*t;
return b+c*(tc + -3*ts + 3*t);
}
draw();
here is my solution
var Mid_diff=(startAngle+endAngle)/2;
var dest_x=arc.center.x+(arc.maxRadius*Math.cos(Mid_diff));
var dest_y=arc.center.y+(arc.maxRadius*Math.sin(Mid_diff));
var gradient = context.createRadialGradient( arc.center.x,arc.center.y,0,dest_x,dest_y,arc.maxRadius);
gradient.addColoStop(0,rgba(0,12,107,0);
gradient.addColoStop(1,rgba(0,12,107,1);
context.strokeStyle = gradient;
worked