Related
I am building a web application that draws a set of letters in different fonts on an HTML 5 Canvas using fillText. The user will click somewhere on that canvas and I need to check which letter they clicked on (or if they clicked on a letter at all).
I think I will need to:
Get the vector path for each letter (I have no clue how to do this).
Check if the click point is inside the letter path using some simple collision-detection algorithm.
Is there some easy function to do this that I am missing? Or maybe a library for things like this? If there aren't any libraries, how do I get the path for the letter in a specific font to do the checking myself?
I need to use the actual shape of the letter and not just its bounding box as I don't want the user to be able to click in the middle of an O and it register as a hit.
Any hints in this direction would be appreciated.
Logic
You can't handle separate letters on canvas without providing custom logic for it. Everything drawn to the canvas is merged to a soup of pixels.
And unfortunately you can't add text as pure path so you will have to check pixel values. Otherwise you could simply add the text to a new path and use the isPointInPath method for each letter.
One approach
We can't provide full solutions here on SO but here is a basis you can hopefully built on top of to provide basic logic to click single letters on a canvas:
Each letter is stored as object incl. its position, size, font and char, but also with a rectangle hit region (see below)
Define an array with these objects and then pass them to a render function
When you register a click iterate through the array and test against the rectangular hit-region and if inside check the pixel (*)
*) To distinguish between overlapping letters you need to check by priority. You can also render this char onto a separate canvas so you get pixels of only this char. I am not showing this in the demo but you'll get the idea.
Demo
var ltrs = []; /// stores the letter objects
/// Create some random objects
for(;i < 20; i++) {
/// build the object
var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
x: ((w - 20) * Math.random())|0,
y: ((h - 20) * Math.random())|0,
size: (50 * Math.random() + 16)|0,
font: fonts[((fonts.length - 1) * Math.random())|0]};
/// store other things such as color etc.
/// store it in array
ltrs.push(o);
}
Then we have some function to render these characters (see demo).
When we then handle clicks we iterate through the object array and check by boundary first to check what letter we are at (picking just a pixel here won't enable us to ID the letter):
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, o;
/// iterate
for(;o = ltrs[i]; i++) {
/// is in rectangle? "Older" letters has higher priority here...
if (x > o.x && x < (o.x + o.rect[2]) &&
y > o.y && y < (o.y + o.rect[3])) {
/// it is, check if we actually clicked a letter
/// This is what you would adopt to be on a separate canvas...
if (checkPixel(x, y) === true) {
setLetterObject(o, '#f00')
return;
}
}
}
}
The pixel check is straight forward, it picks a single pixel at x/y position and checks its alpha value (or color if you use solid backgrounds):
function checkPixel(x, y) {
var data = ctx.getImageData(x, y, 1, 1).data;
return (data[3] !== 0);
}
CLICK HERE FOR ONLINE DEMO
Updated check pixel function:
This updated check is capable of checking letters even if they are overlapping in the same region.
We create a separate canvas to draw the letter in. This isolates the letter and when we pick a pixel we can only get a pixel from that specific letter. It also doesn't matter what background color is as we our off-screen canvas only set pixels for the letter and not background during the check. The overhead is minimal.
function checkPixel(o, x, y) {
/// create off-screen canvas
var oc = document.createElement('canvas'),
octx = oc.getContext('2d'),
data,
oldX = o.x,
oldY = o.y;
/// default canvas is 300x150, adjust if letter size is larger *)
//oc.width = oc.height = 200;
/// this can be refactored to something better but for demo...
o.x = 0;
o.y = 0;
setLetterObject(octx, o, '#000');
o.x = oldX;
o.y = oldY;
data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
return (data[3] !== 0);
}
*) When we create a canvas the default size is 300x150. To avoid re-allocating a new bitmap we just leave it as it is as the memory is already allocated for it and we only need to pick a single pixel from it. If letters has larger pixel size than the default size we will of course need to re-allocate to make the letter fit.
In this demo we temporary override the x and y position. For production you should enable the setLetterObject method to somehow override this as that would be more elegant. But I'll leave it as-is here in the demo as the most important thing is to understand the principle.
I'd say that the best option is to actually use pixels that by the way are the most accurate thing you can do (remember that the user is seeing pixels when clicking, nothing more).
Since you cannot just use the color directly (because there can be many text objects with the same color (and may be also other primitives with the same color) you could use instead a separate "pick" canvas for that.
Basically when you draw your objects on the main canvas on the repaint function you also draw them in another hidden canvas with the exact same size, but you draw them using a unique color for each entity.
You can therefore have up to 16 millions entity (24-bit) on the canvas and know instantly which one was clicked by keeping a map between color code and the entity itself. By the way this sort of map is often used in CAD applications exactly to speed up picking.
The only somewhat annoying part is that there's no portable way to disable antialiasing when drawing on a canvas so it's possible that the color you'll get back from the pick canvas is not one of the colors you used or, even worse, it's possible that by clicking on the border of an entity a different unrelated entity is considered selected.
This should be a very rare event unless your display is really really crowded and picking is basically random anyway.
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>
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
I'm creating a single player web RPG in JavaScript.
It will not be too heavy of an events-based game, but I will be loading lots of objects to the screen.
I have two questions:
1st: I've used createJS to load my bitmaps, but noticed severe lag when I loaded several objects to the screen at once and animated them. I'm wondering if there is a better library than createJS to load objects.
2nd: I was planning on using PHP for user profiles, stats, etc... but found writing AJAX calls to the server required several steps...
first, send javascript variable to php using jquery .get()
then update database
then send newly updated variable back by using json_encode, and echoing out the variable.
Finally, store that updated variable back in a javascript variable
I'm wondering how something like this is handled in NodeJS. I read How to decide when to use Node.js? to figure out the usage of NodeJS, and this sentence in particular, "I believe Node.js is best suited for real-time applications: online games, collaboration tools, chat rooms, or anything where what one user does with the application needs to be seen by other users immediately, without a page refresh." spoke to me... but I'm still not sure if I'll need to use NodeJS instead of PHP.
Thank you
EDIT:
Hi, I think I'm doing the tiling and caching incorrectly...
In my Game.php, I call several function to create/draw the objects.
Game.php:
function init() {
canvas = document.getElementById("demoCanvas");
start();
}
function start() {
stage = new createjs.Stage("demoCanvas");
stage.mouseMoveOutside = true;
//Create Objects
createWorld();
createTiles();
createPlayers();
In my function tick() I have a centerViewTo which centers the player in the viewport relative to the background (mimicking a camera).
The issue with this is, I pass in a single bitmap bgBMP that I was drawing the player relative to. If I do tiling, I'm not sure how I can draw the player relative to the background image.
centerViewTo(stage.canvas, stage, segment, {x:0, y:0, width:bgBMP.image.width, height:bgBMP.image.height});
Moreover, http://www.createjs.com/Docs/EaselJS/classes/DisplayObject.html#method_cache says I should cache each object before adding it to the container? Anyway, I get why you add all objects to the stage and then cache the stage all at once.
So I've tried that here... but it's not rendering anything:
CreateObjects.js:
var world;
var bgBMP;
var tile;
var mapWidth = 10; //Map size is 1000... so 10x10 tiles
var mapHeight = 10;
//World will be a container to hold all objects
function createWorld() {
world = new createjs.Container();
bgBMP = new createjs.Bitmap("images/bg2.png");
world.cache(0, 0, mapWidth , mapHeight );
world.addChild(bgBMP);
stage.addChild(world);
}
function createTiles() {
for (var x = 0; x < mapWidth; x++){
for (var y = 0; y < mapHeight; y++){
var tile = new createjs.Bitmap('images/myTile.png');
tile.x = x * 32;
tile.y = y * 32;
world.cache(0, 0, mapWidth , mapHeight );
world.addChild(tile);
}
}
}
I'm currently creating a RPG too with createjs, and I can display 200 characters, with their own animations and life, without lag issues.
Createjs is a really good framework, but you have to understand how it works to avoid performance issues. It's very important to separe layers in differents containers, and to cache them. For example, if the background of your game is composed with a lot of 32x32 tiles (like in RPG maker), you should cache it since it's not supposed to change during the game.
For example :
var backgroundContainer = new createjs.Container();
// mapWidth and mapHeight is the number of X and Y tiles
for (var x = 0; x < mapWidth; x++){
for (var y = 0; y < mapHeight; y++){
var tile = new createjs.Bitmap('mytile-'+i+'.png');
tile.x = x * 32;
tile.y = y * 32;
backgroundContainer.addChild(tile);
}
}
backgroundContainer.cache(0, 0, mapWidth, mapHeight);
stage.addChild(backgroundContainer);
When you cache a container, it will draw all the children in a hidden canvas, and then just draw the cached canvas on your current canvas on stage update.
If you don't cache it, just think that every time you call stage.update(), all your children will be drawn one per one on your canvas.
About NodeJS : nodeJS is really cool with the Socket.IO plugin, which use the full power of web sockets. Forget ajax if you need real time for a game : websocket is what you need. Try socket.io http://socket.io/ you will love it ;)
EDIT:
You don't use the cache well, I think you need to understand first what exactly does the cache. In my previous example, you can see we add all tiles in a container. So we have mapWidth * mapHeight tiles. Every time you call stage.update(), it will loop each tile bitmap and draw it on canvas. So if you have a 50x20 map, it will draw 1000 bitmaps every stage.update(), that's not good for performances.
If we cache backgroundContainer all tiles will be draw on an internal canvas, and when stage.update() is called it will only draw 1 image this time instead of 1000. You have to cache your world container after you add all the tiles, as in my example.
The power of createjs is that you can encapsulate your display objects as you want, this is how I organize my containers in my game :
stage
screen
scene
background -> Here I display a background image (E.g: the sky)
body
tiles -> All my background tiles (I cache this)
events -> Map events (moving NPC, my character, etc.)
overlay -> Here I display an overlay image (E.g: a fog image with 0.2 opacity)
transition -> Here I make transition when my character go to a new map
When my character move on the map, I just need to move the body container (changing X and Y properties)
I am building a web application that draws a set of letters in different fonts on an HTML 5 Canvas using fillText. The user will click somewhere on that canvas and I need to check which letter they clicked on (or if they clicked on a letter at all).
I think I will need to:
Get the vector path for each letter (I have no clue how to do this).
Check if the click point is inside the letter path using some simple collision-detection algorithm.
Is there some easy function to do this that I am missing? Or maybe a library for things like this? If there aren't any libraries, how do I get the path for the letter in a specific font to do the checking myself?
I need to use the actual shape of the letter and not just its bounding box as I don't want the user to be able to click in the middle of an O and it register as a hit.
Any hints in this direction would be appreciated.
Logic
You can't handle separate letters on canvas without providing custom logic for it. Everything drawn to the canvas is merged to a soup of pixels.
And unfortunately you can't add text as pure path so you will have to check pixel values. Otherwise you could simply add the text to a new path and use the isPointInPath method for each letter.
One approach
We can't provide full solutions here on SO but here is a basis you can hopefully built on top of to provide basic logic to click single letters on a canvas:
Each letter is stored as object incl. its position, size, font and char, but also with a rectangle hit region (see below)
Define an array with these objects and then pass them to a render function
When you register a click iterate through the array and test against the rectangular hit-region and if inside check the pixel (*)
*) To distinguish between overlapping letters you need to check by priority. You can also render this char onto a separate canvas so you get pixels of only this char. I am not showing this in the demo but you'll get the idea.
Demo
var ltrs = []; /// stores the letter objects
/// Create some random objects
for(;i < 20; i++) {
/// build the object
var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
x: ((w - 20) * Math.random())|0,
y: ((h - 20) * Math.random())|0,
size: (50 * Math.random() + 16)|0,
font: fonts[((fonts.length - 1) * Math.random())|0]};
/// store other things such as color etc.
/// store it in array
ltrs.push(o);
}
Then we have some function to render these characters (see demo).
When we then handle clicks we iterate through the object array and check by boundary first to check what letter we are at (picking just a pixel here won't enable us to ID the letter):
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, o;
/// iterate
for(;o = ltrs[i]; i++) {
/// is in rectangle? "Older" letters has higher priority here...
if (x > o.x && x < (o.x + o.rect[2]) &&
y > o.y && y < (o.y + o.rect[3])) {
/// it is, check if we actually clicked a letter
/// This is what you would adopt to be on a separate canvas...
if (checkPixel(x, y) === true) {
setLetterObject(o, '#f00')
return;
}
}
}
}
The pixel check is straight forward, it picks a single pixel at x/y position and checks its alpha value (or color if you use solid backgrounds):
function checkPixel(x, y) {
var data = ctx.getImageData(x, y, 1, 1).data;
return (data[3] !== 0);
}
CLICK HERE FOR ONLINE DEMO
Updated check pixel function:
This updated check is capable of checking letters even if they are overlapping in the same region.
We create a separate canvas to draw the letter in. This isolates the letter and when we pick a pixel we can only get a pixel from that specific letter. It also doesn't matter what background color is as we our off-screen canvas only set pixels for the letter and not background during the check. The overhead is minimal.
function checkPixel(o, x, y) {
/// create off-screen canvas
var oc = document.createElement('canvas'),
octx = oc.getContext('2d'),
data,
oldX = o.x,
oldY = o.y;
/// default canvas is 300x150, adjust if letter size is larger *)
//oc.width = oc.height = 200;
/// this can be refactored to something better but for demo...
o.x = 0;
o.y = 0;
setLetterObject(octx, o, '#000');
o.x = oldX;
o.y = oldY;
data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
return (data[3] !== 0);
}
*) When we create a canvas the default size is 300x150. To avoid re-allocating a new bitmap we just leave it as it is as the memory is already allocated for it and we only need to pick a single pixel from it. If letters has larger pixel size than the default size we will of course need to re-allocate to make the letter fit.
In this demo we temporary override the x and y position. For production you should enable the setLetterObject method to somehow override this as that would be more elegant. But I'll leave it as-is here in the demo as the most important thing is to understand the principle.
I'd say that the best option is to actually use pixels that by the way are the most accurate thing you can do (remember that the user is seeing pixels when clicking, nothing more).
Since you cannot just use the color directly (because there can be many text objects with the same color (and may be also other primitives with the same color) you could use instead a separate "pick" canvas for that.
Basically when you draw your objects on the main canvas on the repaint function you also draw them in another hidden canvas with the exact same size, but you draw them using a unique color for each entity.
You can therefore have up to 16 millions entity (24-bit) on the canvas and know instantly which one was clicked by keeping a map between color code and the entity itself. By the way this sort of map is often used in CAD applications exactly to speed up picking.
The only somewhat annoying part is that there's no portable way to disable antialiasing when drawing on a canvas so it's possible that the color you'll get back from the pick canvas is not one of the colors you used or, even worse, it's possible that by clicking on the border of an entity a different unrelated entity is considered selected.
This should be a very rare event unless your display is really really crowded and picking is basically random anyway.
The question title may be vague. Basically, imagine a racing game built in canvas. The track takes up 10,000 x 10,000 pixels of screen space. However the browser window is 500 x 500 pixels. The car should stay centered in the browser and the 'viewable' area of the 10,000 x 10,000 canvas will change. Otherwise the car would just drive off the edge at disappear.
Does this technique have a name?
What are the basic principles to make this happen?
If the car should stay at the same position (relative to the canvas' position), then you should not move the car. Instead, move the background picture/track/map to the other side.
Causing your eyes to think the car moves right can be done by either moving the car to the right, or by moving the map to the left. The second option seems to be what you want, since the car won't move whereas the viewable area (i.e. the map) will.
This is a quick demo from scratch: http://jsfiddle.net/vXsqM/.
It comes down to altering the map's position the other way round:
$("body").on("keydown", function(e) {
if(e.which === 37) pos.x += speed; // left key, so move map to the right
if(e.which === 38) pos.y += speed;
if(e.which === 39) pos.x -= speed;
if(e.which === 40) pos.y -= speed;
// make sure you can't move the map too far.
// clamp does: if x < -250 return -250
// if x > 0 return 0
// else it's allowed, so just return x
pos.x = clamp(pos.x, -250, 0);
pos.y = clamp(pos.y, -250, 0);
draw();
});
You can then draw the map with the position saved:
ctx.drawImage(img, pos.x, pos.y);
If you're looking for a way to actually move the car when the map cannot be moved any further (because you're driving the car close to a side of the map), then you'd have to extend the clamping and also keep track of when the car should be moved and how far: http://jsfiddle.net/vXsqM/1/.
// for x coordinate:
function clamp2(x, y, a, b) { // x = car x, y = map x, a = min map x, b = max map x
return y > b ? -y : y < a ? a - y : x;
}
The position clamping then becomes a little more complex:
// calculate how much car should be moved
posCar.x = clamp2(posCar.x, posMap.x, -250, 0);
posCar.y = clamp2(posCar.y, posMap.y, -250, 0);
// also don't allow the car to be moved off the map
posCar.x = clamp(posCar.x, -100, 100);
posCar.y = clamp(posCar.y, -100, 100);
// calculate where the map should be drawn
posMapReal.x = clamp(posMap.x, -250, 0);
posMapReal.y = clamp(posMap.y, -250, 0);
// keep track of where the map virtually is, to calculate car position
posMap.x = clamp(posMap.x, -250 - 100, 0 + 100);
posMap.y = clamp(posMap.y, -250 - 100, 0 + 100);
// the 100 is because the car (circle in demo) has a radius of 25 and can
// be moved max 100 pixels to the left and right (it then hits the side)
Two things:
Canvas transformation methods
First, the canvas transformation methods (along with context.save() and context.restore() are your friends and will greatly simplify the math needed to view a portion of a large 'world`. If you use this approach, you can get the desired behavior just by specifying the portion of the world that is visible and the world-coordinates of everything you want to draw.
This is not the only way of doing things* but the transformation methods are meant for exactly this kind of problem. You can use them or you can reinvent them by manually keeping track of where your background should be drawn, etc., etc.
Here's an example of how to use them, adapted from a project of mine:
function(outer, inner, ctx, drawFunction) {
//Save state so we can return to a clean transform matrix.
ctx.save();
//Clip so that we cannot draw outside of rectangle defined by `outer`
ctx.beginPath();
ctx.moveTo(outer.left, outer.top);
ctx.lineTo(outer.right, outer.top);
ctx.lineTo(outer.right, outer.bottom);
ctx.lineTo(outer.left, outer.bottom);
ctx.closePath();
//draw a border before clipping so we can see our viewport
ctx.stroke();
ctx.clip();
//transform the canvas so that the rectangle defined by `inner` fills the
//rectangle defined by `outer`.
var ratioWidth = (outer.right - outer.left) / (inner.right - inner.left);
var ratioHeight = (outer.bottom - outer.top) / (inner.bottom - inner.top);
ctx.translate(outer.left, outer.top);
ctx.scale(ratioWidth, ratioHeight);
ctx.translate(-inner.left, -inner.top);
//here I assume that your drawing code is a function that takes the context
//and draws your world into it. For performance reasons, you should
//probably pass `inner` as an argument too; if your draw function knows what
//portion of the world it is drawing, it can ignore things outside of that
//region.
drawFunction(ctx);
//go back to the previous canvas state.
ctx.restore();
};
If you are clever, you can use this to create multiple viewports, picture-in-pictures, etc. of different sizes and zoom in and out on stuff.
Performance
Second, as I commented in the code, you should make sure your drawing code knows what portion of your larger 'world' will be visible so that you don't do a lot of work trying to draw things that will not be visible.
The canvas transformation methods are meant for solving exactly this kind of problem. Use 'em!
*You will likely have problems if your world is so large that its coordinates cannot fit in an appropriate integer. You'll hit that problem roughly when your world exceeds billion (10^9) or a long trillion (10^18) pixels in any dimension, depending on whether the integers are 32- or 64-bit. If your world isn't measured in pixels but in 'world units', you'll run into problems when your world's total size and smallest feature scale lead to floating point inaccuracies. In that case, you will need to do extra work to keep track of things... but you'll probably still want to use the canvas transformation methods!
My very first game was a racing game where I moved the background instead of the car and although I want to think now that I had my reasons to make it so... I just didn't know better.
There are a few techniques that you need to know to achieve this well.
Tiled background. You need to make your track out of smaller pieces that tiled. To To draw 10,000 x 10,000 pixels is 100MPix image usually such image will have 32bit depth (4 bytes) this will end up being 400MB in memory. Compressions like PNG, JPEG won't help you since these are made to store and transfer images. They cant be rendered to a canvas without decompressing.
Move the car along your track. There is nothing worst then moving the BG under the car. If you need to add more features to your game like AI cars... now they will have to move along the map and to implement car collisions you need to make some not hard but strange spacial transformations.
Add camera entity. The camera needs to have position and viewport size (this is the size of your canvas). The camera will make or break your game. This is the entity that will give you the sense of speed in the game... You can have a camera shake for collisions, if you have drifts if your game the camera can slide pass the desired position and center back to the car, etc. Of course the most important thing will be tracking the car. Some simple suggestions I can give you are to not put the car in dead center of the camera. put the car a little behind so you can see a bit more what's in front of your. The faster the car moves the more you should offset the camera. Also you can't just compute the position of the camera instead compute desired position and slowly per frame move the current camera position to the desired position.
Now when you have camera and a large tiled map, when you draw the tiles you have to subtrack the camera position. You can also compute which tiles are not visible and skip them. This technique will allow you do extend your game with even larger maps or you can stream your map where you don't have all the tiles loaded and load in advance on background (AJAX) what will be visible soon.