Get coords of the intersection of the line in the plane - javascript

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.

Related

Loop construction in Processing artistic rendition

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.

Improve discontinuity detection algorithm

I'm trying to create an algorithm that detects discontinuities (like vertical asymptotes) within functions between an interval for the purpose of plotting graphs without these discontinuous connecting lines. Also, I only want to evaluate within the interval so bracketing methods like bisection seems good for that.
EDIT
https://en.wikipedia.org/wiki/Classification_of_discontinuities
I realize now there are a few different kinds of discontinuities. I'm mostly interested in jump discontinuities for graphical purposes.
I'm using a bisection method as I've noticed that discontinuities occur where the slope tends to infinity or becomes vertical, so why not narrow in on those sections where the slope keeps increasing and getting steeper and steeper. The point where the slope is a vertical line, that's where the discontinuity exists.
Approach
Currently, my approach is as follows. If you subdivide the interval using a midpoint into 2 sections and compare which section has the steepest slope, then that section with the steepest slope becomes the new subinterval for the next evaluation.
Termination
This repeats until it converges by either slope becoming undefined (reaching infinity) or the left side or the right side of the interval equaling the middle (I think this is because the floating-point decimal runs out of precision and cannot divide any further)
(1.5707963267948966 + 1.5707963267948968) * .5 = 1.5707963267948966
Example
function - floor(x)
(blue = start leftX and rightX, purple = midpoint, green = 2nd iteration midpoints points, red = slope lines per iteration)
As you can see from the image, each bisection narrows into the discontinuity and the slope keeps getting steeper until it becomes a vertical line at the discontinuity point at x=1.
To my surprise this approach seems to work for step functions like floor(x) and tan(x), but it's not that great for 1/x as it takes too many iterations (I'm thinking of creating a hybrid method where I use either illinois or ridders method on the inverse of 1/x as it those tend to find the root in just one iteration).
Javascript Code
/* Math function to test on */
function fn(x) {
//return (Math.pow(Math.tan(x), 3));
return 1/x;
//return Math.floor(x);
//return x*((x-1-0.001)/(x-1));
}
function slope(x1, y1, x2, y2) {
return (y2 - y1) / (x2 - x1);
}
function findDiscontinuity(leftX, rightX, fn) {
while (true) {
let leftY = fn(leftX);
let rightY = fn(rightX);
let middleX = (leftX + rightX) / 2;
let middleY = fn(middleX);
let leftSlope = Math.abs(slope(leftX, leftY, middleX, middleY));
let rightSlope = Math.abs(slope(middleX, middleY, rightX, rightY));
if (!isFinite(leftSlope) || !isFinite(rightSlope)) return middleX;
if (middleX === leftX || middleX === rightX) return middleX;
if (leftSlope > rightSlope) {
rightX = middleX;
rightY = middleY;
} else {
leftX = middleX;
leftY = middleY;
}
}
}
Problem 1 - Improving detection
For the function x*((x-1-0.001)/(x-1)), the current algorithm has a hard time detecting the discontinuity at x=1 unless I make the interval really small. As an alternative, I could also add most subdivisions but I think the real problem is using slopes as they trick the algorithm into choosing the incorrect subinterval (as demonstrated in the image below), so this approach is not robust enough. Maybe there are some statistical methods that can help determine a more probable interval to select. Maybe something like least squares for measuring the differences and maybe applying weights or biases!
But I don't want the calculations to get too heavy and 5 points of evaluation are the max I would go with per iteration.
EDIT
After looking at problem 1 again, where it selects the wrong (left-hand side) subinterval. I noticed that the only difference between the subintervals was the green midpoint distance from their slope line. So taking inspiration from linear regression, I get the squared distance from the slope line to the midpoints [a, fa] and [b, fb] corresponding to their (left/right) subintervals. And which subinterval has the greatest change/deviation is the one chosen for further subdivision, that is, the greater of the two residuals.
This further improvement resolves problem 1. Although, it now takes around 593 iterations to find the discontinuity for 1/x. So I've created a hybrid function that uses ridders method to find the roots quicker for some functions and then fallback to this new approach. I have given up on slopes as they don't provide enough accurate information.
Problem 2 - Jump Threshold
I'm not sure how to incorporate a jump threshold and what to use for that calculation, don't think slopes would help.
Also, if the line thickness for the graph is 2px and 2 lines of a step function were on top of each other then you wouldn't be able to see the gap of 2px between those lines. So the minimum jump gap would be calculated as such
jumpThreshold = height / (ymax-ymin) = cartesian distance per pixel
minJumpGap = jumpThreshold * 2
But I don't know where to go from here! And once again, maybe there are statistical methods that can help to determine the change in function so that the algorithm can terminate quickly if there's no indication of a discontinuity.
Overall, any help or advice in improving what I got already would be much appreciated!
EDIT
As the above images explains, the more divergent the midpoints are the greater the need for more subdivisions for further inspection for that subinterval. While, if the points mostly follow a straight line trend where the midpoints barely deviate then should exit early. So now it makes sense to use the jumpThreshold in this context.
Maybe there's further analysis that could be done like measuring the curvature of the points in the interval to see whether to terminate early and further optimize this method. Zig zag points or sudden dips would be the most promising. And maybe after a certain number of intervals, keep widening the jumpThreshold as for a discontinuity you expect the residual distance to rapidly increase towards infinity!
Updated code
let ymax = 5, ymin = -5; /* just for example */
let height = 500; /* 500px screen height */
let jumpThreshold = Math.pow(.5 * (ymax - ymin) / height, 2); /* fraction(half) of a pixel! */
/* Math function to test on */
function fn(x) {
//return (Math.pow(Math.tan(x), 3));
return 1 / x;
//return Math.floor(x);
//return x * ((x - 1 - 0.001) / (x - 1));
//return x*x;
}
function findDiscontinuity(leftX, rightX, jumpThreshold, fn) {
/* try 5 interations of ridders method */
/* usually this approach can find the exact reciprocal root of a discountinuity
* in 1 iteration for functions like 1/x compared to the bisection method below */
let iterations = 5;
let root = inverseRidderMethod(leftX, rightX, iterations, fn);
let limit = fn(root);
if (Math.abs(limit) > 1e+16) {
if (root >= leftX && root <= rightX) return root;
return NaN;
}
root = discontinuityBisection(leftX, rightX, jumpThreshold, fn);
return root;
}
function discontinuityBisection(leftX, rightX, jumpThreshold, fn) {
while (true) {
let leftY = fn(leftX);
let rightY = fn(rightX);
let middleX = (leftX + rightX) * .5;
let middleY = fn(middleX);
let a = (leftX + middleX) * .5;
let fa = fn(a);
let b = (middleX + rightX) * .5;
let fb = fn(b);
let leftResidual = Math.pow(fa - (leftY + middleY) * .5, 2);
let rightResidual = Math.pow(fb - (middleY + rightY) * .5, 2);
/* if both subinterval midpoints (fa,fb) barely deviate from their slope lines
* i.e. they're under the jumpThreshold, then return NaN,
* indicating no discountinuity with the current threshold,
* both subintervals are mostly straight */
if (leftResidual < jumpThreshold && rightResidual < jumpThreshold) return NaN;
if (!isFinite(fa) || a === leftX || a === middleX) return a;
if (!isFinite(fb) || b === middleX || b === rightX) return b;
if (leftResidual > rightResidual) {
/* left hand-side subinterval */
rightX = middleX;
middleX = a;
} else {
/* right hand-side subinterval */
leftX = middleX;
middleX = b;
}
}
}
function inverseRidderMethod(min, max, iterations, fn) {
/* Modified version of RiddersSolver from Apache Commons Math
* http://commons.apache.org/
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
let x1 = min;
let y1 = 1 / fn(x1);
let x2 = max;
let y2 = 1 / fn(x2);
// check for zeros before verifying bracketing
if (y1 == 0) {
return min;
}
if (y2 == 0) {
return max;
}
let functionValueAccuracy = 1e-55;
let relativeAccuracy = 1e-16;
let oldx = Number.POSITIVE_INFINITY;
let i = 0;
while (i < iterations) {
// calculate the new root approximation
let x3 = 0.5 * (x1 + x2);
let y3 = 1 / fn(x3);
if (!isFinite(y3)) return NaN;
if (Math.abs(y3) <= functionValueAccuracy) {
return x3;
}
let delta = 1 - (y1 * y2) / (y3 * y3); // delta > 1 due to bracketing
let correction = (signum(y2) * signum(y3)) * (x3 - x1) / Math.sqrt(delta);
let x = x3 - correction; // correction != 0
if (!isFinite(x)) return NaN;
let y = 1 / fn(x);
// check for convergence
let tolerance = Math.max(relativeAccuracy * Math.abs(x), 1e-16);
if (Math.abs(x - oldx) <= tolerance) {
return x;
}
if (Math.abs(y) <= functionValueAccuracy) {
return x;
}
// prepare the new interval for the next iteration
// Ridders' method guarantees x1 < x < x2
if (correction > 0.0) { // x1 < x < x3
if (signum(y1) + signum(y) == 0.0) {
x2 = x;
y2 = y;
} else {
x1 = x;
x2 = x3;
y1 = y;
y2 = y3;
}
} else { // x3 < x < x2
if (signum(y2) + signum(y) == 0.0) {
x1 = x;
y1 = y;
} else {
x1 = x3;
x2 = x;
y1 = y3;
y2 = y;
}
}
oldx = x;
}
}
function signum(a) {
return (a < 0.0) ? -1.0 : ((a > 0.0) ? 1.0 : a);
}
/* TEST */
console.log(findDiscontinuity(.5, .6, jumpThreshold, fn));
Python Code
I don't mind if the solution is provided in Javascript or Python
import math
def fn(x):
try:
# return (math.pow(math.tan(x), 3))
# return 1 / x
# return math.floor(x)
return x * ((x - 1 - 0.001) / (x - 1))
except ZeroDivisionError:
return float('Inf')
def slope(x1, y1, x2, y2):
try:
return (y2 - y1) / (x2 - x1)
except ZeroDivisionError:
return float('Inf')
def find_discontinuity(leftX, rightX, fn):
while True:
leftY = fn(leftX)
rightY = fn(rightX)
middleX = (leftX + rightX) / 2
middleY = fn(middleX)
leftSlope = abs(slope(leftX, leftY, middleX, middleY))
rightSlope = abs(slope(middleX, middleY, rightX, rightY))
if not math.isfinite(leftSlope) or not math.isfinite(rightSlope):
return middleX
if middleX == leftX or middleX == rightX:
return middleX
if leftSlope > rightSlope:
rightX = middleX
rightY = middleY
else:
leftX = middleX
leftY = middleY

Line to line intersection works only on one side

I'm creating a raycasting engine on javascript using p5js and there is an issue with the line to line (raycast to wall) intersection.
I found a lot of line to line collision algorithms, including p5 collide library, but the problem appears on every one of them.
this.intersects = function (raycastStart, raycastEnd) {
var x1 = this.startPoint.x; //Start point is the first point of a line.
var y1 = this.startPoint.y;
var x2 = this.endPoint.x; //End point is the second point of a line.
var y2 = this.endPoint.y;
var x3 = raycastStart.x;
var y3 = raycastStart.y;
var x4 = raycastEnd.x;
var y4 = raycastEnd.y;
var a_dx = x2 - x1;
var a_dy = y2 - y1;
var b_dx = x4 - x3;
var b_dy = y4 - y3;
var s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / (-b_dx * a_dy + a_dx * b_dy);
var t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / (-b_dx * a_dy + a_dx * b_dy);
//Vector2 is simply class with two fields: x and y.
return (s >= 0 && s <= 1 && t >= 0 && t <= 1) ? new Vector2(x1 + t * a_dx, y1 + t * a_dy) : null;
}
The line to line collision works on one side properly, but on the other, it works incorrect, according to my y position.
this is my map.
on one side it works perfectly
but on the other, it checks collision for line segments, that are lower than my Y position
(I would comment, but don't have enough reputation to do so...)
It appears that your line collision algorithm is working. But what appears to be missing is a check to determine which raycaster-to-line intersection is closer. That is, in your working example the raycast never casts across two line segments, so there is no question about which line segment constrains your raycast. But in your non-working example, the raycaster hits 2 of your 4 segments, so you now need to determine which of the 2 intersection points is closer to the raycast start, in order to determine which line segment is closer.

How to fix map boundaries on d3 cartographic raster reprojection?

I try to use the raster reprojection of a map following this example. If I change the example kavrayskiy7 projection by the Azimuthal Equidistant projection,
var projection = d3.geo.azimuthalEquidistant()
.scale(90)
.translate([width / 2, height / 2])
.clipAngle(180 - 1e-3)
.precision(.1);
it should project the Earth onto a disc (the image of the projection map). However, the raster reprojection goes beyond that disc and fills the entire canvas with an extended picture (the inverse projection function is not injective, several x/y points on the map correspond to a single lon/lat coordinates). In the original example, this should be avoided with the line
if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; }
but for this example that does not work. I found other glitches for instance when using the Mollweide projection (two lines appear at the poles) due to the same efect.
To solve this, one way would be to fix the inverse projections so they return error or None when the x/y input is out of range. My attempt was to check if a point is on range using the forward projection of the whole sphere to obtain a SVG path with the boundary of the map, as given by this code:
var path = d3.geo.path()
.projection(projection);
var bdry = svg.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
(see for instance this example). However, I found no easy method to check whether a point [x,y] is inside a SVG closed path.
So my questions are:
Is there a bug on the inverse projections, or am I not using them correctly?
How could I find if a [x,y] point is inside the svg path, assuming that this is the best approach?
By curiosity, where is the algorithm code of the d3 path function to obtain the boundary profile of the map? I could not find it on the github repo.
Thanks.
Edit: I went through all the 44 projections in this example and I found glitches on the following 25:
Albers, Bromley, Collignon, Eckert II, Eckert IV, Eckert VI, Hammer, Hill, Goode Homolosine, Lambert cylindrical equal-area, Larrivée, Laskowski, McBryde–Thomas Flat-Polar Parabolic, McBryde–Thomas Flat-Polar Quartic, McBryde–Thomas Flat-Polar Sinusoidal, Mollweide, Natural Earth, Nell–Hammer, Polyconic, Sinu-Mollweide, van der Grinten, van der Grinten IV, Wagner IV, Wagner VII, Winkel Tripel.
I'm using a second answer only because this is a different approach to the same problem. Again, this answer is an alternative approach that tries to avoid a point in polygon solution that uses an svg outline of the projection extent.
This alternative should (I've only tried a handful) work for any projection while my other answer works only for projections projected to a disc. Secondly, this approach doesn't attempt to define the projection area to determine if a pixel should be rendered, but uses d3.projection itself.
As multiple points can return the same value with projection.invert, we can run a forward projection to verify if a pixel should be drawn.
If projection(projection.invert(point)) == point then the point is within the bounds of our projection.
Granted, there may be some precision/rounding errors in this, so some degree of tolerance could be specified.
This check fits within the for loop:
for (var y = 0, i = -1; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var p = projection.invert([x, y]), λ = p[0], φ = p[1];
var pxy = projection(p);
var tolerance = 0.5;
if ( λ > 180 || λ < -180 || φ > 90 || φ < -90 ) { i += 4; continue; }
if ( (Math.abs(pxy[0] - x) < tolerance ) && (Math.abs(pxy[1] - y) < tolerance ) ) {
var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
targetData[++i] = 255;
}
else {
i += 4;
}
}
}
As with the other answer, I built a block out of it here.
I haven't checked this answer for performance, and it seems odd that this sort of check is needed, but it might be a suitable alternative approach to the svg approach proposed in your question.
While I'm pretty sure you are using the projection.inverse function correctly,
relying on :
if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; }
to clip the projection will always fail as projection.inverse appears to always return angles within 180 degrees east/west. While there may be a way to modify the projection itself to return values greater than 180 degrees, it is likely more difficult than other approaches (And honestly, that goes well beyond any answer I can give).
Likewise, using an SVG path to represent the world outline and then using that as the basis to determine if a point should be drawn is probably complicating the matter.
Instead, assuming a circular disc, you can easily compute the radius of the disc and from there determine if a pixel should be drawn:
var edge = {};
var center = {};
edge.x = projection([180 - 1e-6, 0])[0];
edge.y = projection([180 - 1e-6, 0])[1];
center.x = width/2;
center.y = height/2;
var radius = Math.pow( Math.pow(center.x - edge.x,2) + Math.pow(center.y - edge.y,2) , 0.5 )
Using the radius of the disc, we can then compute if a pixel falls on the disc or beyond it in the for loop:
for (var y = 0, i = -1; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var p = projection.invert([x, y]), λ = p[0], φ = p[1];
if (Math.pow( Math.pow(center.x-x,2) + Math.pow(center.y-y,2), 0.5) < radius) {
if ( λ > 180 || λ < -180 || φ > 90 || φ < -90 ) { i += 4; continue; }
var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
targetData[++i] = 255;
}
else {
targetData[++i] = 0;
targetData[++i] = 0;
targetData[++i] = 0;
targetData[++i] = 0;
}
}
}
Together, these gave me:
For aesthetic effect, it might be worthwhile trimming the radius down by a certain percent. Of course for different projections this approach may be either difficult or impossible.
I've put the code into a bl.ock here (I moved it to d3 v4 in the process, partly to see if the behavior of projection.inverse is the same).
For the third part of your question, you could try d3's graticule (graticule.outline) function for some info on how d3 gets the boundary profile of a projection.

Julia Set In Javascript

I'm messing around with some Mandlebrot set stuff because I think the pictures it produces are pretty. I thought I might try to tackle the problem of drawing one in javascript to see what I could do. I looked at a couple algorithms, namely:
http://library.thinkquest.org/26242/full/progs/a2.html
Which I translated into this:
drawGraph: function(canvas,resolution,iterations,colors,coefficent){
var context = canvas.getContext('2d');
for(var m = 0; m < resolution.x; m++){
for(var n = 0; n < resolution.y; n++){
var x = m,
x2 = x*x,
y = n,
y2 = y*y;
var i;
for(i = 1; i < iterations; i++){
if(x2 + y2 > 4) break;
var new_x = x2 - y2 + coefficent.a;
var new_y = 2*x*y + coefficent.b;
x = new_x;
y = new_y;
}
var color = i % colors;
DrawUtils.drawPoint(context,m,n,color);
}
}
}
Which essentially draws a box of one color.
Then I tried this one:
http://en.wikipedia.org/wiki/Mandelbrot_set#Escape_time_algorithm
Which I translated into this:
drawGraph: function(canvas,resolution,iterations,colors,coefficent){
var context = canvas.getContext('2d');
for(var m = 0; m < resolution.x; m++){
for(var n = 0; n < resolution.y; n++){
var x = 0,
y = 0,
x0 = ((m/resolution.x) * 3.5) - 2.5,
y0 = ((n/resolution.y) * 2) - 1;
var i = 0;
while(x*x + y*y < 4 && i < iterations){
var x_temp = x*x - y*y + x0;
y = 2*x*y + y0;
x = x_temp;
i++;
}
var color = 0;
if(x*x + y*y >= 4){
color = i % colors;
}
DrawUtils.drawPoint(context,m,n,color);
}
}
}
Which produces a black box. The wording in the algorithm kind of confused me though since it said x0 and y0 scaled are factors of the pixel, but then after the algorithm, it says the coefficient c = x0 + iy0; so, does that mean I don't pass a predetermined coefficient into the function?
For most of these tests I was using the coefficient 0.25 + 0i, but I tried others that produced the exact same results.
What am I doing wrong here?
First point: you need to be clear about the difference between Julia sets and the Mandelbrot set. Both are insights into the behaviour of f(z) = z^2 + c under iteration, but from different perspectives.
For a Julia set, we fix c and make a plot of how different initial zs behave
For the Mandelbrot set, we make a plot of how the same initial z = 0 behaves for different cs.
With that addressed...
For your first code (which tries to draw the a Julia set for the c in coefficient), your translation from the BASIC in the first page you link to is not quite right. Where that has
‘ run through every point on the screen, setting
‘ m and n to the coordinates
FOR m = x_minimum TO x_maximum STEP x_resolution
FOR n = y_minimum TO y_maximum STEP y_resolution
‘ the initial z value is the current pixel,
‘ so x and y have to be set to m and n
x = m: y = n
you have
for(var m = 0; m < resolution.x; m++){
for(var n = 0; n < resolution.y; n++){
which is close, except for the crucial point that you are not taking any steps to implement STEP x_resolution. Your m is an integer that runs from 0 to resolution.x - 1 in steps of 1; and your x is set to m.
So instead of looking at the complex plane from say -2-2i to 2+2i (a decent viewport for seeing a Julia set), you are instead looking at the complex plane from 0 to resolution.x + resolution.y i, which will have at most a few pixels set in its lower-left corner.
The second code (which attempts to draw a Mandelbrot set) does have code to scale to a correct range, and I can't immediately see what's going wrong - I would debug and see if m/resolution.x is always 0, as #user973572 suggests may be the problem.
In your first example, I think you forgot to update x2 and y2 so they are always the same value. You need to update x2 and y2 before checking if the sum is greater than 4. Something like
for(i = 1; i < iterations; i++){
x2 = x*x,
y2 = y*y
if(x2 + y2 > 4) break;
which is probably wrong because I know nothing about javascript.

Categories

Resources