Loop construction in Processing artistic rendition - javascript

The code is from here:
t=0
draw=_=>{t||createCanvas(W = 720,W)
t+=.01
B=blendMode
colorMode(HSB)
B(BLEND)
background(0,.1)
B(ADD)
for(y = 0; y < W; y += 7)
for(x = 0; x < W; x+=7)
dist(x, y, H = 360, H) +
!fill(x * y % 360, W, W,
T=tan(noise(x, y) * 9 + t))
< sin(atan2(y - H, x - H) * 2.5 + 84)**8 * 200 + 130?
circle(x, y + 30, 4/T) : 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
I see that t is increased by 0.01 each iteration, but I am unsure whether for each t value the entire canvas is refreshed, or whether there is an endpoint somewhat set by :0}. Is this correct?
It also seems like there is a Boolean call in the middle basically comparing two distances to determine which circles are filled and how. If the < was instead > the circles would form a complementary pattern on the rendition.
The ! is explained here, as a way of saving space and calling a function as soon as it is declared. I presume it determines how points are filled with a certain variable color scheme depending on the Boolean operation result. The +!fill() is unfamiliar, as well as the ? at the end, and I guess that they amount to: if the x, y location is within the boundary of the star the circles are colored (filled) as such, but the negation in '!' is confusing.
Can I get an English explanation of the main structural points on this code (the loop and the Boolean) to match the syntax?
I have so far gathered that the basic loop is
for(y from 0 to the width of the canvas at increments of 7)
for(x from... )
check if the distance from (x , y) to 360 is less than sin()^8 * 200 + 130
If so fill (or not fill with the ! ????) with these colors
otherwise do nothing :0

This is what it might look like if it were written normally
let t = 0;
const W = 720;
// https://p5js.org/reference/#/p5/draw
// `draw` needs to be in the global scope so p5 can use it
draw = () => {
// create a canvas if this is the first frame
if (!t) createCanvas(W, W);
t += 0.01;
// Use HSB and blending to do the fancy effects
// The black circles are because we're ignoring stroke and so using its defaults
// The blending will eventually hide it anyway
colorMode(HSB);
blendMode(BLEND);
background(0, 0.1);
blendMode(ADD);
// iterate over 7px grid
for(y = 0; y < W; y += 7) {
for(x = 0; x < W; x += 7) {
// center
const H = 360;
// distance from center
const D = dist(x, y, H, H);
// pick an alpha based on location and time
const T = tan(noise(x, y) * 9 + t);
// set fill color
fill(x * y % 360, W, W, T);
// magic to calculate the star's boundary
// sine wave in polar coords, I think
const S = sin(atan2(y - H, x - H) * 2.5 + 84)**8 * 200 + 130;
// draw a circle if we're within the star's area
// circle's color, alpha, and radius depend on location and time
if (D < S) circle(x, y + 30, 4/T);
}
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
Note that there are some hacks in the original code that are solely to help make it a one-liner or to reduce the number of characters. They don't have any meaningful effect on the results.

The main issue is the +! with ? and :0, which is terribly confusing, but clearly it is equivalent to
t=0
draw=_=>{t||createCanvas(W = 720,W)
t+=.01
B=blendMode
colorMode(HSB)
B(BLEND)
background(0,.1)
B(ADD)
for(y = 0; y < W; y += 7)
for(x = 0; x < W; x+=7)
if(dist(x, y, H = 360, H) < sin(atan2(y - H, x - H) * 2.5 + 84)**8 * 200 + 130){
fill(x * y % 360, W, W,
T=tan(noise(x, y) * 9 + t))
circle(x, y + 30, 4/T)}else{0}}
The Boolean in the ? applies to the dist() part (in absolute values the angle has to be less than sin()... + 130.
The + part I still don't understand, and hopefully will be addressed by someone who knows Processing or JS (not me). However, it probably forces the execution to identify and throw out with regards to filling (hence !) values that are too low for the sin(atan2()), which will happen at around 0 and pi.
Because of arctan2() being (here)
the number of spikes in the star will be a multiple of 5 going from 0 to 2 pi. The fact that changing the code to < sin(2 * atan2(...)... doubles the spikes implies that the fill() part is also in Boolean operation of its own.
The result of the Boolean determines whether the colorful fill is applied or not (applied if less than).
The :0 at the end is the else do nothing.

Related

How to move along a bezier curve with a constant velocity without a costly precomputation?

Forgive me for the long code example, but I couldn't figure out how to properly explain my question with any less code:
let c = document.querySelector("canvas");
let ctx = c.getContext("2d");
class BezierCurve {
constructor(x1, y1, cpX, cpY, x2, y2) {
this.f = 0;
this.x1 = x1;
this.y1 = y1;
this.cpX = cpX;
this.cpY = cpY;
this.x2 = x2;
this.y2 = y2;
this.pointCache = this.calcPoints();
}
calcX(t) { return (1 - t) * (1 - t) * this.x1 + 2 * (1 - t) * t * this.cpX + t * t * this.x2; }
calcY(t) { return (1 - t) * (1 - t) * this.y1 + 2 * (1 - t) * t * this.cpY + t * t * this.y2; }
calcPoints() {
const step = 0.001, segments = [];
for (let i = 0; i <= 1 - step; i += step) {
let dx = this.calcX(i) - this.calcX(i + step);
let dy = this.calcY(i) - this.calcY(i + step);
segments.push(Math.sqrt(dx * dx + dy * dy));
}
const len = segments.reduce((a, c) => a + c, 0);
let result = [], l = 0, co = 0;
for (let i = 0; i < segments.length; i++) {
l += segments[i];
co += step;
result.push({ t: l / len, co });
}
return result;
}
draw() {
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.quadraticCurveTo(this.cpX, this.cpY, this.x2, this.y2);
ctx.stroke();
}
tick(amount = 0.001) {
this.f = this.f < 1 ? this.f + amount : 0;
}
}
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
}
let a = new BezierCurve(25, 25, 80, 250, 100, 50);
let b = new BezierCurve(225, 25, 280, 250, 300, 50);
function draw(curve, fraction) {
let x = curve.calcX(fraction);
let y = curve.calcY(fraction);
curve.draw();
drawCircle(x, y, 5);
curve.tick();
}
// Inefficient but using this instead of binary search just to save space in code example
function findClosestNumInArray(arr, goal) {
return arr.reduce((prev, cur) => Math.abs(cur.t - goal) < Math.abs(prev.t - goal) ? cur : prev);
}
function drawLoop(elapsed) {
c.width = 600;
c.height = 600;
draw(a, a.f);
let closest = findClosestNumInArray(b.pointCache, b.f).co;
draw(b, closest);
requestAnimationFrame(drawLoop);
}
drawLoop(0);
<canvas></canvas>
Okay, so, to explain what's going on: if you hit Run code snippet you'll see that there are two curves, which I'll refer to as a (left one) and b (right one).
You may notice that the dot moving along a's curve starts off fast, then slows down around the curve, and then speeds up again. This is despite the fractional part being incremented by a constant 0.001 each frame.
The dot for b on the other hand moves at a constant velocity throughout the entire iteration. This is because for b I use the pointCache mapping that I precompute for the curve. This function calcPoints generates a mapping such that the input fractional component t is associated with the "proper" actual percentage along the curve co.
Anyways, this all works, but my issue is that the precomputation calcPoints is expensive, and referencing a lookup table to find the actual fractional part along the line for a percentage is inexact and requires significant memory usage. I was wondering if there was a better way.
What I'm looking for is a way to do something like curve.calcX(0.5) and actually get the 50% mark along the curve. Because currently the existing equation does not do this, and I instead have to do this costly workaround.
We can try to modify your method to be a bit more efficient. It is still not the exact solution you hope for but it might do the trick.
Instead of repeatedly evaluating the Bézier curve at parameter values differing by 0.001 (where you do not reuse the computation from the previous step) we could use the idea of subdivision. Do you know De Casteljau's algorithm? It not only evaluates the Bézier curve at a given parameter t, it also provides you with means to subdivide the curve in two: one Bézier curve that equals the original curve on the interval [0, t] and another one that equals the original curve on [t, 1]. Their control polygons are a much better approximation of the curves than the original control polygon.
So, you would proceed as follows:
Use De Casteljau's algorithm to subdivide the curve at t=0.5.
Use De Casteljau's algorithm to subdivide the first segment at t=0.25.
Use De Casteljau's algorithm to subdivide the second segment at t=0.75.
Proceed recursively in the same manner until prescribed depth. This depends on the precision you would like to achieve.
The control polygons of these segments will be your (piecewise linear) approximation of the original Bézier curve. Either use them to precompute the parameters as you have done so far; or plot this approximation directly instead of using quadraticCurveTo with the original curve. Generating this approximation should be much faster than your procedure.
You can read more about this idea in Sections 3.3, 3.4 and 3.5 of Prautzsch, Boehm and Paluszny: Bézier and B-spline techniques. They also provide an estimate how quickly does this procedure converge to the original curve.
Not totally sure this will work, but are you aware of Horner's Scheme for plotting Bezier points?
/***************************************************************************
//
// This routine plots out a bezier curve, with multiple calls to hornbez()
//
//***************************************************************************
function bezierCalculate(context, NumberOfDots, color, dotSize) {
// This routine uses Horner's Scheme to draw entire Bezier Line...
for (var t = 0.0; t < 1.0001; t = t + 1.0 / NumberOfDots) {
xTemp = hornbez(numberOfControlPoints - 1, "x", t);
yTemp = hornbez(numberOfControlPoints - 1, "y", t);
drawDot(context, xTemp, yTemp, dotSize, color);
}
}
//***************************************************************************
//
// This routine uses Horner's scheme to compute one coordinate
// value of a Bezier curve. Has to be called
// for each coordinate (x,y, and/or z) of a control polygon.
// See Farin, pg 59,60. Note: This technique is also called
// "nested multiplication".
// Input: degree: degree of curve.
// coeff: array with coefficients of curve.
// t: parameter value.
// Output: coordinate value.
//
//***************************************************************************
function hornbez(degree, xORy, t) {
var i;
var n_choose_i; /* shouldn't be too large! */
var fact, t1, aux;
t1 = 1 - t;
fact = 1;
n_choose_i = 1;
var aux = FrameControlPt[0][xORy] * t1;
/* starting the evaluation loop */
for (i = 1; i < degree; i++) {
fact = fact * t;
n_choose_i = n_choose_i * (degree - i + 1) / i; /* always int! */
aux = (aux + fact * n_choose_i * FrameControlPt[i][xORy]) * t1;
}
aux = aux + fact * t * FrameControlPt[degree][xORy];
return aux;
}
Not sure exactly where you are going here, but here's a reference of something I wrote a while ago... And for the contents of just the Bezier iframe, see this... My implied question? Is Bezier the right curve for you?

Getting X and Y coordinates from tile ID

I'm stumped on what is probably some pretty simple math. I need to get the X and Y coordinates from each tiles referenced ID. The grid below shows the order the ids are generated in. Each tile has a width and height of 32. Number ones x & y would be equal to (0,0). This is for a game I'm starting to make with canvas using a tileset.
1|2|3
4|5|6
7|8|9
So far for X, I've come up with...
(n % 3) * 32 - 32 // 3 is the width of the source image divded by 32
And for Y...
(n / 3) * 32
This is obviously wrong, but It's the closest I've come, and I don't think I'm too far off from the actual formula.
Here is my actual code so far:
function startGame() {
const canvas = document.getElementById("rpg");
const ctx = canvas.getContext("2d");
const tileSet = new Image();
tileSet.src = "dungeon_tiles.png";
let map = {
cols: 10,
rows: 10,
tsize: 32,
getTileX: function(counter, tiles) {
return ((tiles[counter] - 1) % 64) * 32;
},
getTileY: function(counter, tiles) {
return ((tiles[counter] - 1) / 64) * 32;
}
};
let counter = 0;
tileSet.onload = function() {
for (let c = 0; c < map.cols; c++) {
for (let r = 0; r < map.rows; r++) {
let x = map.getTileX(counter, mapObj.layers[0].data); // mapObj.layers[0].data is the array of values
let y = map.getTileY(counter, mapObj.layers[0].data);
counter += 1;
ctx.drawImage(
tileSet, // image
x, // source x
y, // source y
map.tsize, // source width
map.tsize, // source height
r * map.tsize, // target x
c * map.tsize, // target y
map.tsize, // target width
map.tsize // target height
);
}
}
};
}
If 1 is (0,0) and each tile is 32*32, then finding your horizontal position is a simple 32*(t-1) where t is your tile number. t-1 because your tiles start from 1 instead of 0. Now, you have 3 tiles per row so you want to reset every 3, so the final formula for your x is 32*((t-1)%3).
For the vertical position it's almost the same, but you want to increase your position by 32 only once every 3 tiles, so this is your y: 32*floor((t-1)/3).
floor((t-1)/3) is simply integer division since the numbers are always positive.
If I understand this correctly, you want to get the 1|2|3 values based on x, y correct? You can do something like this:
((y * total # of rows) + x) + 1
This would convert the 2D x, y index to a single index which is, as you stated, 1|2|3. This formula is based on your example where count starts at 1 and not 0. If you want to convert it to 0 base, just remove the + 1.
If you have the width and height, or probably location of input/character, you can have a GetX(int posX) and GetY(int posY) to get the x and y based on the position. Once you have converted the position to x, y values, use the formula above.
int GetX(int posX)
{
return (posX / 32);
}
int GetY(int posY)
{
return (posY / 32);
}
int GetIndex(int posX, int posY)
{
return ((GetY(posY) / totalRows) + GetX(posX)) + 1;
}

Get coords of the intersection of the line in the plane

I have a canvas with this params:
width = 400, height = 400
and have a line passing through the point cursor[x1,y1] at an angle Q (in degree)
I need get all coords of the intersection of the line in the plane and write it to array. Now i use this equation: y - y1 = k * (x - x1)
to check all point I use this code:
var rad = Q * Math.PI/180;
for (ctrY = 0; ctrY < 400; ctrY += 1) {
for (ctrX = 0; ctrX < 400; ctrX += 1) {
if ( (ctrY - cursor.y) ===
~~(Math.tan(rad) * (ctrX - cursor.x)) ) {
z.push([ctrX, ctrY]);
}
}
}
For example when 0 < Q < 90 and cursor[x1,y1] = [200,200] z.length = 0 and it's not correct.
Where i'm wrong? Maybe there is a more convenient algorithm?
P.S. Sorry for my english
Seems you need line rastering algorithm. Consider Bresenham algorithm.
You can also look at DDA algorithm
I imagine an algorithm like this. (I only consider the case when 0 < Q < 90). First I will want to calculate the points where the line will intersect the Ox and Oy axes, considering the origin (0,0) point the upper left corner and if we imagine that the negative x and y values are respectively to the left and to the top of this point. Let x2 and y2 be the values where the line will intersect Ox and Oy. We want to calculate these values. We now have a system with 2 unknown variables (x2 and y2): Math.tan(rad) = (y1 -y2)/x1 and Math.tan(rad) = y1/(x1-x2). We can deduct these equations by drawing the line on the coordinate system and analyzing a bit. If we solve the system of equations we find something like: x2 = (x1*y1 -x1 * x1 * Math.tan(rad)/(2 * y1-x1)) and y2= y1- x1 * Math.tan(rad) (These need to be verified, I haven't double checked my calculus). A linear equation can be defined by the formula y = a*x + b and in our case a = x2 and b = y2. We can then calculate the points like this:
for (xIdx = 0; xIdx < 400; xIdx += 1) {
var ctrX = xIdx;
var ctrY = x2 * ctrX + y2 //todo: replace with the respective calculated variables x2 and y2(we could also define two functions in js) and proper rounding
z.push([ctrX, ctrY]);
}
I'm not sure if I'm 100% accurate but I hope you understand my idea.

hough transform - javascript - node.js

So, i'm trying to implement hough transform, this version is 1-dimensional (its for all dims reduced to 1 dim optimization) version based on the minor properties.
Enclosed is my code, with a sample image... input and output.
Obvious question is what am i doing wrong. I've tripled check my logic and code and it looks good also my parameters. But obviously i'm missing on something.
Notice that the red pixels are supposed to be ellipses centers , while the blue pixels are edges to be removed (belong to the ellipse that conform to the mathematical equations).
also, i'm not interested in openCV / matlab / ocatve / etc.. usage (nothing against them).
Thank you very much!
var fs = require("fs"),
Canvas = require("canvas"),
Image = Canvas.Image;
var LEAST_REQUIRED_DISTANCE = 40, // LEAST required distance between 2 points , lets say smallest ellipse minor
LEAST_REQUIRED_ELLIPSES = 6, // number of found ellipse
arr_accum = [],
arr_edges = [],
edges_canvas,
xy,
x1y1,
x2y2,
x0,
y0,
a,
alpha,
d,
b,
max_votes,
cos_tau,
sin_tau_sqr,
f,
new_x0,
new_y0,
any_minor_dist,
max_minor,
i,
found_minor_in_accum,
arr_edges_len,
hough_file = 'sample_me2.jpg',
edges_canvas = drawImgToCanvasSync(hough_file); // make sure everything is black and white!
arr_edges = getEdgesArr(edges_canvas);
arr_edges_len = arr_edges.length;
var hough_canvas_img_data = edges_canvas.getContext('2d').getImageData(0, 0, edges_canvas.width,edges_canvas.height);
for(x1y1 = 0; x1y1 < arr_edges_len ; x1y1++){
if (arr_edges[x1y1].x === -1) { continue; }
for(x2y2 = 0 ; x2y2 < arr_edges_len; x2y2++){
if ((arr_edges[x2y2].x === -1) ||
(arr_edges[x2y2].x === arr_edges[x1y1].x && arr_edges[x2y2].y === arr_edges[x1y1].y)) { continue; }
if (distance(arr_edges[x1y1],arr_edges[x2y2]) > LEAST_REQUIRED_DISTANCE){
x0 = (arr_edges[x1y1].x + arr_edges[x2y2].x) / 2;
y0 = (arr_edges[x1y1].y + arr_edges[x2y2].y) / 2;
a = Math.sqrt((arr_edges[x1y1].x - arr_edges[x2y2].x) * (arr_edges[x1y1].x - arr_edges[x2y2].x) + (arr_edges[x1y1].y - arr_edges[x2y2].y) * (arr_edges[x1y1].y - arr_edges[x2y2].y)) / 2;
alpha = Math.atan((arr_edges[x2y2].y - arr_edges[x1y1].y) / (arr_edges[x2y2].x - arr_edges[x1y1].x));
for(xy = 0 ; xy < arr_edges_len; xy++){
if ((arr_edges[xy].x === -1) ||
(arr_edges[xy].x === arr_edges[x2y2].x && arr_edges[xy].y === arr_edges[x2y2].y) ||
(arr_edges[xy].x === arr_edges[x1y1].x && arr_edges[xy].y === arr_edges[x1y1].y)) { continue; }
d = distance({x: x0, y: y0},arr_edges[xy]);
if (d > LEAST_REQUIRED_DISTANCE){
f = distance(arr_edges[xy],arr_edges[x2y2]); // focus
cos_tau = (a * a + d * d - f * f) / (2 * a * d);
sin_tau_sqr = (1 - cos_tau * cos_tau);//Math.sqrt(1 - cos_tau * cos_tau); // getting sin out of cos
b = (a * a * d * d * sin_tau_sqr ) / (a * a - d * d * cos_tau * cos_tau);
b = Math.sqrt(b);
b = parseInt(b.toFixed(0));
d = parseInt(d.toFixed(0));
if (b > 0){
found_minor_in_accum = arr_accum.hasOwnProperty(b);
if (!found_minor_in_accum){
arr_accum[b] = {f: f, cos_tau: cos_tau, sin_tau_sqr: sin_tau_sqr, b: b, d: d, xy: xy, xy_point: JSON.stringify(arr_edges[xy]), x0: x0, y0: y0, accum: 0};
}
else{
arr_accum[b].accum++;
}
}// b
}// if2 - LEAST_REQUIRED_DISTANCE
}// for xy
max_votes = getMaxMinor(arr_accum);
// ONE ellipse has been detected
if (max_votes != null &&
(max_votes.max_votes > LEAST_REQUIRED_ELLIPSES)){
// output ellipse details
new_x0 = parseInt(arr_accum[max_votes.index].x0.toFixed(0)),
new_y0 = parseInt(arr_accum[max_votes.index].y0.toFixed(0));
setPixel(hough_canvas_img_data,new_x0,new_y0,255,0,0,255); // Red centers
// remove the pixels on the detected ellipse from edge pixel array
for (i=0; i < arr_edges.length; i++){
any_minor_dist = distance({x:new_x0, y: new_y0}, arr_edges[i]);
any_minor_dist = parseInt(any_minor_dist.toFixed(0));
max_minor = b;//Math.max(b,arr_accum[max_votes.index].d); // between the max and the min
// coloring in blue the edges we don't need
if (any_minor_dist <= max_minor){
setPixel(hough_canvas_img_data,arr_edges[i].x,arr_edges[i].y,0,0,255,255);
arr_edges[i] = {x: -1, y: -1};
}// if
}// for
}// if - LEAST_REQUIRED_ELLIPSES
// clear accumulated array
arr_accum = [];
}// if1 - LEAST_REQUIRED_DISTANCE
}// for x2y2
}// for xy
edges_canvas.getContext('2d').putImageData(hough_canvas_img_data, 0, 0);
writeCanvasToFile(edges_canvas, __dirname + '/hough.jpg', function() {
});
function getMaxMinor(accum_in){
var max_votes = -1,
max_votes_idx,
i,
accum_len = accum_in.length;
for(i in accum_in){
if (accum_in[i].accum > max_votes){
max_votes = accum_in[i].accum;
max_votes_idx = i;
} // if
}
if (max_votes > 0){
return {max_votes: max_votes, index: max_votes_idx};
}
return null;
}
function distance(point_a,point_b){
return Math.sqrt((point_a.x - point_b.x) * (point_a.x - point_b.x) + (point_a.y - point_b.y) * (point_a.y - point_b.y));
}
function getEdgesArr(canvas_in){
var x,
y,
width = canvas_in.width,
height = canvas_in.height,
pixel,
edges = [],
ctx = canvas_in.getContext('2d'),
img_data = ctx.getImageData(0, 0, width, height);
for(x = 0; x < width; x++){
for(y = 0; y < height; y++){
pixel = getPixel(img_data, x,y);
if (pixel.r !== 0 &&
pixel.g !== 0 &&
pixel.b !== 0 ){
edges.push({x: x, y: y});
}
} // for
}// for
return edges
} // getEdgesArr
function drawImgToCanvasSync(file) {
var data = fs.readFileSync(file)
var canvas = dataToCanvas(data);
return canvas;
}
function dataToCanvas(imagedata) {
img = new Canvas.Image();
img.src = new Buffer(imagedata, 'binary');
var canvas = new Canvas(img.width, img.height);
var ctx = canvas.getContext('2d');
ctx.patternQuality = "best";
ctx.drawImage(img, 0, 0, img.width, img.height,
0, 0, img.width, img.height);
return canvas;
}
function writeCanvasToFile(canvas, file, callback) {
var out = fs.createWriteStream(file)
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
});
stream.on('end', function() {
callback();
});
}
function setPixel(imageData, x, y, r, g, b, a) {
index = (x + y * imageData.width) * 4;
imageData.data[index+0] = r;
imageData.data[index+1] = g;
imageData.data[index+2] = b;
imageData.data[index+3] = a;
}
function getPixel(imageData, x, y) {
index = (x + y * imageData.width) * 4;
return {
r: imageData.data[index+0],
g: imageData.data[index+1],
b: imageData.data[index+2],
a: imageData.data[index+3]
}
}
It seems you try to implement the algorithm of Yonghong Xie; Qiang Ji (2002). A new efficient ellipse detection method 2. p. 957.
Ellipse removal suffers from several bugs
In your code, you perform the removal of found ellipse (step 12 of the original paper's algorithm) by resetting coordinates to {-1, -1}.
You need to add:
`if (arr_edges[x1y1].x === -1) break;`
at the end of the x2y2 block. Otherwise, the loop will consider -1, -1 as a white point.
More importantly, your algorithm consists in erasing every point which distance to the center is smaller than b. b supposedly is the minor axis half-length (per the original algorithm). But in your code, variable b actually is the latest (and not most frequent) half-length, and you erase points with a distance lower than b (instead of greater, since it's the minor axis). In other words, you clear all points inside a circle with a distance lower than latest computed axis.
Your sample image can actually be processed with a clearing of all points inside a circle with a distance lower than selected major axis with:
max_minor = arr_accum[max_votes.index].d;
Indeed, you don't have overlapping ellipses and they are spread enough. Please consider a better algorithm for overlapping or closer ellipses.
The algorithm mixes major and minor axes
Step 6 of the paper reads:
For each third pixel (x, y), if the distance between (x, y) and (x0,
y0) is greater than the required least distance for a pair of pixels
to be considered then carry out the following steps from (7) to (9).
This clearly is an approximation. If you do so, you will end up considering points further than the minor axis half length, and eventually on the major axis (with axes swapped). You should make sure the distance between the considered point and the tested ellipse center is smaller than currently considered major axis half-length (condition should be d <= a). This will help with the ellipse erasing part of the algorithm.
Also, if you also compare with the least distance for a pair of pixels, as per the original paper, 40 is too large for the smaller ellipse in your picture. The comment in your code is wrong, it should be at maximum half the smallest ellipse minor axis half-length.
LEAST_REQUIRED_ELLIPSES is too small
This parameter is also misnamed. It is the minimum number of votes an ellipse should get to be considered valid. Each vote corresponds to a pixel. So a value of 6 means that only 6+2 pixels make an ellipse. Since pixels coordinates are integers and you have more than 1 ellipse in your picture, the algorithm might detect ellipses that are not, and eventually clear edges (especially when combined with the buggy ellipse erasing algorithm). Based on tests, a value of 100 will find four of the five ellipses of your picture, while 80 will find them all. Smaller values will not find the proper centers of the ellipses.
Sample image is not black & white
Despite the comment, sample image is not exactly black and white. You should convert it or apply some threshold (e.g. RGB values greater than 10 instead of simply different form 0).
Diff of minimum changes to make it work is available here:
https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions
Finally, please note that parseInt(x.toFixed(0)) could be rewritten Math.floor(x), and you probably want to not truncate all floats like this, but rather round them, and proceed where needed: the algorithm to erase the ellipse from the picture would benefit from non truncated values for the center coordinates. This code definitely could be improved further, for example it currently computes the distance between points x1y1 and x2y2 twice.

How come my lines aren't matching up?

EDIT: So apparently, PI is finite in JavaScript (which makes sense). But that leaves me with a major problem. What's the next best way to calculate the angles I need?
Alright, first, my code:
http://jsfiddle.net/joshlalonde/vtfyj/34/
I'm drawing cubes that open up to a 120 degree angle.
So the coordinates are calculated based on (h)eight and theta (120).
On line 46, I have a for loop that contains a nested for loop used for creating rows/columns.
It's somewhat subtle, but I noticed that the lines aren't matching up exactly. The code for figuring out each cubes position is on line 49. One of the things in the first parameter (my x value) for the origin of the cube is off. Can anyone help figure out what it is?
var cube = new Cube(
origin.x + (j * -w * (Math.PI)) +
(i * w * (Math.PI))
, origin.y + j * (h / 2) +
i * (h / 2) +
(-k*h), h);
Sorry if that's confusing. I,j, and k refer to the variable being incremented by the for loops. So basically, a three dimensional for loop.
I think the problem lies with Math.PI.
The width isn't the problem, or so I believe. I originally used 3.2 (which I somehow guessed and it seemed to line up pretty good. But I have no clue what the magical number is). I'm guessing it has to do with the angle being converted to Radians, but I don't understand why Math.PI/180 isn't the solution. I tried multiple things. 60 (in degrees) * Math.PI/180 doesn't work. What is it for?
EDIT: It might just be a JavaScript related math problem. The math is theoretically correct but can't be calculated correctly. I'll accept the imperfection to spare myself from re-writing code in unorthodox manners. I can tell it would take a lot to circumvent using trig math.
There are 2 problems...
Change line 35 to var w=h*Math.sin(30);. The 30 here matches the this.theta / 4 in the Cube getWidthmethod since this.theta equals 120.
Use the following code to generate the position of your new cube. You don't need Math.Pi. You needed to use both the cube width and height in your calculation.
var cube = new Cube(
origin.x+ -j*w - i*h,
origin.y + -j*w/2 + i*h/2,
h);
Alright I found the solution!
It's really simple - I was using degrees instead of radians.
function Cube(x, y, h) {
this.x = x
this.y = y
this.h = h;
this.theta = 120*Math.PI/180;
this.getWidth = function () {
return (this.h * Math.sin(this.theta / 2));
};
this.width = this.getWidth();
this.getCorner = function () {
return (this.h / 2);
};
this.corner = this.getCorner();
}
So apparently Javascript trig functions use Radians, so that's one problem.
Next fix I made was to the offset of each point in the cube. It doesn't need one! (o.O idk why. But whatever it works. I left the old code just in case I discover why later on).
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw a black canvas
var h = 32;
var width = Math.sin(60*Math.PI/180);
var w = h*width;
var row = 9; // column and row will always be same (to make cube)
var column = row;
var area = row * column;
var height = 1;
row--;
column--;
height--;
var origin = {
x: canvas.width / 2,
y: (canvas.height / 2) - (h * column/2) + height*h
};
var offset = Math.sqrt(3)/2;
offset = 1;
for (var i = 0; i <= row; i++) {
for (var j = 0; j <= column; j++) {
for (var k = 0; k <= height; k++) {
var cube = new Cube(
origin.x + (j * -w * offset) +
(i * w * offset)
, origin.y + (j * (h / 2) * offset) +
(i * (h / 2) * offset) +
(-k*h*offset), h);
var cubes = {};
cubes[i+j+k] = cube; // Store to array
if (j == column) {
drawCube(2, cube);
}
if (i == row) {
drawCube(1, cube);
}
if (k == height) {
drawCube(0,cube);
}
}
}
}
}
See the full Jsfiddle here: http://jsfiddle.net/joshlalonde/vtfyj/41/

Categories

Resources