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.
Related
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.
The last question was deleted for being too short and vague, so this time I will try to be as verbose as possible.
I am using Javascript to implement a basic physically accurate SPH following this paper. All I want in the beginning is to get the basics working, just to get through the first algorithm.
I start with a test case of just two particles at (0.05, 0.05) and (0.06999, 0.05). Their sample radii just touch, and I am already getting weird numbers.
This is how I calculate the densities and pressures:
for (var i = 0; i < particles.length; i++)
{
var pi = particles[i];
pi.density = 0;
for (var j = 0; j < pi.neighbors.length; j++)
{
var n = pi.neighbors[j];
var pj = n.p;
var h = (pi.sampleDist + pj.sampleDist) / 2;
n.r = subv(pi.pos, pj.pos); // subv() subtracts first vector from second
n.dist = length(n.r);
n.q = n.dist/h;
pi.density += pj.mass * W(n.q, h, 2);
}
pi.pressure = pi.stiffness * (Math.pow(pi.density / pi.restDensity, 7) - 1);
}
using the smoothing kernel
function W(q, h, d)
{
var f = 0;
if (0 <= q && q < 1) f = 2/3 - sqr(q) + 1/2*cube(q); // 2/3 - q² + 1/2q³
else if (1 <= q && q < 2) f = 1/6 * cube(2 - q); // 1/6(2-q)³
return 3/Tau / Math.pow(h, d) * f;
}
These are the settings I'm using:
mass: 0.001 kg
h: 0.01 m
rest density: 1000 kg/m³
stiffness: 0.2
In the case of just two particles with these settings, each particle's density becomes
pi.density = pj.mass * W(n.q, h, 2); = 0.001 * W(1.99899, 0.01, 2) = 0.001 * 0.00000079577 = 0.00000000079...
This number makes sense to me — there is almost nothing around, so the density is almost zero.
But now I go to calculate the pressure force, which I do like this:
Here is my implementation:
var Fp = {x:0, y:0};
if (pi.density !== 0)
{
var Vp = valueGradient(pi, 'pressure');
Fp.x = -pi.mass/pi.density * Vp.x;
Fp.y = -pi.mass/pi.density * Vp.y;
}
function valueGradient(pi, A) // A is a scalar
{
var vA = {x: 0, y: 0};
for (var j = 0; j < pi.neighbors.length; j++)
{
var n = pi.neighbors[j];
var pj = n.p;
var h = (pi.sampleDist + pj.sampleDist) / 2;
var f = pj.mass / pj.density * pj[A];
var vw = VW(n, h);
vA.x += f * vw.x;
vA.y += f * vw.y;
}
return vA;
}
function VW(n, h)
{
var dw =
dW(n.q, h, 2)
/ (n.dist * h);
return {
x: dw * n.r.x,
y: dw * n.r.y };
}
function dW(q, h, d) // derivative of kernel
{
var f = 0;
if (0 <= q && q < 1) f = -2*q + 3/2*sqr(q); // -2q + 3/2q²
else if (1 <= q && q < 2) f = -1/2 * sqr(2 - q); // -1/2(2-q)²
return 3/Tau / Math.pow(h, d) * f;
}
But something is going wrong, because when the particles sample radii just touch, the repulsive force is enormous, and when they're close it goes to zero. I get why this would be happening, because given my settings there are lots of 0.000... everywhere, and with the density so tiny, dividing by all these 0.000... will give huge numbers in the end. So it makes sense why those numbers are there, and so I suspect that I'm misunderstanding something fundamental.
This feels... backwards. What mistake am I making? I keep looking at the kernel derivative...
Another thing is that the forces are not strictly symmetrical as they should be, they tend to stay diagonal until the difference in one of the coordinates is around zero, where they suddenly flip.
I feel like I'm messing up the differential equations somewhere...
A live version of this code that can be stepped through is at www.bourt.com/particles. The code in question is in simulation.js, physics.js and kernel.js.
I wrote some code in p5.js to see if i can properly make a collision detection system but when i put more than 2 squares in, squares seem to bump each other inside of other squares. I'd like to know if there's anyway to stop this plus, if you have any good pointers on how to do tidy/shorten my code id like to hear them.
My code:
var r; //later defined as an array for the squares
var num; //number of squares
function setup(){
r = [];
num = 10;
createCanvas(windowWidth,windowHeight- 4);
for(var i = 0;i < num; i++){
r[i] = new Box(random(width-40),random(height-40),40,40);
}
}
function draw(){
background(40);
for(var i = 0;i < num; i++) {
r[i].show();
for(var j = 0;j<num; j++){
//this is the if statement evaluating if the left and right of the square is touching each other. i is one square and j is the other. you see in each if statement i have the acceleration being added, this is because if it wasn't then they would be true if the squares were touching each other on any side
if(r[i].right+r[i].xa >= r[j].left && r[i].bottom >= r[j].top && r[i].top <= r[j].bottom && r[i].left + r[i].xa <= r[j].right){
r[i].xa *= -1;
r[j].xa *= -1;
}
//this is also just as confusing just read through it carefully
if(r[i].bottom + r[i].ya >= r[j].top && r[i].right >=r[j].left && r[i].left <= r[j].right && r[i].top + r[i].ya <= r[j].bottom){
r[i].ya *= -1;
r[j].ya *= -1;
}
}
}
}
function Box(x, y, wid, hei){
this.x = x;//input for square shape
this.y = y;//ditto
this.width = wid;//ditto
this.height= hei;//ditto
this.xa = random(2,5);//xa is the x acceleration
this.ya = random(2,5);//ya is the y acceleration
this.left;
this.right;
this.top;
this.bottom;
this.show = function(){
this.left = this.x; //i define left,right,top,bottom in show function so they get updated
this.right = this.x +this.width;
this.top = this.y;
this.bottom = this.y +this.height;
push();
fill(255);
noStroke();
rect(this.x,this.y,this.width,this.height);
pop();//push pop just in case i want to change square colors individually in the future
this.x += this.xa;//adding acceleration to the squares
this.y += this.ya;//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if(this.x > width-this.width||this.x <0){//bouncing off the right and left wall
this.xa *= -1;
if(this.x > width/2){// making sure if the square spawns or glitches on the other side of the wall it doesn't get stuck, this checks which side the square is on when it touches the wall then moves it directly on the wall
this.x = width-this.width;
}else{
this.x = 0;
}
}
if(this.y > height-this.height||this.y <0){// same as above but for the y axis
this.ya *= -1;
if(this.y > height/2){
this.y = height-this.height;
}else{
this.y = 0;
}
}
}
}
function windowResized(){
createCanvas(windowWidth,windowHeight- 4);//window resizing adjustment
}
you can view it using this.
just copy and paste.
The solution to the unsolvable
Sorry no such thing
Collision solutions are not easy when you have many moving objects in the scene.
Your immediate problem
Your problem if mainly because you are making an assumption on the box's direction of travel when they collide. You multiply the direction by -1 to reverse direction.
All good for 2 objects, but add a 3rd and you will end up with the 3 coming together. Each in turn you change the direction, box1 hits box2 both move away from each other, then in the same frame box1 hits box3 and now box1 and box3 are moving apart.Your speeds are constant so after a three way collision there will always be 2 boxes traveling in the same direction but overlapping.
The overlapping boxes on the next frame detect the overlap and both reverse direction, as they are already traveling in the same direction the direction switch does not help them move apart.
A step forward
Well a step apart, The following modification to the code just ensures that when possible a collision results in the box move away from each other.
function draw() {
background(40);
for (var i = 0; i < num; i++) {
const bx1 = r[i];
r[i].show();
for (var j = 0; j < num; j++) {
if (j !== i) {
// t for top, b for bottom, r for right and l for left. 1 for first box 2 for second
// bx for box
const bx2 = r[j];
const t1 = bx1.top + bx1.ya;
const b1 = bx1.bottom + bx1.ya;
const l1 = bx1.left + bx1.xa;
const r1 = bx1.right + bx1.xa;
const t2 = bx2.top + bx2.ya;
const b2 = bx2.bottom + bx2.ya;
const l2 = bx2.left + bx2.xa;
const r2 = bx2.right + bx2.xa;
// the or's mean that the condition will complete at the first passed clause
// If not (not over lapping) AKA is overlapping
if (!(t1 > b2 || b1 < t2 || l1 > r2 || r1 < l2)) {
if (r1 >= l2) {
bx1.xa = -Math.abs(bx1.xa);
bx2.xa = Math.abs(bx2.xa);
}
if (l1 <= r2) {
bx1.xa = Math.abs(bx1.xa);
bx2.xa = -Math.abs(bx2.xa);
}
if (b1 >= t2) {
bx1.ya = -Math.abs(bx1.ya);
bx2.ya = Math.abs(bx2.ya);
}
if (t1 <= b2) {
bx1.ya = Math.abs(bx1.ya);
bx2.ya = -Math.abs(bx2.ya);
}
}
}
}
}
}
But that only moves the problem away from overlapping, now there are many collision that are wrong as there is no test to determine the point of collision
In the above code you are trying to solve from an unsolvable position. Boxes in real life never overlap. Boxes in real life will slow down and speed up. perfectly flat sides will never collide with more than on side at a time.
To do this you will need to use integration. Its not that hard and is just a process of dividing time into smaller steps. Collide, move, check for overlap, move apart then back to collide.
Verlet integration
Also verlet integration will make it easier. Rather than store a boxes speed as a vector you store the current position and the previous position.
box.x = 10;
box.y = 10;
box.ox = 8; // the boxes old position
box.oy = 8;
You move a box as follows
sx = box.x - box.ox;
sy = box.y - box.oy;
box.ox = box.x;
box.oy = box.y;
box.x += sx; // the boxes old position
box.y += sy;
When you hit something you need to change the old position so as to give the next iteration the correct direction
if(box.y > ground){
box.y = ground - (box.y - ground); // move away from ground same dist as moved into ground
box.oy = box.y -sy;
}
Do them all in groups.
Move all at once, then test for collision at once. Dont move and test one at a time.
Verlet integration is much more forgiving as it lets speed of movement absorb some of the error. Rather than be all in position as the standard vector method does.
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.
I'm building a patient monitor simulator in JavaScript. This involves drawing parabolas (semi-circles) on an HTML canvas. Normally this wouldn't be an issue with the bezierCurveTo() function however this solution is not applicable here as I need to animate the curve pixel by pixel as demonstrated here http://www.jet5.com/ecg/. From my understanding this will require an array of all points in the curve.
My question is how can I generate this array of points from a provided width and height that I want the curve to be. Is there some sort of special command or algorithm that I can use to obtain these Cartesian coordinates. For a clearer picture of what I need please refer to the following image http://en.ecgpedia.org/wiki/File:Epi_endo_en.png.
A lecturer helped me with the following equation: y = (x - t1) x (t2 - x). Here is my code (I ave created a point object and remember this is for an HTML canvas where 0, 0 is in the top left corner):
var y = 0;
var duration = 200;
var t1 = 0;
var t2 = duration;
var x1 = 0;
var x2 = 0;
for (i = 0; i < duration; i++) {
x1 = i;
x2 = duration - i;
var ctx = canvas.getContext("2d");
y = (x1 - t1) * (t2 - x2)
if (i < duration / 2) {
y = -y;
}
data.push(new point(y));
}
While this partly worked from my understanding this equation wouldn't allow me to specify a height only the parabolas width.
Any help is greatly appreciated
Best thing to do in this kind of mathematical situation is to normalize.
Which means here, try to always go back to the case when x is between 0 and 1.
so if x is in [ t1 ; t2 ]
delta = (x-t1) / (t2 - t1).
now delta moves in [0;1] !!! Magic
In the same way, for your shape functions use normalized function that returns only in [ 0 ; 1].
The example you give in your code becomes :
function shape(x) { return (x<0.5) ? x*(1-x) : -x*(1-x) }
And the code becomes - just to be clear -
for ( x = t1; x<t2; x++ ) {
var delta = (x-t1) / (t2 - t1) ;
y = amplitude * shape(delta);
// do something with (x,y)
}
See a working jsbin here :
http://jsbin.com/wodecowibexa/1/edit?js,output
![var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
ctx.fillStyle = '#C66';
// shift for all the drawings
var plotShift = {x:0, y:200 } ;
// draw between t1 and t2 with an initial y shift.
// expects an easing function i.e. a function \[0;1\] -> \[0;1\]
// yShift should be the last computed value.
// returns the last computed value to allow chaining.
function drawEasingFunction (yShift, t1, t2, amplitude, func) {
var x=0, y=0;
var duration = t2 - t1;
for (x= t1; x < t2; x++) {
// delta is a figure between 0 and 1
var delta = (x - t1) / duration;
//
y = yShift + amplitude*func(delta);
ctx.fillRect(x+plotShift.x,y+plotShift.y,2,2);
}
return y;
}
var easingFunctions = \[
function (x) { return x} , /* line */
function (x) { return x*x} , /* line */
function (x) { return Math.sqrt(x)} , /* line */
function (x) { return Math.sin(Math.PI*x/2)} , /* sin */
function (x) { return x*(1-x)} , /* that's your function */
\];
var lastY = 0;
var currentX = 0;
var length = 50;
// demo of all functions
for (; currentX < cv.width; currentX+=length) {
// take a random function
var fnIndex = Math.floor(Math.random()*easingFunctions.length) ;
var thisEasingFunction = easingFunctions\[fnIndex\];
// take some random amplitude
var amplitude = (60 + Math.random()*10);
// randomly switch the amplitude sign.
if (Math.random() > 0.5) amplitude = -amplitude;
// draw ! (and store last value)
lastY = drawEasingFunction(lastY, currentX, currentX+length, amplitude, thisEasingFunction);
}][2]