Algorithm to build a pyramid with squares - javascript

I'm trying to build a pyramid using squares in HTML5 Canvas, I have an algoritm that is half working, the only problem is that after three days and some lack of math abilities I haven't been able to find the proper formula.
Here is what I have, check the code comments so you can see what part of the algorithm we have to change.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var W = 1000; var H = 600;
var side = 16;
canvas.width = W;
canvas.height = H;
function square(x, y) {
ctx.fillStyle = '#66FF00';
ctx.fillRect(x, y, side, side);
ctx.strokeStyle = '#000';
ctx.strokeRect(x, y, side, side);
}
function draw() {
ctx.fillRect(0, 0, W, H);
ctx.save();
for(var i = 0; i < 30; i++) {
for(var j = 0; j < i + 1; j++) {
square(
//Pos X
//This is what we have to change to
//make it look like a pyramid instead of stairs
W / 2 - ((side / 2) + (j * side)),
//Pos Y
side * (i + 1)
);
}
}
ctx.restore();
}
//STARTS DRAWING
draw();
This is the code working in jsfiddle so we can try it:
https://jsfiddle.net/g5spscpu/
The desired result is:
Well, I would love if someone could give me a hand, my brain is burning.

You need to use the i index in the formula for X position with:
W/2 - ((side / 2) + ((j - i/2) * side))
see https://jsfiddle.net/9esscdkc/

Related

Drawing diagonal lines at an angle within a rectangle

I'm trying to fill a rectangle with diagonal lines at 30 degrees that don't get clipped by the canvas. Each line should start and end on the edges of the canvas, but do not go outside the canvas.
I've gotten somewhat of a result but is struggling to understand how I can fix the ends so the lines become evenly distributed:
Here is the code I got so far:
const myCanvas = document.getElementById("myCanvas");
const _ctx = myCanvas.getContext("2d");
const canvasWidth = 600;
const canvasHeight = 300;
// Helper function
const degToRad = (deg) => deg * (Math.PI / 180);
const angleInDeg = 30;
const spaceBetweenLines = 16;
const lineThickness = 16;
_ctx.fillStyle = `black`;
_ctx.fillRect(0, 0, canvasWidth, canvasHeight);
const step = (spaceBetweenLines + lineThickness) / Math.cos(angleInDeg * (Math.PI / 180));
for (let distance = -canvasHeight + step; distance < canvasWidth; distance += step) {
let x = 0;
let y = 0;
if(distance < 0) {
// Handle height
y = canvasHeight - (distance + canvasHeight);
} else {
// Handle height
x = distance;
}
const lineLength = canvasHeight - y;
const slant = lineLength / Math.tan(degToRad((180 - 90 - angleInDeg)));
const x2 = Math.min(x + slant, canvasWidth);
const y2 = y + lineLength;
_ctx.beginPath();
_ctx.moveTo(x, y);
_ctx.lineTo(x2, y2);
_ctx.lineWidth = lineThickness;
_ctx.strokeStyle = 'green';
_ctx.stroke();
}
and a JSFiddle for the code.
What I'm trying to achieve is drawing a pattern where I can control the angle of the lines, and that the lines are not clipped by the canvas. Reference photo (the line-ends don't have flat):
Any help?
My example is in Javascript, but it's more the logic I'm trying to wrap my head around. So I'm open to suggestions/examples in other languages.
Update 1
If the angle of the lines is 45 degree, you will see the gutter becomes correct on the left side. So I'm suspecting there is something I need to do differently on my step calculations.
My current code is based on this answer.
Maybe try something like this for drawing the lines
for(var i = 0; i < 20; i++){
_ctx.fillrect(i * spaceBetweenLines + lineThickness, -100, canvas.height + 100, lineThickness)
}

How to add a zoom feature with mouse to my canvas

I'm working on a webapp and it includes one part where I draw the graph of a function, the coordinate system is made by Canvas. The problem is, I can not zoom into my coordinate system. I want to make it able to zoom in and out + moving the coordinate system using the mouse. The x and y values should also increase/decrease while zooming in/out.
Could somebody help me with this ?
I searched for some solutions, but I couldn't find anything useful. That's why I decided to ask it here.
Here are my codes:
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>
<!--Canva startup-->
<script>
// Setup values
var height = 300;
var width = 300;
var zoomFactor = 15;
// --------
var c = document.getElementById("myCanvas");
var xZero = width / 2;
var yZero = height / 2;
var ctx = c.getContext("2d");
// Draw Cord-System-Grid
ctx.beginPath();
ctx.moveTo(xZero, 0);
ctx.lineTo(xZero, height);
ctx.strokeStyle = "#000000";
ctx.stroke();
ctx.moveTo(0, yZero);
ctx.lineTo(width, yZero);
ctx.strokeStyle = "#000000";
ctx.stroke();
ctx.beginPath();
// Draw Numbers
ctx.font = "10px Georgia";
var heightTextX = yZero + 10;
for(var i = 0; i < width; i = i + width / 10) {
var numberX = (-1 * xZero / zoomFactor) + i / zoomFactor;
ctx.fillText(numberX, i, heightTextX);
}
var heightTextY = yZero + 10;
for(var n = 0; n < height; n = n + height / 10) {
var numberY = (-1 * yZero / zoomFactor) + n / zoomFactor;
if(numberY !== 0)
ctx.fillText(numberY * -1, heightTextY, n);
}
</script>
I asked this question before a week, but couldn't get an answer.
I hope somebody can help me
Attach the onwheel event to a function and use the ctx.scale() function to zoom in and out.
You will probably want to do something like
let canvas = document.getElementById('myCanvas');
ctx.translate(canvas.width/2, canvas.height/2);
ctx.scale(zoomFactor, zoomFactor);
ctx.translate(-canvas.width/2, -canvas.height/2);
To make sure it zooms from the center.
Sorry for any minor errors I'm writing this from my phone.

How to optimize canvas rendering for dynamic loading HTML5 game world?

I've been working on an isometric game engine for my own game. Currently, it's a big, open world with the map data being retrieved dynamically from the Node.js server. 
To understand what I'm doing... for the most part it's a tile based world. So each map has a max number of cols,rows (19) and each world has a max number of maps by col,row (6). So it's a 6x6 world map consisting of 19x19 tiles per map. Whenever the players move onto a new map/region, the client requests a 3x3 matrix of the surrounding maps with the center map being the map the player is currently on. This part is pretty well optimized.
My problem, however, is finding a great way to optimize the drawing onto the canvas. Currently, I don't have a lot of lag doing so, but I also have a fast computer, but I worry that at times it could cause others to lag / mess with the rendering of other graphics.
Basically, how I have it working right now is when the data is sent back from the server, it adds each map and all the tile images for each col/row it has to render into a buffer. Each loop of the game loop, it will basically render a small section of the 25 tiles onto the specific map's hidden canvas. When all of the requested maps are done rendering (after a few game loops), the camera will go ahead and merge these hidden maps into 1 big map canvas of the 3x3 matrix (by slicing parts from the hidden canvases and merging them onto the new canvas).
Ideally I would love this whole process to be async. but I've been looking into web workers and apparently they do not support canvas well. Has anyone come up with a process to do something similar and keep it well optimized?
Thanks!
Here's an example of rendering a 19x19 grid in each frame. A new random tile is added from right to left top to bottom in each frame. The grid is rendered in the same order and you can see that this works for overlapping tiles.
I think it's best to save each tile and make a function that renders the entire grid. So if the player gets updates in the 3x3 surrounding area then download and keep those tiles and re-render the entire grid.
update
I provided a function to eliminate overdraw and a toggle. This may increase performance for some people. It draws from bottom to top left to right. This draws the overlaying items first and with globalCompositeOperation "distination-over" tells the canvas to leave existing pixels alone when adding new content. This should mean less work to do in putting pixels on the canvas as it's not drawing over unused pixels.
var cols = 19;
var tile_width = 32;
var rows = 19;
var tile_height = 16;
var y_offset = 64;
var h_tw = tile_width / 2;
var h_th = tile_height / 2;
var frames = 0;
var fps = "- fps";
setInterval(function(){
fps = frames + " fps";
frames = 0;
}, 1000);
var can = document.getElementById('tile');
var ctx = can.getContext('2d');
var wcan = document.getElementById('world');
var wctx = wcan.getContext('2d');
wcan.width = cols * tile_width;
wcan.height = rows * tile_height + y_offset;
var tiles = initTiles();
document.getElementById('toggle').addEventListener('click', function() {
if (this.innerHTML == 'renderWorld') {
renderFn = renderWorldNoOverdraw;
this.innerHTML = "renderWorldNoOverdraw";
} else {
renderFn = renderWorld;
this.innerHTML = "renderWorld";
}
});
//renderWorld();
var ani_x = cols;
var ani_y = 0;
var renderFn = renderWorld;
ani();
function initTiles () {
var tiles = [];
for (var y = 0; y < rows; y++) {
var row = [];
for (var x = 0; x < cols; x++) {
var can = document.createElement('canvas');
can.width=tile_width;
can.height=tile_height+y_offset;
row[x]=can;
}
tiles[y] = row;
}
return tiles;
}
function ani() {
var can = tiles[ani_y][--ani_x]
if (ani_x == 0) ani_x = cols, ani_y++;
ani_y %= rows;
var ctx = can.getContext('2d');
randTile(can, ctx);
renderFn();
requestAnimationFrame(ani);
}
// renders from bottom left to right and skips
// drawing over pixels already present.
function renderWorldNoOverdraw() {
frames++;
wctx.clearRect(0,0,wcan.width,wcan.height);
wctx.save();
wctx.globalCompositeOperation = "destination-over";
wctx.translate(0, y_offset);
var x_off = 0;
var y_off = 0;
var y_off2 = 0;
for (var y = rows; y--;) {
x_off = (cols * h_tw)- ((rows-y) * h_tw);
y_off = y * h_th + tile_height;
y_off2 = y_off;
for (var x = 0; x < cols; x++) {
var can = tiles[y][x];
wctx.drawImage(can, x_off, y_off2 + y_offset);
y_off2 -= h_th;
x_off += h_tw;
}
}
wctx.translate(0,-y_offset);
wctx.fillStyle = "#ddaadd";
wctx.fillRect(0,0,wcan.width, wcan.height);
wctx.restore();
wctx.fillStyle= "black";
wctx.fillText(fps, 10, 10);
}
function renderWorld() {
frames++;
wctx.fillStyle = "#CCEEFF";
wctx.fillRect(0, 0, wcan.width, wcan.height);
wctx.save();
wctx.translate(0, y_offset);
var x_off = 0;
var y_off = 0;
var y_off2 = 0;
for (var y = 0; y < rows; y++) {
x_off = (cols * h_tw) + (y * h_tw) - h_tw;
y_off = y * h_th;
y_off2 = y_off;
for (var x = cols; x--;) {
var can = tiles[y][x];
wctx.drawImage(can, x_off, y_off2 - 64);
y_off2 += h_th;
x_off -= h_tw;
}
y_off += h_th;
x_off -= h_tw;
}
wctx.restore();
wctx.fillStyle= "black";
wctx.fillText(fps, 10, 10);
}
function randTile(can, ctx) {
var maxH = can.height - 24;
var ranH = Math.floor(Math.random() * maxH);
var h = Math.max(ranH, 1);
ctx.clearRect(0, 0, can.width, can.height);
ctx.beginPath();
ctx.save();
ctx.translate(0, can.height - 16);
ctx.moveTo(0, 8);
ctx.lineTo(16, 0);
ctx.lineTo(32, 8);
ctx.lineTo(16, 16);
ctx.lineTo(0, 8);
ctx.strokeStyle = "#333333";
ctx.stroke();
// random floor color
var colors = ["#dd9933", "#22aa00", "#66cccc", "#996600"];
ctx.fillStyle = colors[Math.floor(Math.random() * 4)];
ctx.fill();
// random building
if (Math.floor(Math.random() * 8) == 0) {
ctx.beginPath();
ctx.moveTo(8, 8);
ctx.lineTo(8, -h - 4);
ctx.lineTo(16, -h);
ctx.lineTo(16, 12);
ctx.lineTo(8, 8);
ctx.stroke();
ctx.fillStyle = "#333333";
ctx.fill();
ctx.beginPath();
ctx.moveTo(16, 12);
ctx.lineTo(16, -h);
ctx.lineTo(24, -h - 4);
ctx.lineTo(24, 8);
ctx.lineTo(16, 12);
ctx.stroke();
ctx.fillStyle = "#999999";
ctx.fill()
ctx.beginPath();
ctx.moveTo(16, -h);
ctx.lineTo(24, -h - 4);
ctx.lineTo(16, -h - 8);
ctx.lineTo(8, -h - 4);
ctx.moveTo(16, -h);
ctx.stroke();
ctx.fillStyle = "#CCCCCC";
ctx.fill()
}
ctx.restore();
}
body {
background-color: #444444;
}
<button id="toggle">renderWorld</button><br/>
<canvas id='tile' width="32" height="32" style="display:none"></canvas>
<canvas id="world" width="608" height="368">
</canvas>

JavaScript Loop to create grid on canvas

What I'm trying to do is fill myCanvas with 32x32 blocks of random colors. I'm using a loop to create the blocks and assign them colors. I've figured out how to get the line to descend on the Y axis as the loop goes on but I can't wrap my head around getting the X axis right.
If you make the canvas larger you will see that lines of blocks 20 blocks long go on to the right.
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
for (i = 0; i < 400; i++) {
var X = i * 32;
var Y = Math.floor(i / 20) * 32;
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(X, Y, 32, 32);
console.log('X:' + X);
console.log('Y:' + Y);
}
I've tried using modulus like this:
if(i % 20 == 0){
X = 0;
}
But it only fixes it when I get multiples of 20 so only the left side of the canvas will be filled with blocks. My problem is wrapping my head around the math involved in getting this done. Sorry I'm pretty tired and new at this :(
Fiddle: http://jsfiddle.net/orwfo7re/
you were already pretty close with your modulus suggestion! However, your if statement only gets executed when i % 20 == 0, something that for instance is not true for i=21
The solution is to just use var x = (i % 20) * 32 for var x in your algoritm. So:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
for (i = 0; i < 400; i++) {
var X = (i % 20) * 32;
var Y = Math.floor(i / 20) * 32;
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(X, Y, 32, 32);
}
fiddle: https://jsfiddle.net/MartyB/ur9vug0x/2/
Do you mean to do something like this:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var width = 640;
var height = 640;
for (y=0; y< height; y=y+32) {
for (x=0; x < width; x=x+32) {
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(x, y, 32, 32);
}
}

HTML5 Canvas JavaScript Crosshair Grid

I wanted to make a crosshair grid (every 10px).
I had many problems with it. Can it be done in easier way than 3x For loop?
http://jsfiddle.net/TnnRp/1/
var canvas = document.getElementById('grid');
var context = canvas.getContext('2d');
// grid
var width = canvas.width;
var height = canvas.height;
var p = 10;
var h = 10;
for (var i = 10; i <= width - 5; i += 10) {
for (var e = 10; e <= height - 5; e += 10) {
context.moveTo(h + 0.5, e - 1);
context.lineTo(h + 0.5, e + 2);
}
h += 10;
for (var f = 10; f <= width - 5; f += 10) {
context.moveTo(f - 1, p + 0.5);
context.lineTo(f + 2, p + 0.5);
}
p += 10;
}
context.stroke();
You can always reduce it to two loops and there are two ways with that as well. But before: I agree with markE - your code is just fine as it is.
My version here is to reduce loops and show one way to optimize its speed:
//pre-translate to force anti-alias
context.translate(0.5, 0.5);
Now we draw just one single cross-hair:
var cc = 1; //cross-hair size
context.moveTo(p / 2, h / 2 - cc);
context.lineTo(p / 2, h / 2 + cc);
context.moveTo(p / 2 - cc, h / 2);
context.lineTo(p / 2 + cc, h / 2);
context.stroke();
And now we "blit" our hearts out, first horizontally:
//replicate drawn cross-hair = faast.
for (i = 0; i < width - p; i += p) {
if (i > 0) p *= 2;
context.drawImage(canvas, 0, 0, p, h, p, 0, p,h);
}
And now we replicate that line vertically:
for(i = 0; i < height; i+=h) {
if (i > 0) h *= 2;
context.drawImage(canvas, 0, 0, width, h, 0, h, width, h);
}
Notice that we are not just copying one line, but when we have draw one replicate, we duplicate those two, then we skip four and copy the four etc.
This method is super-fast and is the way the browser (or rather the system function the browser uses) also replicate patterns (but with internal compiled code). You could also have used the first cross-hair to set a pattern on an off-screen canvas and filled the canvas with that which could be a notch faster.
Updated fiddle
With Ken's help.
Working jsFiddle
var canvas = document.getElementById('grid');
var context = canvas.getContext('2d');
var width = canvas.width,
height = canvas.height;
context.moveTo(10.5, 10 - 1);
context.lineTo(10.5, 10 + 2);
context.moveTo(10.5 -1, 10.5);
context.lineTo(10.5 +2, 10.5);
context.stroke();
var h=10,
p=10;
for (i = 0; i < width; i += p) {
p *= 2;
context.drawImage(canvas, p, 0);
}
for(i = 0; i < height; i+=h) {
h *= 2;
context.drawImage(canvas, 0, h);
}

Categories

Resources