I have a page that is basically a large canvas with a lot of small icons connected with lines, and the user needs to be able to pan/zoom around. I've got everything working, but its very choppy. It seems that the repaining is the problem (if I remove the icons it becomes very smooth), but if I run Chrome's profiler, none of my functions are taking up any significant time at all.
Are there any better approaches to panning, without having to repaint everything? For instance in WinAPI, there was a function that scrolled the window content and only invalidated the thin region that just scrolled into view. Is there any way to do something similar in Javascript/canvas, since all I really need is to move the entire window?
I have tried making a giant canvas with everything pre-painted on it, that is then moved around with scrollLeft/scrollTop, but that takes way too much memory (what else should I expect from a 4000x4000 image) and makes zoom very slow instead.
Here's the page if anyone is interested, the code is pretty readable I hope:
http://poe.rivsoft.net/
You will have to just put up with some slower parts. Consider creating dirty regions. These are areas that need to be redrawn when panning. Keep a back buffer the same size as the canvas. When panning copy from the back buffer to its self the area that remains visible and mark the newly visible area as dirty. Then every frame rerender only the dirty areas onto the back buffer. For zooming you can zoom the back buffer and re render when the user pauses or incrementally, this will create a pixelated view (like google maps) when zooming in or aliasing and dirty areas on the sides when zooming out, until you update it.
You can also limit the amount of dirty area redrawn each frame so maintaining a constant frame rate. It will not look as nice but it will improve the panning and zooming. On my machine it runs well (nice job BTW) so you may want to consider implementing optimisations only on machines that can not handle the load.
Also looking at the function DrawNode there is lots of room for optimisation as you have a lot of redundant code (especially once all assets have loaded)
This is just a suggestion as I do not know if nodes are unique or if the x, y coords change, but that can be accommodated as well. You have a lot of searching and checks that should be avoided. The use of strings instead of numbers or booleans to check for status and type is also slow.
function DrawNode(ctx, node, x, y, active) {
// Has this node got quick render information
if (node.qNode) {
// if so render the quick version
var qn = node.qNode; // creating the var qn and then qn.? is quicker than access node.qNode.?
ctx.drawImage(qn.image, qn.coords.x, qn.coords.y, qn.coords.w, qn.coords.h, qn.x, qn.y, qn.size, qn.size);
return;
}
var type = NodeTypes[node.type];
var frameType = "frame" + (active ? "Active" : "Inactive"); // active should be a boolean
if (type && type.size && node.type !== "jewel") { // should be !node.isJewel with isJewwl a boolean
var spriteType = node.type;
if (node.type !== "mastery") // Should be boolean
spriteType += (active ? "Active" : "Inactive");
var sprites = SkillTree.skillSprites[spriteType][3];
var image = GetImage("Assets/" + sprites.filename);
var coords = sprites.coords[node.icon];
if (image && image.loaded && coords) {
ctx.drawImage(image, coords.x, coords.y, coords.w, coords.h,
x - type.size * 0.5, y - type.size * 0.5, type.size, type.size);
// add the information to quickly render the node next time.
// You may want to add sub objects for Mastery Active,inactive
node.qNode = {
image : image,
coords : coords,
x : x - type.size * 0.5,
y : y - type - sise * 0.5,
size : type.size
}
} else if (!image || !image.loaded) {
return false;
}
}
// same deal for the other type.
}
When optimising you start at the slowest point and make that code as efficient as possible, then work your way out. It is well written code but it has no eye for speed so I would say there is lots more room for improvement in the code.
Related
I'm currently tiling many PNG images on several stacked FastLayers with Konva.js. The PNGs contain opacity, and they do not require dragging or hitboxes. The tiles are replaced often, and this seems to work well for medium-sized grids with dimensions of around 30x30. Once the tiles start growing to around 100x100, or even 60x60, the performance begins to slow when replacing individual tiles.
I've started to work on "chunking" tiles, i.e., adding tiles into smaller FastLayer groups. For example, a single 100x100 FastLayer would be divided into several 10x10 FastLayers. When a single tile changes, the idea is that only that chunk should should re-render, ideally speeding up the rendering time overall.
Is this is a good design to attempt, or should I try a different approach? I've looked over the performance tips in the Konva.js documentation, but I haven't seen anything directly relevant to this case.
So, after some research and tinkering, I've discovered the fastest way to render ~4000 images.
Don't use React components for Konva.js. I use React to structure my app, but I've skipped using an intermediate library for Konva.js rendering. Using React Components for the canvas will halve your performance.
Cache common images. I use a simple LRU cache to reuse HTMLImageElement objects.
Reuse Konva.js nodes (Konva.Image) whenever possible. My implementation is rendering a grid of images. The locations do not change, but the images may. Before, I would destroy() a node, and the add another. The destroy() causes an additional render, which creates jank for your users. Instead, I just use the image() method in combination with id() and name() to find and replace images at grid coordinates.
My app allows users to paint long strokes across the grid. This works OK in small strokes, when only using the literal mouse events. For long strokes, this does not work for two reasons. First, the OS and browser throttle the mouse events, giving you intermittent mouse events. Second, being in the middle of a render will give the same side effect. Instead, the software now detects long strokes, and "fills in" the missing coordinates that the user intended to draw between the intermittent mouse events.
Render at intervals. Since my grid can change often, I decided to sample the grid information 24 times a second, rather than allowing each tile change to queue up a batchDraw(). The underlying implementation is using RxJS to poll a Redux store once every 42ms, and only queues a batchDraw() if something has changed.
Caching definitely helps performance, but so does hiding. Konva doesn't (or didn't last I researched this) do any view culling. Below is code I used to hide the island shapes in my Konva strategy game.
stage.on('dragmove', function() {
cullView();
});
function cullView() {
var boundingX = ((-1 * (stage.x() * (1/zoomLevel)))-(window.innerWidth/2))-degreePixels;
var boundingY = ((-1 * (stage.y() * (1/zoomLevel)))-(window.innerHeight/2))-degreePixels;
var boundingWidth = (2 * window.innerWidth * (1/zoomLevel)) + (2*degreePixels);
var boundingHeight = (2 * window.innerHeight * (1/zoomLevel)) + (2*degreePixels);
var x = 0;
var y = 0;
for (var i = 0; i < oceanIslands.length; i++) {
x = oceanIslands[i].getX();
y = oceanIslands[i].getY();
if (((x > boundingX) && (x < (boundingX + boundingWidth))) && ((y > boundingY) && (y < (boundingY + boundingHeight)))) {
if (!oceanIslands[i].visible()) {
oceanIslands[i].show();
oceanIslands[i].clearCache();
if (zoomLevel <= cacheMaxZoom) {
oceanIslands[i].cache();
}
}
} else {
oceanIslands[i].hide();
}
}
I'm still a beginner at javascript, and I'm making a game about dying the whole screen white while the paint brush becomes smaller and smaller until in completely disappears.
I wanted to know, is there a simple way to figure out if the whole canvas has been painted, so I can put a winning screen?
I'm using the processing.js library, here is my code, if it's of any use:
background(255,0,0);
var eight = 100;
var draw = function(){
strokeWeight(eight);
point(mouseX,mouseY);
eight -= 0.2;
if(eight<0){
noStroke();
}
Here's a modestly efficient way of determining if the user has whited every pixel
Create an array where each canvas pixel is represented by an array element.
var pixels=new Array(canvas.width*canvas.height);
Initially fill the array with all zeros.
Create a variable that hold the # of unique pixels whited out so far.
var whited=0;
When the user passes over a pixel, see if the pixel has already been whited. If it hasn't been whited, change its array value to 1 and increment the whited variable.
var n = mouseY * canvas.width + mouseX
if(pixels[n]=0){
pixels[n]=1;
whited++;
}
You have a winner if the value of whited equals the number of pixels on the canvas.
if(whited==pixels.length){
alert('You have won!');
}
A thought: Instead of making the user find every (tiny) missed pixel, you might consider making a grid so the user has an easier time finding that 1 (larger) missed grid cell instead of finding one missed pixel in a sea of white.
You can go over all the pixels and check if they are not white
for (var i=0;i<imgData.data.length;i+=4)
{
if(imgData.data[i]==0&&imgData.data[i+1]==0&&imgData.data[i+2]==0&&imgData.data[i]+3==0){alert("white pixel")}
}
http://www.w3schools.com/tags/canvas_getimagedata.asp
Since you're using Processing, just walk over the pixels:
void setup() {
...
}
void draw() {
...
}
void yourCheckFunction() {
loadPixels();
boolean allWhite = true;
for(int c: pixels) {
if(brightness(c) < 255) {
// we found a not-white pixel!
allWhite = false;
break;
}
}
if (allWhite) {
// the paint surface is entirely white.
} else {
// there are non-white patches left
}
}
There are lots of ways to optimize this (like chopping up the surface into distinct areas with their own administrative true/false value so you can first check if they were all-white on a previous run, and if so, you don't need to recheck them) but this covers the basics:
assume the canvas is all white pixels
try to invalidate that assumption by finding a not-white pixel
immediately stop checking if you do
if there are none, your loop will end "naturally"
Alternatively, you can track how many pixels your user's action have painted. Once that number of pixels is equal to width*height, all pixels must necessarily be white (see markE's answer for that)
I'm looking for a logical understanding with sample implementation ideas on taking a tilemap such as this:
http://thorsummoner.github.io/old-html-tabletop-test/pallete/tilesets/fullmap/scbw_tiles.png
And rendering in a logical way such as this:
http://thorsummoner.github.io/old-html-tabletop-test/
I see all of the tiles are there, but I don't understand how they are placed in a way that forms shapes.
My understanding of rendering tiles so far is simple, and very manual. Loop through map array, where there are numbers (1, 2, 3, whatever), render that specified tile.
var mapArray = [
[0, 0, 0, 0 ,0],
[0, 1, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 1, 1 ,0]
];
function drawMap() {
background = new createjs.Container();
for (var y = 0; y < mapArray.length; y++) {
for (var x = 0; x < mapArray[y].length; x++) {
if (parseInt(mapArray[y][x]) == 0) {
var tile = new createjs.Bitmap('images/tile.png');
}
if (parseInt(mapArray[y][x]) == 1) {
var tile = new createjs.Bitmap('images/tile2.png');
}
tile.x = x * 28;
tile.y = y * 28;
background.addChild(tile);
}
}
stage.addChild(background);
}
Gets me:
But this means I have to manually figure out where each tile goes in the array so that logical shapes are made (rock formations, grass patches, etc)
Clearly, the guy who made the github code above used a different method. Any guidance on understanding the logic (with simply pseudo code) would be very helpful
There isn't any logic there.
If you inspect the page's source, you'll see that the last script tag, in the body, has a huge array of tile coordinates.
There is no magic in that example which demonstrates an "intelligent" system for figuring out how to form shapes.
Now, that said, there are such things... ...but they're not remotely simple.
What is more simple, and more manageable, is a map-editor.
Tile Editors
out of the box:
There are lots of ways of doing this... There are free or cheap programs which will allow you to paint tiles, and will then spit out XML or JSON or CSV or whatever the given program supports/exports.
Tiled ( http://mapeditor.org ) is one such example.
There are others, but Tiled is the first I could think of, is free, and is actually quite decent.
pros:
The immediate upside is that you get an app that lets you load image tiles, and paint them into maps.
These apps might even support adding collision-layers and entity-layers (put an enemy at [2,1], a power-up at [3,5] and a "hurt-player" trigger, over the lava).
cons:
...the downside is that you need to know exactly how these files are formatted, so that you can read them into your game engines.
Now, the outputs of these systems are relatively-standardized... so that you can plug that map data into different game engines (what's the point, otherwise?), and while game-engines don't all use tile files that are exactly the same, most good tile-editors allow for export into several formats (some will let you define your own format).
...so that said, the alternative (or really, the same solution, just hand-crafted), would be to create your own tile-editor.
DIY
You could create it in Canvas, just as easily as creating the engine to paint the tiles.
The key difference is that you have your map of tiles (like the tilemap .png from StarCr... erm... the "found-art" from the example, there).
Instead of looping through an array, finding the coordinates of the tile and painting them at the world-coordinates which match that index, what you would do is choose a tile from the map (like choosing a colour in MS Paint), and then wherever you click (or drag), figure out which array point that relates to, and set that index to be equal to that tile.
pros:
The sky is the limit; you can make whatever you want, make it fit any file-format you want to use, and make it handle any crazy stuff you want to throw at it...
cons:
...this of course, means you have to make it, yourself, and define the file-format you want to use, and write the logic to handle all of those zany ideas...
basic implementation
While I'd normally try to make this tidy, and JS-paradigm friendly, that would result in a LOT of code, here.
So I'll try to denote where it should probably be broken up into separate modules.
// assuming images are already loaded properly
// and have fired onload events, which you've listened for
// so that there are no surprises, when your engine tries to
// paint something that isn't there, yet
// this should all be wrapped in a module that deals with
// loading tile-maps, selecting the tile to "paint" with,
// and generating the data-format for the tile, for you to put into the array
// (or accepting plug-in data-formatters, to do so)
var selected_tile = null,
selected_tile_map = get_tile_map(), // this would be an image with your tiles
tile_width = 64, // in image-pixels, not canvas/screen-pixels
tile_height = 64, // in image-pixels, not canvas/screen-pixels
num_tiles_x = selected_tile_map.width / tile_width,
num_tiles_y = selected_tile_map.height / tile_height,
select_tile_num_from_map = function (map_px_X, map_px_Y) {
// there are *lots* of ways to do this, but keeping it simple
var tile_y = Math.floor(map_px_Y / tile_height), // 4 = floor(280/64)
tile_x = Math.floor(map_px_X / tile_width ),
tile_num = tile_y * num_tiles_x + tile_x;
// 23 = 4 down * 5 per row + 3 over
return tile_num;
};
// won't go into event-handling and coordinate-normalization
selected_tile_map.onclick = function (evt) {
// these are the coordinates of the click,
//as they relate to the actual image at full scale
map_x, map_y;
selected_tile = select_tile_num_from_map(map_x, map_y);
};
Now you have a simple system for figuring out which tile was clicked.
Again, there are lots of ways of building this, and you can make it more OO,
and make a proper "tile" data-structure, that you expect to read and use throughout your engine.
Right now, I'm just returning the zero-based number of the tile, reading left to right, top to bottom.
If there are 5 tiles per row, and someone picks the first tile of the second row, that's tile #5.
Then, for "painting", you just need to listen to a canvas click, figure out what the X and Y were,
figure out where in the world that is, and what array spot that's equal to.
From there, you just dump in the value of selected_tile, and that's about it.
// this might be one long array, like I did with the tile-map and the number of the tile
// or it might be an array of arrays: each inner-array would be a "row",
// and the outer array would keep track of how many rows down you are,
// from the top of the world
var world_map = [],
selected_coordinate = 0,
world_tile_width = 64, // these might be in *canvas* pixels, or "world" pixels
world_tile_height = 64, // this is so you can scale the size of tiles,
// or zoom in and out of the map, etc
world_width = 320,
world_height = 320,
num_world_tiles_x = world_width / world_tile_width,
num_world_tiles_y = world_height / world_tile_height,
get_map_coordinates_from_click = function (world_x, world_y) {
var coord_x = Math.floor(world_px_x / num_world_tiles_x),
coord_y = Math.floor(world_px_y / num_world_tiles_y),
array_coord = coord_y * num_world_tiles_x + coord_x;
return array_coord;
},
set_map_tile = function (index, tile) {
world_map[index] = tile;
};
canvas.onclick = function (evt) {
// convert screen x/y to canvas, and canvas to world
world_px_x, world_px_y;
selected_coordinate = get_map_coordinates_from_click(world_px_x, world_px_y);
set_map_tile(selected_coordinate, selected_tile);
};
As you can see, the procedure for doing one is pretty much the same as the procedure for doing the other (because it is -- given an x and y in one coordinate-set, convert it to another scale/set).
The procedure for drawing the tiles, then, is nearly the exact opposite.
Given the world-index and tile-number, work in reverse to find the world-x/y and tilemap-x/y.
You can see that part in your example code, as well.
This tile-painting is the traditional way of making 2d maps, whether we're talking about StarCraft, Zelda, or Mario Bros.
Not all of them had the luxury of having a "paint with tiles" editor (some were by hand in text-files, or even spreadsheets, to get the spacing right), but if you load up StarCraft or even WarCraft III (which is 3D), and go into their editors, a tile-painter is exactly what you get, and is exactly how Blizzard made those maps.
additions
With the basic premise out of the way, you now have other "maps" which are also required:
you'd need a collision-map to know which of those tiles you could/couldn't walk on, an entity-map, to show where there are doors, or power-ups or minerals, or enemy-spawns, or event-triggers for cutscenes...
Not all of these need to operate in the same coordinate-space as the world map, but it might help.
Also, you might want a more intelligent "world".
The ability to use multiple tile-maps in one level, for instance...
And a drop-down in a tile-editor to swap tile-maps.
...a way to save out both tile-information (not just X/Y, but also other info about a tile), and to save out the finished "map" array, filled with tiles.
Even just copying JSON, and pasting it into its own file...
Procedural Generation
The other way of doing this, the way you suggested earlier ("knowing how to connect rocks, grass, etc") is called Procedural Generation.
This is a LOT harder and a LOT more involved.
Games like Diablo use this, so that you're in a different randomly-generated environment, every time you play. Warframe is an FPS which uses procedural generation to do the same thing.
premise:
Basically, you start with tiles, and instead of just a tile being an image, a tile has to be an object that has an image and a position, but ALSO has a list of things that are likely to be around it.
When you put down a patch of grass, that grass will then have a likelihood of generating more grass beside it.
The grass might say that there's a 10% chance of water, a 20% chance of rocks, a 30% chance of dirt, and a 40% chance of more grass, in any of the four directions around it.
Of course, it's really not that simple (or it could be, if you're wrong).
While that's the idea, the tricky part of procedural generation is actually in making sure everything works without breaking.
constraints
You couldn't, for example have the cliff wall, in that example, appear on the inside of the high-ground. It can only appear where there's high ground above and to the right, and low-ground below and to the left (and the StarCraft editor did this automatically, as you painted). Ramps can only connect tiles that make sense. You can't wall off doors, or wrap the world in a river/lake that prevents you from moving (or worse, prevents you from finishing a level).
pros
Really great for longevity, if you can get all of your pathfinding and constraints to work -- not only for pseudo-randomly generating the terrain and layout, but also enemy-placement, loot-placement, et cetera.
People are still playing Diablo II, nearly 14 years later.
cons
Really difficult to get right, when you're a one-man team (who doesn't happen to be a mathematician/data-scientist in their spare time).
Really bad for guaranteeing that maps are fun/balanced/competitive...
StarCraft could never have used 100% random-generation for fair gameplay.
Procedural-generation can be used as a "seed".
You can hit the "randomize" button, see what you get, and then tweak and fix from there, but there'll be so much fixing for "balance", or so many game-rules written to constrain the propagation, that you'll end up spending more time fixing the generator than just painting a map, yourself.
There are some tutorials out there, and learning genetic-algorithms, pathfinding, et cetera, are all great skills to have... ...buuuut, for purposes of learning to make 2D top-down tile-games, are way-overkill, and rather, are something to look into after you get a game/engine or two under your belt.
I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between two sprites which have x/y positions (corresponding to their centre), a rotation in radians, and of course known width/height.
After spending many weeks of work (yeah, I'm not even exaggerating), I finally came up with a working solution, which unfortunately turned out to be about 10,000x too slow and impossible to optimize in any meaningful manner. I have entirely abandoned the idea of actually drawing and reading pixels from a canvas. That's just not going to cut it, but please don't make me explain in detail why. This needs to be done with math and an "imaginated" 2D world/grid, and from talking to numerous people, the basic idea became obvious. However, the practical implementation is not. Here's what I do and want to do:
What I already have done
In the beginning of the program, each sprite is pixel-looked through in its default upright position and a 1-dimensional array is filled up with data corresponding to the alpha channel of the image: solid pixels get represented by a 1, and transparent ones by 0. See figure 3.
The idea behind that is that those 1s and 0s no longer represent "pixels", but "little math orbs positioned in perfect distances to each other", which can be rotated without "losing" or "adding" data, as happens with pixels if you rotate images in anything but 90 degrees at a time.
I naturally do the quick "bounding box" check first to see if I should bother calculating accurately. This is done. The problem is the fine/"for-sure" check...
What I cannot figure out
Now that I need to figure out whether the sprites collide for sure, I need to construct a math expression of some sort using "linear algebra" (which I do not know) to determine if these "rectangles of data points", positioned and rotated correctly, both have a "1" in an overlapping position.
Although the theory is very simple, the practical code needed to accomplish this is simply beyond my capabilities. I've stared at the code for many hours, asking numerous people (and had massive problems explaining my problem clearly) and really put in an effort. Now I finally want to give up. I would very, very much appreciate getting this done with. I can't even give up and "cheat" by using a library, because nothing I find even comes close to solving this problem from what I can tell. They are all impossible for me to understand, and seem to have entirely different assumptions/requirements in mind. Whatever I'm doing always seems to be some special case. It's annoying.
This is the pseudo code for the relevant part of the program:
function doThisAtTheStartOfTheProgram()
{
makeQuickVectorFromImageAlpha(sprite1);
makeQuickVectorFromImageAlpha(sprite2);
}
function detectCollision(sprite1, sprite2)
{
// This easy, outer check works. Please ignore it as it is unrelated to the problem.
if (bounding_box_match)
{
/*
This part is the entire problem.
I must do a math-based check to see if they really collide.
These are the relevant variables as I have named them:
sprite1.x
sprite1.y
sprite1.rotation // in radians
sprite1.width
sprite1.height
sprite1.diagonal // might not be needed, but is provided
sprite2.x
sprite2.y
sprite2.rotation // in radians
sprite2.width
sprite2.height
sprite2.diagonal // might not be needed, but is provided
sprite1.vectorForCollisionDetection
sprite2.vectorForCollisionDetection
Can you please help me construct the math expression, or the series of math expressions, needed to do this check?
To clarify, using the variables above, I need to check if the two sprites (which can rotate around their centre, have any position and any dimensions) are colliding. A collision happens when at least one "unit" (an imagined sphere) of BOTH sprites are on the same unit in our imaginated 2D world (starting from 0,0 in the top-left).
*/
if (accurate_check_goes_here)
return true;
}
return false;
}
In other words, "accurate_check_goes_here" is what I wonder what it should be. It doesn't need to be a single expression, of course, and I would very much prefer seeing it done in "steps" (with comments!) so that I have a chance of understanding it, but please don't see this as "spoon feeding". I fully admit I suck at math and this is beyond my capabilities. It's just a fact. I want to move on and work on the stuff I can actually solve on my own.
To clarify: the 1D arrays are 1D and not 2D due to performance. As it turns out, speed matters very much in JS World.
Although this is a non-profit project, entirely made for private satisfaction, I just don't have the time and energy to order and sit down with some math book and learn about that from the ground up. I take no pride in lacking the math skills which would help me a lot, but at this point, I need to get this game done or I'll go crazy. This particular problem has prevented me from getting any other work done for far too long.
I hope I have explained the problem well. However, one of the most frustrating feelings is when people send well-meaning replies that unfortunately show that the person helping has not read the question. I'm not pre-insulting you all -- I just wish that won't happen this time! Sorry if my description is poor. I really tried my best to be perfectly clear.
Okay, so I need "reputation" to be able to post the illustrations I spent time to create to illustrate my problem. So instead I link to them:
Illustrations
(censored by Stackoverflow)
(censored by Stackoverflow)
OK. This site won't let me even link to the images. Only one. Then I'll pick the most important one, but it would've helped a lot if I could link to the others...
First you need to understand that detecting such collisions cannot be done with a single/simple equation. Because the shapes of the sprites matter and these are described by an array of Width x Height = Area bits. So the worst-case complexity of the algorithm must be at least O(Area).
Here is how I would do it:
Represent the sprites in two ways:
1) a bitmap indicating where pixels are opaque,
2) a list of the coordinates of the opaque pixels. [Optional, for speedup, in case of hollow sprites.]
Choose the sprite with the shortest pixel list. Find the rigid transform (translation + rotation) that transforms the local coordinates of this sprite into the local coordinates of the other sprite (this is where linear algebra comes into play - the rotation is the difference of the angles, the translation is the vector between upper-left corners - see http://planning.cs.uiuc.edu/node99.html).
Now scan the opaque pixel list, transforming the local coordinates of the pixels to the local coordinates of the other sprite. Check if you fall on an opaque pixel by looking up the bitmap representation.
This takes at worst O(Opaque Area) coordinate transforms + pixel tests, which is optimal.
If you sprites are zoomed-in (big pixels), as a first approximation you can ignore the zooming. If you need more accuracy, you can think of sampling a few points per pixel. Exact computation will involve a square/square collision intersection algorithm (with rotation), more complex and costly. See http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm.
Here is an exact solution that will work regardless the size of the pixels (zoomed or not).
Use both a bitmap representation (1 opacity bit per pixel) and a decomposition into squares or rectangles (rectangles are optional, just an optimization; single pixels are ok).
Process all rectangles of the (source) sprite in turn. By means of rotation/translation, map the rectangles to the coordinate space of the other sprite (target). You will obtain a rotated rectangle overlaid on a grid of pixels.
Now you will perform a filling of this rectangle with a scanline algorithm: first split the rectangle in three (two triangles and one parallelogram), using horizontal lines through the rectangle vertexes. For the three shapes independently, find all horizontal between-pixel lines that cross them (this is simply done by looking at the ranges of Y values). For every such horizontal line, compute the two intersections points. Then find all pixel corners that fall between the two intersections (range of X values). For any pixel having a corner inside the rectangle, lookup the corresponding bit in the (target) sprite bitmap.
No too difficult to program, no complicated data structure. The computational effort is roughly proportional to the number of target pixels covered by every source rectangle.
Although you have already stated that you don't feel rendering to the canvas and checking that data is a viable solution, I'd like to present an idea which may or may not have already occurred to you and which ought to be reasonably efficient.
This solution relies on the fact that rendering any pixel to the canvas with half-opacity twice will result in a pixel of full opacity. The steps follow:
Size the test canvas so that both sprites will fit on it (this will also clear the canvas, so you don't have to create a new element each time you need to test for collision).
Transform the sprite data such that any pixel that has any opacity or color is set to be black at 50% opacity.
Render the sprites at the appropriate distance and relative position to one another.
Loop through the resulting canvas data. If any pixels have an opacity of 100%, then a collision has been detected. Return true.
Else, return false.
Wash, rinse, repeat.
This method should run reasonably fast. Now, for optimization--the bottleneck here will likely be the final opacity check (although rendering the images to the canvas could be slow, as might be clearing/resizing it):
reduce the resolution of the opacity detection in the final step, by changing the increment in your loop through the pixels of the final data.
Loop from middle up and down, rather than from the top to bottom (and return as soon as you find any single collision). This way you have a higher chance of encountering any collisions earlier in the loop, thus reducing its length.
I don't know what your limitations are and why you can't render to canvas, since you have declined to comment on that, but hopefully this method will be of some use to you. If it isn't, perhaps it might come in handy to future users.
Please see if the following idea works for you. Here I create a linear array of points corresponding to pixels set in each of the two sprites. I then rotate/translate these points, to give me two sets of coordinates for individual pixels. Finally, I check the pixels against each other to see if any pair are within a distance of 1 - which is "collision".
You can obviously add some segmentation of your sprite (only test "boundary pixels"), test for bounding boxes, and do other things to speed this up - but it's actually pretty fast (once you take all the console.log() statements out that are just there to confirm things are behaving…). Note that I test for dx - if that is too large, there is no need to compute the entire distance. Also, I don't need the square root for knowing whether the distance is less than 1.
I am not sure whether the use of new array() inside the pixLocs function will cause a problem with memory leaks. Something to look at if you run this function 30 times per second...
<html>
<script type="text/javascript">
var s1 = {
'pix': new Array(0,0,1,1,0,0,1,0,0,1,1,0),
'x': 1,
'y': 2,
'width': 4,
'height': 3,
'rotation': 45};
var s2 = {
'pix': new Array(1,0,1,0,1,0,1,0,1,0,1,0),
'x': 0,
'y': 1,
'width': 4,
'height': 3,
'rotation': 90};
pixLocs(s1);
console.log("now rotating the second sprite...");
pixLocs(s2);
console.log("collision detector says " + collision(s1, s2));
function pixLocs(s) {
var i;
var x, y;
var l1, l2;
var ca, sa;
var pi;
s.locx = new Array();
s.locy = new Array();
pi = Math.acos(0.0) * 2;
var l = new Array();
ca = Math.cos(s.rotation * pi / 180.0);
sa = Math.sin(s.rotation * pi / 180.0);
i = 0;
for(x = 0; x < s.width; ++x) {
for(y = 0; y < s.height; ++y) {
// offset to center of sprite
if(s.pix[i++]==1) {
l1 = x - (s.width - 1) * 0.5;
l2 = y - (s.height - 1) * 0.5;
// rotate:
r1 = ca * l1 - sa * l2;
r2 = sa * l1 + ca * l2;
// add position:
p1 = r1 + s.x;
p2 = r2 + s.y;
console.log("rotated pixel [ " + x + "," + y + " ] is at ( " + p1 + "," + p2 + " ) " );
s.locx.push(p1);
s.locy.push(p2);
}
else console.log("no pixel at [" + x + "," + y + "]");
}
}
}
function collision(s1, s2) {
var i, j;
var dx, dy;
for (i = 0; i < s1.locx.length; i++) {
for (j = 0; j < s2.locx.length; j++) {
dx = Math.abs(s1.locx[i] - s2.locx[j]);
if(dx < 1) {
dy = Math.abs(s1.locy[i] - s2.locy[j]);
if (dx*dx + dy+dy < 1) return 1;
}
}
}
return 0;
}
</script>
</html>
I've been playing around with canvas a lot lately. Now I am trying to build a little UI-library, here is a demo to a simple list (Note: Use your arrow keys, Chrome/Firefox only)
As you can tell, the performance is kinda bad - this is because I delete and redraw every item on every frame:
this.drawItems = function(){
this.clear();
if(this.current_scroll_pos != this.scroll_pos){
setTimeout(function(me) { me.anim(); }, 20, this);
}
for (var i in this.list_items){
var pos = this.current_scroll_pos + i*35;
if(pos > -35 && pos < this.height){
if(i == this.selected){
this.ctx.fillStyle = '#000';
this.ctx.fillText (this.list_items[i].title, 5, pos);
this.ctx.fillStyle = '#999';
} else {
this.ctx.fillText (this.list_items[i].title, 5, pos);
}
}
}
}
I know there must be better ways to do this, like via save() and transform() but I can't wrap my head around the whole idea - I can only save the whole canvas, transform it a bit and restore the whole canvas. The information and real-life examples on this specific topic are also pretty rare, maybe someone here can push me in the right direction.
One thing you could try to speed up drawing is:
Create another canvas element (c2)
Render your text to c2
Draw c2 in the initial canvas with the transform you want, simply using drawImage
drawImage takes a canvas as well as image elements.
Ok, I think I got it. HTML5 canvas uses a technique called "immediate mode" for drawing, this means that the screen is meant to be constantly redrawn. What sounds odd (and slow) first is actually a big advantage for stuff like GPU-acceleration, also it is a pretty common technique found in opengl or sdl. A bit more information here:
http://www.8bitrocket.com/2010/5/15/HTML-5-Canvas-Creating-Gaudy-Text-Animations-Just-Like-Flash-sort-of/
So the redrawing of every label in every frame is totally OK, I guess.