Below is my Value Noise implementation, which I'm using for terrain generation. It creates strange artifacts when the terrain's length (Y size) is longer than its width (X size), but not otherwise.
I've been staring at this for hours. Any idea what's causing this?
(Screenshots from the demo. You can mess with the code in your browser console and see the results immediately by putting THREE.Terrain.Value = ValueNoise; rebuild(); after the code below.)
1:1 Aspect Ratio:
1:1.1 Aspect Ratio:
/**
* Generate a heightmap using white noise.
*
* #param {Vector3[]} g The terrain vertices.
* #param {Object} options Settings
* #param {Number} scale The resolution of the resulting heightmap.
* #param {Number} segments The width of the target heightmap.
* #param {Number} range The altitude of the noise.
* #param {Number[]} data The target heightmap.
*/
function WhiteNoise(g, options, scale, segments, range, data) {
if (scale > segments) return;
var i = 0,
j = 0,
xl = segments,
yl = segments,
inc = Math.floor(segments / scale),
k;
// Walk over the target. For a target of size W and a resolution of N,
// set every W/N points (in both directions).
for (i = 0; i <= xl; i += inc) {
for (j = 0; j <= yl; j += inc) {
k = j * xl + i;
data[k] = Math.random() * range;
/* c b *
* l t */
var t = data[k],
l = data[ j * xl + (i-inc)] || t, // left
b = data[(j-inc) * xl + i ] || t, // bottom
c = data[(j-inc) * xl + (i-inc)] || t; // corner
// Interpolate between adjacent points to set the height of
// higher-resolution target data.
for (var lastX = i-inc, x = lastX; x < i; x++) {
for (var lastY = j-inc, y = lastY; y < j; y++) {
if (x === lastX && y === lastY) continue;
var px = ((x-lastX) / inc),
py = ((y-lastY) / inc),
r1 = px * b + (1-px) * c,
r2 = px * t + (1-px) * l;
data[y * xl + x] = py * r2 + (1-py) * r1;
}
}
}
}
// Assign the temporary data back to the actual terrain heightmap.
// Accumulate additively across multiple calls to WhiteNoise.
for (i = 0, xl = options.xSegments + 1; i < xl; i++) {
for (j = 0, yl = options.ySegments + 1; j < yl; j++) {
k = j * xl + i;
g[k].z += data[k] || 0;
}
}
}
/**
* Generate random terrain using value noise.
*
* The basic approach of value noise is to generate white noise at a
* smaller octave than the target and then interpolate to get a higher-
* resolution result. This is then repeated at different resolutions.
*
* #param {Vector3[]} g The terrain vertices.
* #param {Object} options Settings
*/
ValueNoise = function(g, options) {
// Set the segment length to the smallest power of 2 that is greater
// than the number of vertices in either dimension of the plane
var segments = Math.max(options.xSegments, options.ySegments) + 1, n;
for (n = 1; Math.pow(2, n) < segments; n++) {}
segments = Math.pow(2, n);
// Store the array of white noise outside of the WhiteNoise function to
// avoid allocating a bunch of unnecessary arrays; we can just
// overwrite old data each time WhiteNoise() is called.
var data = new Array(segments*(segments+1));
// Layer white noise at different resolutions.
var range = options.maxHeight - options.minHeight;
for (var i = 2; i < 7; i++) {
WhiteNoise(g, options, Math.pow(2, i), segments, range * Math.pow(2, 2.4-i*1.2), data);
}
// Clamp and stretch the results
THREE.Terrain.Clamp(g, {
maxHeight: options.maxHeight,
minHeight: options.minHeight,
stretch: true,
});
};
When you assign the height change of the temporary data field, you really have two different indices, because you have two different map sizes: the original map and the temporary map inflated to the next power of 2. So:
for (i = 0, xl = options.xSegments + 1; i < xl; i++) {
for (j = 0, yl = options.ySegments + 1; j < yl; j++) {
var kg = j * xl + i;
var kd = j * segments + i;
g[kg] += data[kd];
}
}
I also think that you might have an off-by-one error in your data index. The size of data should be (segments + 1) * (segments + 1), because you need the outer cells in both dimensions and your xl and yl should be segments + 1.
Related
I am learning WebGL with vanilla javascript. I have been able to follow along my tutorial to draw a simple bell shape using gl.LINES with the following function to create the vertices:
function createVertexData() {
var n = 20; // across
var m = 10; // down
// Positions.
vertices = new Float32Array(3 * (n + 1) * (m + 1));
// Index data for Linestrip.
indices = new Uint16Array(2 * 2 * n * m);
var dt = 2 * Math.PI / n;
var dr = 1 / m;
// Counter for entries in index array.
var iIndex = 0;
// Loop angle t.
for (var i = 0, t = Math.PI; i <= n; i++, t += dt) {
// Loop radius r.
for (var j = 0, r = 0; j <= m; j++, r += dr) {
var iVertex = i * (m + 1) + j;
var x = r * Math.cos(t);
var y = Math.cos(r * Math.PI);
var z = r * Math.sin(t);
// Set vertex positions.
vertices[iVertex * 3] = x;
vertices[iVertex * 3 + 1] = y;
vertices[iVertex * 3 + 2] = z;
// Set index.
// Line on beam.
if (j > 0 && i > 0) {
indices[iIndex++] = iVertex - 1;
indices[iIndex++] = iVertex;
}
// Line on ring.
if (j > 0 && i > 0) {
indices[iIndex++] = iVertex - (m + 1);
indices[iIndex++] = iVertex;
}
}
}
}
Now I want to change the createVertexData() function to let's say draw a pillow (only the mesh, not the texture).
The formula for that pillow is (source):
x = cos(u)
y = cos(v)
z = a sin(u) sin(v)
Where:
- a is a constant
- u is an element from [0, pi]
- v is an element from [-pi, pi]
All my attempts at changing the createVertexData() miserably failed: I calculated x, y, z according to the new formula and set the loops to run according to the domains of u and v. Also renamed variables of course. The resulting shape always looked very off and not even close to the pillow.
How does it need to look like?
Thanks in advance.
How does the distribution of:
var randomNumber = Math.random()*50 + Math.random()*20;
compare to that of:
var randomNumber = Math.random()*70;
The first will not produce a flat distribution with more values near 70/2, while the second will produce an even distribution..
The easy way to find out is just to sample the values and graph them.
Sampled slowly just for fun.
const ctx = canvas.getContext("2d");
const a1 = new Float64Array(70);
const a2 = new Float64Array(70);
var total = 0;
function doSamples(samples){
for(var i = 0; i < samples; i ++){
var n1 = Math.random() * 50 + Math.random() * 20;
var n2 = Math.random() * 70;
a1[n1 | 0] += 1;
a2[n2 | 0] += 1;
}
var max = 0;
for(i = 0; i < 70; i ++){
max = Math.max(max,a1[i],a2[i]);
}
ctx.clearRect(0,0,canvas.width,canvas.height);
for(i = 0; i < 70; i ++){
var l1 = (a1[i] / max) * canvas.height;
var l2 = (a2[i] / max) * canvas.height;
ctx.fillStyle = "Blue";
ctx.fillRect(i * 8,canvas.height - l1,4,l1)
ctx.fillStyle = "Orange";
ctx.fillRect(i * 8 + 4,canvas.height - l2,4,l2)
}
total += samples;
count.textContent = total;
}
function doit(){
doSamples(500);
setTimeout(doit,100);
}
doit();
canvas {border:2px solid black;}
<canvas id="canvas" width = 560 height = 200></canvas><br>
Orange is random() * 70<br>
Blue is random() * 50 + random() * 20<br>
Graph is normalised.
<span id="count"></span> samples.
You could do a brute force approach by counting a million random values and check if the sum r70s is equal to a single random value r70.
As you see the distribution is not equal.
function countValue(key, value) {
value = Math.floor(value);
count[key][value] = (count[key][value] || 0) + 1;
}
var i,
r20, r50, r70,
count = { r20: [], r50: [], r70: [], r70s: [] };
for (i = 0; i < 1e6; i++) {
r20 = Math.random() * 20;
r50 = Math.random() * 50;
r70 = Math.random() * 70;
countValue('r20', r20);
countValue('r50', r50);
countValue('r70', r70);
countValue('r70s', r20 + r50);
}
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The density function of a sum of random variables is the convolution of the density functions of the summands.
In this case the two summands have uniform densities, therefore their convolution is a piecewise linear function (a triangle). In general for the sum of n uniform variables, the density of the sum is a piecewise polynomial of degree n - 1.
The sum has the expected value equal to the sum of the expected values, namely 50/2 and 20/2, which is equal to 70/2, which is the expected value of Math.random()*70. So the expected values are the same, but the distributions are different.
I want an array looking like this:
[
[0,0,1,1,1,0,0],
[0,1,1,1,1,1,0],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,1,1,1,1,1,0],
[0,0,1,1,1,0,0],
]
My first approach was to get the circumference
var steps = 100;
var coord = [];
var x,y;
for (var i = 0; i < steps; i++) {
var phase = 2 * Math.PI * i / steps;
x = Math.round(cenx + range * Math.cos(phase));
y = Math.round(ceny + range * Math.sin(phase))
if(x>=0 && y >=0){
coord.push([x,y]);
}
}
and with the resulting coords i could have juggled around to get the circular area. but i doubt that would be performant.
So my second approach would be to check every entry of the array whether it has a certain distance (i.e. radius) to the center of my circle. but for huge maps that wouldnt be performant either. perhaps checking only in a reasonable frame would be wiser.
but im certain there is a better approach for this problem.
im needing this for a fog of war implementation.
Your second suggested approach of testing each point in the array will be simple to implement, and can be optimized to just one subtract, one multiply and one test per element in the inner loop.
The basic test is ((x - centerX) * (x - centerX)) + ((y - centerY) * (y - centerY)) > radiusSq, but since ((y - centerY) * (y - centerY)) will be constant for a given row you can move that outside the loop.
Given that you have to visit each element in the array and set it anyway (meaning your algorithm will always be O(n2) on the circle radius), the test is a negligible cost:
// circle generation code:
function makeCircle(centerX, centerY, radius, a, arrayWidth, arrayHeight)
{
var x, y, d, yDiff, threshold, radiusSq;
radius = (radius * 2) + 1;
radiusSq = (radius * radius) / 4;
for(y = 0; y < arrayHeight; y++)
{
yDiff = y - centerY;
threshold = radiusSq - (yDiff * yDiff);
for(x = 0; x < arrayWidth; x++)
{
d = x - centerX;
a[y][x] = ((d * d) > threshold) ? 0 : 1;
}
}
}
// test code:
var width = 7;
var dim = (width * 2) + 1;
var array = new Array(dim);
for(row = 0; row < dim; row++)
array[row] = new Array(dim);
makeCircle(width, width, width, array, dim, dim);
for(var y = 0, s = ""; y < dim; y++)
{
for(var x = 0; x < dim; x++)
{
s += array[y][x];
}
s += "<br>";
}
document.body.innerHTML += s + "<br>";
I would use the mid-point circle algorithm and see the array as a bitmap.
I did this JavaScript implementation a while back, modified here to use an array as target source for the "pixel". Just note that a circle will produce odd widths and heights as the distance is always from a single center point and we can only use integer values in this case.
Tip: For speed improvements you could use typed array instead of a regular one (shown below).
Example
Make sure to use integer values as input, the code will clip values outside the "bitmap"/array -
var width = 7, height = 7,
array = new Uint8Array(width * height);
// "draw" circle into array
circle(3, 3, 3);
renderDOM();
// circle example 2
width = height = 17;
array = new Uint8Array(width * height);
circle(8, 8, 8);
renderDOM();
function circle(xc, yc, r) {
if (r < 1) return;
var x = r, y = 0, // for Bresenham / mid-point circle
cd = 0,
xoff = 0,
yoff = r,
b = -r,
p0, p1, w0, w1;
while (xoff <= yoff) {
p0 = xc - xoff;
p1 = xc - yoff;
w0 = xoff + xoff;
w1 = yoff + yoff;
hl(p0, yc - yoff, yc + yoff, w0); // fill a "line"
hl(p1, yc - xoff, yc + xoff, w1);
if ((b += xoff+++xoff) >= 0) {
b -= --yoff + yoff;
}
}
// for fill
function hl(x, y1, y2, w) {
w++;
var xw = 0;
while (w--) {
xw = x + w;
setPixel(xw, y1);
setPixel(xw, y2);
}
}
function setPixel(x, y) {
if (x < width && y < height && x >= 0 && y >= 0)
array[y * width + x] = 1;
}
}
function renderDOM() {
for(var i = 0, str = ""; i < array.length; i++) {
if (i > 0 && !(i % width)) str += "<br>";
str += array[i];
}
document.body.innerHTML += str + "<br><br>";
}
body {font:18px monospace}
For an odd-sized array (2r+1 x 2r+1),
for (row= 0; row < 2 * r + 1; row++)
{
f= (row + 1) * (row - 2 * r - 1) + r * r + r;
for (col= 0; col < 2 * r + 1; f+= 2 * (col - r) + 1; col++)
{
array[row][col]= f >= 0;
}
}
I have this weird problem. I'm trying to code some 2d collision and stumbled across a collision detection program. I decided to try translate it into javascript and maybe learn something along the way.
The thing is that when I run it in my browser, all circles freeze in place as soon as two of them collide, but the program don't crash and I get no errors in the console.
I've tried to debug it and I think the problem lays within the first if-statement in the checkForCollision-function. Like it's always false.
Here's a link to the original version (Scroll down to "Listing 3" for complete code):
http://compsci.ca/v3/viewtopic.php?t=14897&postdays=0&postorder=asc&start=0
And here is my translation:
var canvas = document.getElementById('canvas01');
var drawFps = document.getElementById('fps');
var context = canvas.getContext('2d');
// The amount of delay between frames
var delay = 50;
// The maximum distance two circles can be apart and still be considered colliding
var epsilon = 10^-9;
// The number of circles
var numCircles = 5;
// We anticipate many circles so we create an array
var circles = new Array();
// Stores the amount of time untill a collision occurs
var t;
// Initialize the circles
createCircle();
function createCircle() {
for(var i = 0; i < numCircles; i++) {
var velX = Math.floor(Math.random() * 5) + 1; // this will get a number between 1 and 5;
velX *= Math.floor(Math.random() * 2) == 1 ? 1 : -1; // this will add minus sign in 50% of cases
var velY = Math.floor(Math.random() * 5) + 1;
velY *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
var radius = Math.floor(Math.random() * 30) + 15;
var mass = Math.PI * Math.pow(radius, 2);
circleData = {
x : Math.floor(Math.random() * canvas.width),
y : Math.floor(Math.random() * canvas.height),
vx : velX,
vy : velY,
vxp : velX,
vyp : velY,
r : radius,
m : mass
}
circles.push(circleData);
}
}
setInterval(loop, 17);
// Returns the amount of frames untill a collision will occur
function timeToCollision() {
var t = Number.MAX_VALUE;
var A;
var B;
var C;
var D;
var DISC;
// Loop through every pair of circles and calculate when they will collide
for(var i = 0; i < circles.length; i++) {
for(var j = 0; j < circles.length; j++) {
if(movingToCircle (circles[i], circles[j])) {
// Breaking down the formula for t
A = Math.pow(circles[i].vx, 2) + Math.pow(circles[i].vy, 2) - 2 * circles[i].vx * circles[j].vx + Math.pow(circles[j].vx, 2) - 2 * circles[i].vy * circles[j].vy + Math.pow(circles[j].vy, 2);
B = -circles[i].x * circles[i].vx - circles[i].y * circles[i].vy + circles[i].vx * circles[j].x + circles[i].vy * circles[j].y + circles[i].x * circles[j].vx - circles[j].x * circles[j].vx + circles[i].y * circles[j].vy - circles[j].y * circles[j].vy;
C = Math.pow(circles[i].vx, 2) + Math.pow(circles[i].vy, 2) - 2 * circles[i].vx * circles[j].vx + Math.pow(circles[j].vx, 2) - 2 * circles[i].vy * circles[j].vy + Math.pow(circles[j].vy, 2);
D = Math.pow(circles[i].x, 2) + Math.pow(circles[i].y, 2) - Math.pow(circles[i].r, 2) - 2 * circles[i].x * circles[j].x + Math.pow(circles[j].x, 2) - 2 * circles[i].y * circles[j].y + Math.pow(circles[j].y, 2) - 2 * circles[i].r * circles[j].r - Math.pow(circles[j].r, 2);
DISC = Math.pow((-2 * B), 2) - 4 * C * D;
// If the discriminent if non negative, a collision will occur and
// we must compare the time to our current time of collision. We
// udate the time if we find a collision that has occurd earlier
// than the previous one.
if(DISC >= 0) {
// We want the smallest time
t = Math.min(Math.min(t, 0.5 * (2 * B - Math.sqrt(DISC)) / A), 0.5 * (2 * B + Math.sqrt(DISC)) / A)
}
}
}
}
return t;
}
// Draws all the circles to the screen
function drawCircles() {
for(var i = 0; i < circles.length; i++) {
context.fillStyle = '#000000';
context.beginPath();
context.arc(circles[i].x, circles[i].y, circles[i].r, 0, 2 * Math.PI, true);
context.closePath();
context.fill();
}
}
// Updates all the circles attributes. If a collision Occures in between frames,
// the circles will be updated to the point of the collision. We return when the
// collision occurs so that we can adjust the delay in the main loop.
function updateCircles() {
// We want to increment by at most one frame
var t = Math.min(1, timeToCollision());
for(var i = 0; i < circles.length; i++) {
circles[i].x += circles[i].vx * t;
circles[i].y += circles[i].vy * t;
}
return t;
}
// Collision reaction function
function collide(c1, c2) {
var nx = (c1.x - c2.x) / (c1.r + c2.r);
var ny = (c1.y - c2.y) / (c1.r + c2.r);
var a1 = c1.vx * nx + c1.vy * ny;
var a2 = c2.vx * nx + c2.vy * ny;
var p = 2 * (a1 - a2) / (c1.m + c2.m);
c1.vxp = c1.vx - p * nx * c2.m;
c1.vyp = c1.vy - p * ny * c2.m;
c2.vxp = c2.vx + p * nx * c1.m;
c2.vyp = c2.vy + p * ny * c1.m;
}
// Checks if a collision has occured between any of the circles
function checkForCollision() {
for(var i = 0; i < circles.length; i++) {
for(var j = 0; j < circles.length; j++) {
if(movingToCircle(circles[i], circles[j]) && Math.pow((circles[j].x - circles[i].x), 2) + Math.pow((circles[j].y - circles[i].y), 2) <= Math.pow((circles[i].r + circles[j].r + epsilon), 2)) {
collide(circles[i], circles[j]);
}
}
if(circles[i].x < 1 || circles[i].x > canvas.width) {
circles[i].vxp *= -1;
}
if(circles[i].y < 1 || circles[i].y > canvas.height) {
circles[i].vyp *= -1;
}
}
for(var i = 0; i < circles.length; i++) {
circles[i].vx = circles[i].vxp;
circles[i].vy = circles[i].vyp;
}
}
// Tells us if two circles are moving towards each other
function movingToCircle(c1, c2) {
// Position Vector dotted with the Relative Velocity Vector
return (c2.x - c1.x) * (c1.vx - c2.vx) + (c2.y - c1.y) * (c1.vy - c2.vy) > 0;
}
// Main animation loop
function loop() {
// Clear Canvas
context.fillStyle = '#ffffff';
context.fillRect( 0, 0, canvas.width, canvas.height );
drawCircles();
checkForCollision();
t = updateCircles();
}
Note that I've changed balls to circles just because I find it fits better for 2d.
Thank you in advance.
just wanted to know if anybody has already done this in Javascript or if I have to do it myself - if latter: How would I do it? (not asking for a piece of code, only curious which approach you would use)
I have a better solution. It is not necessary to iterate through all the pixels, only through the ones outside the bounding box. Think of it this way, if you wanted to do the same thing in 1D: finding the first and last position of a value in an array, would you walk through the entire array? It would be better to walk through from the start till you find the first value, then walk from the end till you find the last value. The following code does the same for 2D. I haven't tested it thoroughly (either for correctness or speed), but it seems to work, and common sense say it's faster.
BitmapData.prototype.getColorBoundsRect = function(mask, color, findColor, rect){
findColor = typeof findColor !== 'undefined' ? findColor : true;
rect = typeof rect !== 'undefined' ? rect : new module.Rect(0, 0, this.width, this.height);
var l = rect.w - 1;
var r = 0;
var t = rect.h - 1;
var b = 0;
var data = this.context.getImageData(rect.x, rect.y, rect.w, rect.h).data;
// Scan from top to first pixel.
for (var i = 0; i < data.length; i += 4){
var val = module.RGBToHex({r:data[i], g:data[i+1], b:data[i+2], a:data[i+3]});
// console.log(val, mask, color, (val & mask) >>> 0)
if ((findColor && ((val & mask) >>> 0 == color)) || (!findColor && ((val & mask) >>> 0 != color))){
l = r = ((i / 4) % rect.w);
t = b = Math.floor(i / 4 / rect.w);
break;
}
}
// We found nothing.
if (i >= data.length) {
return null;
}
// Scan from bottom to first pixel
for (var j = data.length - 4; j > i; j -= 4){
var val = module.RGBToHex({r:data[j], g:data[j+1], b:data[j+2], a:data[j+3]});
if ((findColor && ((val & mask) >>> 0 == color)) || (!findColor && ((val & mask) >>> 0 != color))){
l = Math.min(l, ((j / 4) % rect.w))
r = Math.max(r, ((j / 4) % rect.w))
b = Math.floor(j / 4 / rect.w);
break;
}
}
console.log(l, r, t, b);
// Scan from left
for (var x = 0; x < l; x ++){
for (var y = t + 1; y <= b; y ++){
i = (y * rect.w + x) * 4
var val = module.RGBToHex({r:data[i], g:data[i+1], b:data[i+2], a:data[i+3]});
if ((findColor && ((val & mask) >>> 0 == color)) || (!findColor && ((val & mask) >>> 0 != color))){
l = Math.min(l, x);
break;
}
}
}
console.log(l, r, t, b);
// Scan from right
for (var x = rect.w - 1; x > r; x --){
for (var y = t; y < b; y ++){
i = (y * rect.w + x) * 4
var val = module.RGBToHex({r:data[i], g:data[i+1], b:data[i+2], a:data[i+3]});
if ((findColor && ((val & mask) >>> 0 == color)) || (!findColor && ((val & mask) >>> 0 != color))){
r = Math.max(r, x);
break;
}
}
}
console.log(l, r, t, b)
return new module.Rect(l + rect.x, t + rect.y, (r - l), (b - t));
}
In this code BitmapData just wraps a canvas object and its context2d, and Rect is an {x: , y: , w: , h: } object. I had to do some screwing around with RGBToHex to make sure I was getting positive numbers (uint's) too:
module.RGBToHex = function(rgb) {
return (rgb.a << 24 | rgb.r<<16 | rgb.g<<8 | rgb.b) >>> 0;
};
here's my quick'n'dirty solution, maybe somebody'll find it useful ;)
/**
* get a rectangle around color
* #param {...} ctx 2dCanvasObject to be scanned
* #return {Object} object storing the rectangle's data (x, y, w(idth), h(eight))
*/
function getColorBoundsRect(ctx) {
/**
* the canvas' context's data property (shorthand)
* #type {...}
*/
var data = ctx.data,
/**
* counter variable
* #type {Number}
*/
i = 0,
/**
* the "leftest" pixel that is not black (starts right, as we check if currently looped pixel (that is not black) is "lefter" than the current outerLeftPixel)
* #type {Number}
*/
outerLeftPixel = w-1,
/**
* the "rightest" pixel that is not black (starts left, as we check if currently looped pixel (that is not black) is "righter" than the current outerRightPixel)
* #type {Number}
*/
outerRightPixel = 0,
/**
* the "toppest" pixel that is not black (starts at bottom, as we check if currently looped pixel (that is not black) is "topper" than the current outerTopPixel)
* #type {Number}
*/
outerTopPixel = h-1,
/**
* the "bottomest" pixel that is not black (starts at top, as we check if currently looped pixel (that is not black) is "bottomer" than the current outerBottomPixel)
* #type {Number}
*/
outerBottomPixel = 0,
/**
* x coordinate of currently looped pixel
* #type {Number}
*/
x,
/**
* y coordinate of currently looped pixel
* #type {Number}
*/
y;
// loop through all pixels
// i equals the i'th pixel (0 is the upper left pixel, w*h is the bottom right pixel)
while (i < (data.length / 4)) {
// check if currently looped pixel is anything else than black --> color
if ((data[i*4] + data[i*4+1] + data[i*4+2]) > 0) {
// set coordinates for the currently looped pixel
x = i % w; // if one row has 10px and i = 35, the x coordinate of the current pixel is 35 % 10 = 5
y = Math.floor(i / w); // if one row has 10px and i=35, the y coordinate of the current pixel is 35/10 = 3.5 (--> rounded off = 3)
// if the x coordinate of the current (colored) pixel is smaller than the current "leftest" pixel, set the x coordinate as new "leftest pixel"
// same procedure for the other values
if (x < outerLeftPixel) {
outerLeftPixel = x;
}
if (x > outerRightPixel) {
outerRightPixel = x;
}
if (y < outerTopPixel) {
outerTopPixel = y;
}
if (y > outerBottomPixel) {
outerBottomPixel = y;
}
}
++i;
}
// if there is color on the canvas, the outer[Right|Left|Bottom|Top]Pixel properties should have been updated accordingly and the following condition should be true
if (outerRightPixel > outerLeftPixel && outerBottomPixel > outerTopPixel) {
return {
x: outerLeftPixel,
y: outerTopPixel,
w: outerRightPixel - outerLeftPixel,
h: outerBottomPixel - outerTopPixel
};
}
// if there is no color on the canvas, return false, as there is no rectangle
else {
return false;
}
}