Related
I'm not using any engine, but instead trying to build my own softbody dynamics for fun using verlet integeration. I made a cube defined by 4x4 points with segments keeping its shape like so:
I have the points collide against the edges of the scene and it seems to work fine. Though I do get some cases where the points collapses in itself and it'll create a dent instead of maintaining its box shape. For example, if it's a high enough velocity and it lands on its corner it tends to crumble:
I must be doing something wrong or out of order when solving the collision.
Here's how I'm handling it. It's in Javascript, though the language doesn't matter, feel free to reply with any language:
sim = function() {
// Sim all points.
for (let i = 0; i < this.points.length; i++) {
this.points[i].sim();
}
// Keep in bounds.
let border = 100;
for (let i = 0; i < this.points.length; i++) {
let p = this.points[i];
let vx = p.pos.x - p.oldPos.x;
let vy = p.pos.y - p.oldPos.y;
if (p.pos.y > height - border) {
// Bottom screen
p.pos.y = height - border;
p.oldPos.y = p.pos.y + vy;
} else if (p.pos.y < 0 + border) {
// Top screen
p.pos.y = 0 + border;
p.oldPos.y = p.pos.y + vy;
}
if (p.pos.x < 0 + border) {
// Left screen
p.pos.x = 0 + border;
p.oldPos.x = p.pos.x + vx;
} else if (p.pos.x > width - border) {
// Right screen
p.pos.x = width - border;
p.oldPos.x = p.pos.x + vx;
}
}
// Sim its segments.
let timesteps = 20;
for (let ts = 0; ts < timesteps; ts++) {
for (let i = 0; i < this.segments.length; i++) {
this.segments[i].sim();
}
}
}
Please let me know if I need to post any other details.
This may be better answered on a physics or game-dev exchange (and likely has already been), but i'll give it a crack because it's nice to revisit this stuff...
Verlet integration is a fantastically stable if not physically accurate method, but the problem here is not the integration method, or anything you've done wrong as far as I can see; it's the type of simulation: mass-aggregate physics (the building of geometry out of dynamic constraints), which is really nice and simple :) but has some inherent deficiencies and limitations, and this particular problem is inherent to the simulation type.
First, look carefully at the arrangement of constraints in the collapsed box - they are just as valid as the initial one. Although individual constraints may not be as satisfied, in combination they are still in a local equilibrium with each other - there nothing compelling them to form their original arrangement.
Second, the external force (collision with immovable plane) is what overcame the constraint forces originally. Even if the constraints respond proportionally towards infinity as they compress - the simulation can never match reality because it works a frame at a time not continuously, and the longer the frame the more error.
To retain shape more reliably, a more explicit angular constraint is needed, which usually involves quaternions - these are quite a bit harder to implement than distance constraints, and once you have them you will be pretty far along the road to implementing rigid body physics anyway. But there are ways to mitigate instead:
1. Use a smaller interval
All posteriori simulations and numerical integration in general has some inherent instability. While different integration methods (e.g verlet) can mitigate this, generally the smaller the interval the better the stability. This alone will give the constraints more opportunity to push back against external forces, but it will also increase the maximum stable constraint stiffness.
This will probably require you to optimise your engine more. Additionally make sure you don't couple your render step to the simulation, you want to be able to render at multiples of the simulation interval which allows you to run the simulation faster for stability without unnecessarily rendering more frames than is useful, as it will just slow down the simulation.
2. Try more stable shapes
For your box-of-boxes shape, see what happens when you add more constraints between far vertices, it will add more global stability to the shape.
A common type of shape people make with mass-aggregate physics are polygons, because it's straight forward to make them highly interconnected (a little bit like a bicycle wheel, but where each spoke point connects to every other spoke point). In- fact spoke-like designs are one of the most stable, but you can usually apply the same principles to more irregular shapes once you get an intuition for it.
3. A different type of constraint
Quaternions are not the only possible constraint that will help retain configuration, the main problem is not so much that explicit angle isn't being maintained; but rather that when points are forced past a certain position relative to their siblings, their distance constraints flip and start working in reverse - keeping them on that side.
There could be many different ways to solve this without something as complex as quarternions - in fact I will give it some thought and edit this post if I come up with anything, I have a bit more ammunition since I last explored mass-aggregate physics...
TLDR:
I need to change the perspective over an object in a 2d cavas.
Let's say I make a photo of my desk while I sit next to it. I want to change that image, to look as if I would see it from above. Basically the view would change like in the image below (yes, my paint skills are awesome :)) ).
I am using canvas and easeljs (createjs).
I am totally aware that this is not a 3rd object, I don't have a stage and a camera. I am also aware that easeljs doesn't support this as a feature.
What I am actually trying is to emulate this (I am also not interested in quality of the result at this point).
What I tried and it worked (I noticed I am not the first to try to do this and didn't actually found a real answer so this is my current approach):
I take the original image. Divide it in a high number of images (the higher the better) as in the image below.
Then I scale each of the "mini-images" on x axis with a different factor (that I'm computing depending on the angle the picture was made). Part of relevant code below (excuse the hardcodings and creepy code - is from a proof of concept I made in like 10 minutes):
for (var i = 0; i < 400; i++) {
crops[i] = new createjs.Bitmap(baseImage);
crops[i].sourceRect = new createjs.Rectangle(0, i, 700, i + 1);
crops[i].regX = 350;
crops[i].y = i - 1;
crops[i].x = 100;
crops[i].scaleX = (400 - i / 2) * angleFactor;
stage.addChild(crops[i]);
}
Then you crop again only the relevant part.
Ok... so this works but the performance is... terrible - you basically generate 400 images - in this case - then you put them in a canvas. Yes, I know it can be optimized a bit but is still pretty bad. So I was wondering if you have any other (preferably better) approaches.
I also tried combining a skewing transformation with a scale and a rotation but I could not achieve the right result (but I actually still think there may still be something here... I just can't put my finger on it and my "real" math is a bit rusty).
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 am writing a software filter object and trying to implement a light bloom effect. I'm using a simple, two pass convolution approach which works fine except that the effect radius is tiny and I can't seem to control the radius. I've played with larger box filters and adjusted the weights of the various pixels, but none of that seems to have any effect. The effect seems to have a maximum size (which is not very big) and then all changes to the parameters just serve to make it smaller.
I'd like to be able to create a light bloom with an arbitrary radius. After a LOT of experimentation and searching online, I'm starting to wonder if this just can't be done. I've been thinking about alternate approaches--plasmas, gradients, and various seeding schemes--but I'd like to hound this path into the ground first. Does anyone out there know how to create an arbitrarily sized light bloom (in software)?
The javascript is as follows (this operates on an HTML5 canvas; I can add comments to the code if needed):
// the kernel functions are called via Array.map on this.backBuffer.data, a canvas surface color array
this.kernelFirstPass = function(val, index, array)
{
if(index<pitch || index>=array.length-pitch || index%pitch<4 || index%pitch>pitch-5 || index%4==3)
return;
var c = 1,
l1 = 1,
l2 = 1,
l3 = 1,
r1 = 1,
r2 = 1,
r3 = 1;
var avg =
(
c*this.frontBuffer.data[index]+
l1*this.frontBuffer.data[index-4]+
l2*this.frontBuffer.data[index-8]+
l3*this.frontBuffer.data[index-12]+
l1*this.frontBuffer.data[index+4]+
l2*this.frontBuffer.data[index+8]+
l3*this.frontBuffer.data[index+12]
)/(c+l1+l2+l3+l1+l2+l3);
//this.frontBuffer.data[index] = avg;
array[index] = avg;
}
this.kernelSecondPass = function(val, index, array)
{
if(index<pitch || index>=array.length-pitch || index%pitch<4 || index%pitch>=pitch-4 || index%4==3)
return;
var c = 1,
l1 = 1,
l2 = 1,
l3 = 1,
r1 = 1,
r2 = 1,
r3 = 1;
var avg =
(
c*array[index]+
l1*array[index-pitch]+
l2*array[index-(pitch*2)]+
l3*array[index-(pitch*3)]+
l1*array[index+pitch]+
l2*array[index+(pitch*2)]+
l3*array[index+(pitch*3)]
)/(c+l1+l2+l3+l1+l2+l3);
array[index] = avg;
}
Perhaps an important point that I missed in my original question was to explain that I'm not trying to simulate any real or particular phenomenon (and it probably doesn't help that I call it a "light" bloom). It could be that, when dealing with real light phenomenon, in order to have a penumbra with arbitrary radius you need a source (ie. "completely saturated area") with arbitrary radius. If that were in fact the way a real light bloom behaved, then Jim's and tskuzzy's explanations would seem like reasonable approaches to simulating that. Regardless, that's not what I'm trying to accomplish. I want to control the radius of the "gradient" portion of the bloom independently from the size/intensity/etc. of the source. I'd like to be able to set a single, white (max value) pixel in the center of the screen and have the bloom grow out as far as I want it, to the edges of the screen or beyond if I feel like it.
In order to achieve a good bloom effect, you should be using high-dynamic range rendering . Otherwise, your whites will not be bright enough.
The reason for this is that pixel brightnesses are typically represented from the range [0,1]. Thus the maximum brightness is clamped to 1. However in a real world situation, there isn't really a maximum. And although really bright lights are all perceived as a "1", the visual side-effects like bloom are not the same.
So what you have to do is allow for really bright areas to exceed the maximum brightness, at least for the bloom convolution. Then when you do the rendering, clamp the values as needed.
Once you have that done, you should be able to increase the bloom radius simply by increasing the size of the Airy disk used in the convolution.
The simple summary of tskuzzy's answer is: use a floating-point buffer to store the pre-bloom image, and either convolve into a second floationg-point buffer (whence you saturate the pixels back into integer format) or saturate on the fly to convert each output pixel back to integer before storing it directly in an integer output buffer.
The Airy convolution must be done with headroom (i.e. in either fixed-point or floating-point, and these days the former isn't usually worth the hassle with fast FPUs so common) so that brighter spots in the image will bleed correspondingly more over their neighbouring areas.
Note: on-the-fly saturation for colour isn't as simple as individually clipping the channels - if you do that, you might end up with hue distortion and contours around the spots that clip.