Canvas 2D maze torch effect - javascript

I am working on a school project that includes these conditions:
Make maze with using only JS, HTML5 and CSS.
Make a torch effect around the character. You cannot light through walls.
I started making this game with the use of canvas.
I have succeeded to make a torch effect around the character as shown here:
http://people.inf.elte.hu/tunyooo/web2/HTML5-Maze.html
However, I cannot make it NOT to light through walls.
I am fairly sure I should do something like this:
Start a loop in all directions from the current position of the character up until it reaches the view distance OR if the context.getImageData() returns [0,0,0,255]. This way, I could get the character's distance from northern, eastern, western and southern walls.
Then, I could light the maze around the character with a (viewdistance-DistanceFrom*Wall) rectangle.
Unfortunately though, after 15 hours of thinking about this I am running out of ideas how to make this work.
Any tips are appreciated.

A simpler way of doing this is (ps: I get a "forbidden" error on the link provided so i cannot see what you did):
Have a matte version of the maze, a transparent and white image where white represent allowed drawing areas. This matte image should match the maze image in size and placement.
Create an off-screen canvas the size of the torch image
When you need to draw the torch first draw the matte image into the off-screen canvas. Draw it offset so the correct part of the matte is drawn. For example: if the torch will be drawn at position 100, 100 on the maze then draw the matte into the off-screen canvas at -100,-100 - or simply create the canvas the same size as the maze and draw in the matte at 0,0 and the torch at the relative position. More memory is used but simpler to maintain.
Change composite mode to source-in and then draw the torch. Change composite mode back to copy for the next draw.
Now your torch is clipped to fit within the wall. Now simply draw the off-screen canvas to your main canvas instead of the torch.
Note: it's important that the torch is made such as it cannot reach the other side of the wall (diameter size) or it will instead shine "under" the maze walls - this can be solved other ways though by using matte for different zones which is chosen depending on player position (not shown here).
To move in the demo below just move the mouse over the canvas area.
Live demo
function mousemoved(e) {
var rect = canvas.getBoundingClientRect(), // adjust mouse pos.:
x = e.clientX - rect.left - iTorch.width * 0.5, // center of torch
y = e.clientY - rect.top - iTorch.height * 0.5;
octx.drawImage(iMatte, 0, 0); // draw matte to off-screen
octx.globalCompositeOperation = 'source-in'; // change comp mode
octx.drawImage(iTorch, x, y); // clip torch
octx.globalCompositeOperation = 'copy'; // change comp mode for next
ctx.drawImage(iMaze, 0, 0); // redraw maze
ctx.drawImage(ocanvas, 0, 0); // draw clipped torch on top
}
In the demo the torch is of more or less random size, a bit too big in fact - something I made quick and dirty. But try to move within the maze path to see it being clipped. The off-screen canvas is added on the size of the main canvas to show what goes on.
An added bonus is that you could use the same matte for hit-testing.

Make your maze hallways into clipping paths.
Your torch effects will be contained within the clipping paths.
[ Addition to answer based on questioner's comments ]
To create a clipping path from your existing maze image:
Open up your maze image in a Paint program. The mouse cursors X/Y position are usually displayed as you move over the maze image.
Record the top-left and bottom-right of each maze hallway in an array.
var hallways=[];
hallways.push({left:100, y:50, right: 150, bottom: 65}); // for each hallway
Listen for mouse events and determine which hallway the mouse is in.
// hallwayIndex is the index of the hallway the mouse is inside
var hallwayIndex=-1;
// x=mouse's x coordinate, y=mouse's y coordinate
for(var i=0;i<hallways;i++){
var hall=hallways[i];
if(x>=hall.left &&
x<=hall.right &&
y>=hall.top &&
y<=hall.bottom)
{ hallwayIndex=i; }
}
Redraw the maze on the canvas
Create a clipping path for the current hallway:
var width=hall.right-hall.left;
var height=hall.bottom-hall.top;
ctx.beginPath();
ctx.Rect(hall.left,hall.top,width,height);
ctx.clip();
Draw the player+torch into the hallway (the torch will not glow thru the walls).

There is a brilliant article on this topic: http://www.redblobgames.com/articles/visibility/
Doing it accurately like that, however, is a lot of work. If you want to go with a quick and dirty solution, I would suggest the following. Build the world from large blocks (think retro pixels). This makes collision detection simpler too. Now you can consider all points within the torch radius. Walk in a straight line from the character to the point. If you reach the point without hitting a wall, make it bright.
(You could do the same with 1-pixel blocks too, but you might hit performance issues.)

Related

Unwanted tiling effect in HTML5 canvas when zooming

I'm using html5 canvas to manipulate individual pixels. The canvas is zoomable and draggable, and each pixel is a rectangle, basically I do the following:
pixels.forEach(p => {
context.fillStyle = p.color;
context.fillRect(toScreenX(p.x), toScreenY(p.y), 1*scale, 1*scale);
});
where toScreenX and toScreenY determine the position of the rectangle based on the position of the pixel and the actual scale and offset.
When the zoom scale is 1, everything is okay as shown on picture below:
However, when I start to zoom in, thin white lines start to appear between pixels, and the picture gets distorted as shown in the two figure below:
Here's a short video about the effect: https://imgur.com/a/dvEaZIy
I also tried to use context.putImageData instead of fillRect but for individual pixels, it gets terribly slow.

p5.js canvas drawing path with ellipses

I am working on a real time canvas drawing webapp using socket.io, node.js, and p5.js. I am having trouble creating a smooth line when the mouse is dragged. If the mouse is dragged too fast there is a trail of empty space in between each ellipse. The end goal here is to create a smooth path. Here are the things I have tried so far:
Attempt 1:
noStroke();
fill(lineColor[0],lineColor[1],lineColor[2]);
ellipse(mouseX, mouseY, lineThickness, lineThickness);
Attempt 2:
strokeWeight(lineThickness);
line(mouseX,mouseY);
stroke(lineColor[0],lineColor[1],lineColor[2]);
Here is a picture of what the issue looks like:
canvas drawing incomplete path image
any feedback is welcome; thanks!
Kevin's answer is great because it will be more efficient to draw lines instead of many ellipses. You should also look into:
beginShape()/endShape()
bezierVertex()
curveVertex()
curvePoint()
The above should help you draw a smooth path and setting a thicker stroke will looks as it many filled ellipses are connected forming the path.
If for some reason you do want to draw many ellipses, you can interpolate position when the mouse move faster and create gaps to fill those gaps.
For more information and p5.js example, check out this answer:
Processing: Draw vector instead of pixels
You could use the pmouseX and pmouseY variables, which hold the previous position of the cursor. Use that to draw a line from the previous position to the current position to fill in the blank space between mouse events.
From the reference:
// Move the mouse across the canvas to leave a trail
function setup() {
//slow down the frameRate to make it more visible
frameRate(10);
}
function draw() {
background(244, 248, 252);
line(mouseX, mouseY, pmouseX, pmouseY);
print(pmouseX + " -> " + mouseX);
}
<script src="https://github.com/processing/p5.js/releases/download/0.5.11/p5.js"></script>

How to rotate image - canvas pixel manipulation?

I use getImageData and putImageData to draw on canvas from a buffer canvas. I use these methods because I have a large number of particles and these proved to provide the best performance.
Now I'd like to add rotation of particles but I'm having problems with that.
Here is a jsfiddle which uses transformation matrix for rotation. As you can see in the picture (or fiddle) there are holes in the resulting image which I kinda expected from using this matrix.
nx = ~~ (xx * Math.cos(angle) + yy * Math.sin(angle) + cx);
ny = ~~ (xx * Math.sin(angle) - yy * Math.cos(angle) + cy);
But I don't know how to make this better, especially when I'm looking performance effecient solution?
jsfiddle demo
Image - square after rotation (square is used as a simple body):
Currently my backup is procedurally generated sprite animation which is prepared in advance with standard canvas states: save -> translate -> rotate -> restore.
Thank you very much for any directions you can give me.
The problem is that you are trying to map a single pixel to a single pixel. When you rotate an image, each pixel in the original can influence any of the surrounding pixels in the new image. You are effectively mapping the top left corner of each pixel to it's location in the new image, but you need map the center of each pixel to it's location in the new image and then check the overlap of this rotated pixel with that location, and the 8 surrounding pixels in the new image.
Here you can see the effect. The yellow dots are the centers of the pixel which find the "home" location for the pixel (i.e. where the majority of the influence will be placed). You then need to figure out the percentage of that pixel (the underlying blue/white grid) cell is covered by the original pixel (black box surrounding the yellow dot). Once you figure out the home location influence, you need to repeat that process for the 8 surrounding pixel with respect to current pixel in the original image. In your current code, you are using the top left corner of each pixel to find the home pixel for the new image. You should use the center of the pixel.
Since multiple iterations might affect the same pixel, you'll need to calculate the transformation in a buffer before drawing it to the final image. For pixels in the transformation that are not fully covered by pixels in the original image, figure out the percentage of the pixel that is covered and use that to influence the alpha channel. You'll have to take care when applying the pixels to the final image that you account for the alpha portion and blend with what's already there.

HTML5 canvas - controlling what happens when drawing on same place

How can i control what happens when i draw a line in the same coordinates as previous line?
Currently the color becomes brighter, but i want it to stay the same (or more accurately - i want the second line to on top of the first line).
I tried to do:
_context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
But it seems to work only when drawing canvas on canvas, not when drawing on the same canvas.
Supposing your color has a full alpha, your problem is probably related to the fact you draw lines of width 1 at integer coordinates.
Look at the following fiddle : http://jsfiddle.net/RAgak/
Drawing at integer coordinates makes the line wider and fuzzy. And drawing a second time makes it brighter. But this doesn't happen when I draw the line at half-integer coordinates.
var y = 10;
c.beginPath();
c.moveTo(0, y);
c.lineTo(30, y);
c.stroke(); // fuzzy
c.beginPath();
c.moveTo(50, y+0.5);
c.lineTo(80, y+0.5);
c.stroke(); // ok
This is due to the fact lines are drawn over all pixels they're over (on canvas positionning is in float). When you want to draw precise vertical or horizontal lines in javascript on a canvas, you'd better have them in half ints.
See illustration : The first horizontal line was drawn with a y position of 1. This line is fuzzy and wide. The second horizontal line was drawn with a y position of 4.5. It is thin and precise.
The solution, at least when drawing horizontal or vertical lines (or rects), is to take the width of the line into account and draw at integer or half-integer coordinates.

Why do images lose quality after the context has been rotated?

I'm making a top-down shooter game that relies on the avatar always being rotated pointing to the mouse cursor. I achieve rotation like this:
//Rendering.
context.save(); //Save the context state, we're about to change it a lot.
context.translate(position[0] + picture.width/2, position[1] + picture.height/2); //Translate the context to the center of the image.
context.rotate(phi); //Rotate the context by the object's phi.
context.drawImage(picture.image, -picture.width/2, -picture.height/2); //Draw the image at the appropriate position (center of the image = [0, 0]).
context.restore(); //Get the state back.
When the phi is zero, the image is rendered in its normal quality, with sharp edges and detectable pixels. But, when I set the phi to a nonzero value (actually, when it's not 0, Pi/2, Pi, Pi+Pi/2 or 2Pi), the image looses it's sharpness and the individual pixels can't be seen anymore, because they are blurred out.
Here's a screenshot (sorry about the general bad quality of the screenshot, but I think that the difference is more than noticeable):
This is, well, a bit unacceptable. I can't have the images always blurred out! Why is this happening and can I solve it?
You could try
context.imageSmoothingEnabled = false;
See docs:
context.imageSmoothingEnabled [ = value ]
Returns whether pattern fills and the drawImage() method will attempt to smooth images if they have to rescale them (as opposed to just rendering the images with "big pixels").
Can be set, to change whether images are smoothed (true) or not (false).
If you want a true pixel-art retro style effect, you'd need to manually create rotated sprite images for several angles, look up the appropriate sprite for the current value of phi, and draw it without rotation. This obviously requires a fair amount of art work!
IF you are rotating images around their center point, make sure the image itself has an even number of pixels. Once you end up on odd coordinates the image data needs to be interpolated for the target canvas. Apple has some nice documentation on translating and rotating the canvas.
So for any image, as suggested above use rounding to snap to full pixels.
context.translate(Math.floor(img.width/2), Math.floor(img.height/2));
This way every source pixel of your image will always be drawn exactly into a pixel inside the canvas and blurring does not occur. This however is only true for multiples of 90 degrees.
It seems that all browsers do, to some extend, antialiasing in image drawing so you will probably have to provide rotated images as sprites.
According to this Chromium bug report you might be lucky there if they haven't fixed it yet. Read through and you'll learn that Ian Hickson likely opposed making antialiased image drawing optional.
(picture.width/2, picture.height/2) point won't always work.
(Math.floor(picture.width/2) + 0.5, Math.floor(picture.height/2) + 0.5) should help.
Well, actually it is something you cannot get around
If you rotate an image by a multiple of 90 degrees, your library should smart enough so that no interpolation is applied.
But as soon as you rotate an image by an angle different from a multiple of 90 degrees, you need to interpolate. As a consequence, you get that smoothing. If you are interested in the theory, you may look for a book on computer graphics or image processing.
For the concrete case of image rotation you may have a look at this paper,
http://bigwww.epfl.ch/publications/unser9502.html

Categories

Resources