I am trying to make simulation of the three-body problem in Processing/Java. I have created a Planet object and defined several functions to calculate the attraction, the forces, and the speeds of the Planets. The program runs without any errors. The hitch is that the calculation are returning Infinity and
NaN outputs.
I haven't found any divide by zero errors.
Why is it doing this?
float simulationTime = 0.01;
float earthMass = pow(10,24)*5.972;
Planet[] bodies = {new Planet(100,200, 0, 0,earthMass/2),
new Planet(400,500, 0, 0,earthMass/2),
new Planet(800,200, 0, 0,earthMass/2)};
int size = 500;
float G = 6.674*pow(10, -11);
float gravAttract(float mass1, float mass2, float x1, float y1, float x2, float y2) {
float force = G*mass1*mass2/pow(dist1(x1, y1, x2, y2), 2);
//println(pow(dist1(x1, y1, x2, y2), 2));
//println(force);
return force;
}
float attract(Planet one,Planet two){
float force = G*one.mass*two.mass/pow(dist1(one.xpos,one.ypos,two.xpos,two.ypos),2);
println(one.xpos,two.xpos,one.xspeed,two.xspeed);
return force;
}
float[] forceXY(int body){
float[] xy = {0,0};
for (int ii = 0; ii<bodies.length; ii = ii+1){
if (ii == body){
continue;
}
else{
xy[0] = xy[0]+attract(bodies[body],bodies[ii])*
(bodies[ii].xpos-bodies[body].xpos)/dist1(bodies[body].xpos,bodies[body].ypos,bodies[ii].xpos,bodies[ii].ypos);
xy[1] = xy[1]+attract(bodies[body],bodies[ii])*
(bodies[ii].ypos-bodies[body].ypos)/dist1(bodies[body].xpos,bodies[body].ypos,bodies[ii].xpos,bodies[ii].ypos);
println(xy);
}
}
return xy;
}
float dist1(float x1,float y1,float x2,float y2){
float x = dist(x1,y1,x2,y2);
x = x*149.6*pow(10,7);
//println(x1,y1,x2,y2);
return x;
}
class Planet {
float xpos;
float ypos;
float xspeed;
float yspeed;
float mass;
Planet(float a, float b, float c, float d, float e) {
xpos = a;
ypos = b;
xspeed = c;
yspeed = d;
mass = e;
}
void update(float xforce, float yforce) {
//println("xy",xpos,ypos);
float xa = xforce/mass;
float ya = yforce/mass;
xspeed = xspeed + xa*simulationTime;
yspeed = yspeed + ya*simulationTime;
xpos = xpos + xspeed*simulationTime;
ypos = ypos + yspeed*simulationTime;
}
}
void setup() {
size(1000, 1000);
frameRate(1);
}
void draw() {
background(0);
for (int ii = 0; ii < bodies.length; ii = ii+1){
float[] asdf = new float[2];
asdf = forceXY(ii);
circle(bodies[ii].xpos,bodies[ii].ypos,10);
bodies[ii].update(asdf[0],asdf[1]);
}
}
I see you're trying to do the calculations using the real physical values. The problem with that is that the astronomical masses, distances and forces are astronomical, not just literally but also figuratively. The numbers are so big that the results of the intermediate calculations cannot be represented using the limited range of the float data type.
The solution is to change the units. For example, instead of using meters, kilograms, and seconds, measure the distance in "astronomical units" (distance between Earth and Sun), mass in multiples of Earth masses, and time in years. Or use the standard "astronomical system of units". Most importantly, this changes the value of the gravitational constant G.
If you're not too attached to making a simulation that lets you predict the real positions of the planets, it's much easier to just "wing it" and set G=1, set the masses to small numbers, and play around with the velocities. You'll see it's very easy to come up with a realistic simulation.
Related
I'm creating a Quaternion from input from a serial device. In Processing I rotate around the x-axis in the code below. My Quaternion object takes the input and uses the set function to set the values, euler angles, and normalize. Is there something wrong with the math?
I commented out rotation for z and y, but basically the object doesn't rotate around very well or is jerky compared to the x-axis, which works perfectly. What am I doing wrong in the code below?
For reference, the shape(model) line below is the loaded 3d object from a .obj file loaded in using loadShape and the shape function displays it in the draw loop.
Quaternion q = new Quaternion(s);
q.set(x, y, z, w);
q = q.Euler(q.eulerAngles);
translate(x, y);
rotateX(q.eulerAngles.x);
//rotateY(q.eulerAngles.y);
//rotateZ(q.eulerAngles.z);
shape(model);
rotateX(-q.eulerAngles.x);
translate(-x, -y);
This is part of the Quaternion class:
public class Quaternion {
PApplet s;
public float w,x,y,z;
public PVector eulerAngles;
public Quaternion(PApplet s, float x, float y, float z, float w){
this.s = s;
this.x = x;
this.y = y;
this.z = z;
this.w = w;
normalize();
}
public Quaternion(Quaternion q){
this.s = q.s;
this.w = q.w;
this.x = q.x;
this.y = q.y;
this.z = q.z;
}
public Quaternion normalize() {
float magnitude = w*w + x*x + y*y + z*z;
if(magnitude != 0.0 && magnitude != 1.0){
magnitude = 1.0f / s.sqrt(magnitude);
w *= magnitude;
x *= magnitude;
y *= magnitude;
z *= magnitude;
}
eulerAngles = setEulerAngles();
return this;
}
public Quaternion set(float x, float y, float z, float w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return normalize();
}
// Returns a rotation that rotates z degrees around
// the z axis, x degrees around the x axis, and y
// degrees around the y axis.
public Quaternion Euler(){
float roll = eulerAngles.x;
float pitch = eulerAngles.y;
float yaw = eulerAngles.z;
float cr = (float)Math.cos(roll * 0.5);
float sr = (float)Math.sin(roll * 0.5);
float cp = (float)Math.cos(pitch * 0.5);
float sp = (float)Math.sin(pitch * 0.5);
float cy = (float)Math.cos(yaw * 0.5);
float sy = (float)Math.sin(yaw * 0.5);
w = cy*cr*cp + sy*sr*sp;
x = cy*sr*cp - sy*cr*sp;
y = cy*cr*sp + sy*sr*cp;
z = sy*cr*cp - cy*sr*sp;
return normalize();
}
// set euler angle representation of
// the rotation in 3-dim PVector
private PVector setEulerAngles(){
// roll: x-axis rotation
float sinr = 2.0f * (w*x + y*z);
float cosr = 1.0f - 2.0f * (x*x + y*y);
float roll = (float)Math.atan2(sinr, cosr);
// pitch: y-axis rotation
float sinp = 2.0f * (w*y - z*x);
float pitch = 0.0f;
if(Math.abs(sinp) >= 1){
pitch = (float)Math.copySign(Math.PI/2, sinp);
} else {
pitch = (float)Math.asin(sinp);
}
// yaw: z-axis rotation
float siny = 2.0f * (w*z + x*y);
float cosy = 1.0f - 2.0f * (y*y + z*z);
float yaw = (float)Math.atan2(siny, cosy);
return new PVector(roll, pitch, yaw);
}
}
As far as I can tell, the Euler angles that you get from your method should be applied in ZYX order rather than XYZ. But anyway, do not mess around with Euler angles unless you really have to. And in this case you don't.
Instead, convert the quaternion to a rotation matrix and apply this transform using applyMatrix(). There will be no ambiguity here.
To revert a transform, do not apply the inverse transform (like you did with rotateX(-q.eulerAngles.x) and translate(-x, -y)). It is very easy to confuse the order or forget a transform during development. Instead, use pushMatrix() / popMatrix() or resetMatrix.
Btw, I find the definition of your quaternion class very confusing. Some methods return values that I would not expect to return anything (e.g. normalize()). Furthermore, I do not think that having an Euler angle representation stored with the quaternion is a good idea. And even if you think it is, I don't understand the purpose of the method Euler() since it neither has parameters, nor can you set the Euler angles from outside.
regarding the following code, can I make it better for distance() method?
It feels like it's not completely OOP with this method.. how can I change code to be better OOD for this one ?
Thanks !!
public class Line extends Shape {
private Point lineP1;
private Point lineP2;
public Line(int x1, int x2, int y1, int y2, Color myColor) {
super(x1, x2, y1, y2, myColor);
lineP1 = new Point(this.getX1(),this.getY1());
lineP2 = new Point(this.getX2(),this.getY2());
}
#Override
public void draw(Graphics g) {
g.drawLine(this.getX1(), this.getY1(), this.getX2(), this.getY2());
g.setColor(Color.GREEN);
}
#Override
public boolean contains(Point p) {
if((this.distance(lineP1, p)+this.distance(lineP2, p)) == this.distance(lineP1, lineP2))
return true;
return false;
}
/**#return distance between two given points
* This method return the distance between two given points*/
private double distance(Point p1,Point p2 ){
return Math.sqrt(Math.pow((p1.getX()-p2.getX()), 2) + Math.pow((p1.getY()-p2.getY()), 2));
}
}//class Line
Your distance method seems to be ok (but it would be more performant, if you saved the differences in variables and used the * operator to multiply those values with themselfs instear of using Math.pow).
However, since floating point calculations tend to return inexact results, I don't recomend using the sum of the distances between the end node and the point to test as criterium.
But there's another good way determining, if a point is near a line or not: using the hesse normal form. It works like this:
Let P1 and P2 be vectors corresponing to the end points. * denotes the scalar multiplication and || the length of a vector:
D = (P2 - P1) / |P2 - P1|;
Let N be the vector D with coordinates swaped and the new x coordinate multiplied with -1 (i.e. a vector ortogonal to D).
Then the distance of a point H to the line can be determined like this
| N * H - N * P1 |
Also if H is between P1 and P2 can be checked like this (assuming without loss of generality D * P1 < D * P2):
D * P1 <= D * H <= D * P2
Using scalar products has the additional benefit, that calculating a scalar product only takes 2 multiplication and 1 addition in a 2D space.
This is how you could do this in java code
// components of normal vector
private double normalX;
private double normalY;
// components of direction vector
private double directionX;
private double directionY;
// the value of (N * P) for all points P on the line
private double normalScalarProduct;
// the range allowed for (D * P) for points on the line
private double minDirectionScalarProduct;
private double maxDirectionScalarProduct;
// error ranges; adjust as appropriate
private static final double directionAllowedError = 0.1;
private static final double normalAllowedError = 0.1;
public Line(int x1, int x2, int y1, int y2, Color myColor) {
...
double dx = x2 - x1;
double dy = y2 - y1;
double length = distance(dx, dy);
if (length == 0) {
// choose arbitrary direction, if length == 0
length = 1;
dx = 1;
}
// normalize direction
dx /= length;
dy /= length;
// set D and N values
this.directionX = dx;
this.directionY = dy;
this.normalX = -dy;
this.normalY = dx;
double prod1 = scalarProduct(directionX, directionY, x1, y1);
double prod2 = scalarProduct(directionX, directionY, x2, y2);
if (prod1 < prod2) {
minDirectionScalarProduct = prod1 - directionAllowedError;
maxDirectionScalarProduct = prod2 + directionAllowedError;
} else {
minDirectionScalarProduct = prod2 - directionAllowedError;
maxDirectionScalarProduct = prod1 + directionAllowedError;
}
normalScalarProduct = scalarProduct(x1, y1, normalX, normalY);
}
private static double scalarProduct(double x1, double y1, double x2, double y2) {
return x1*x2 + y1*y2;
}
public boolean contains(Point p) {
if (Math.abs(scalarProduct(p.getX(), p.getX(), normalX, normalY) - normalScalarProduct) <= normalAllowedError) {
// close enough to the line -> check, if between end points
double d = scalarProduct(p.getX(), p.getX(), directionX, directionY);
return minDirectionScalarProduct <= d && d <= maxDirectionScalarProduct;
}
return false;
}
private double distance(double dx, double dy) {
return Math.sqrt(dx*dx + dy*dy);
}
I have written an implementation of Bresenham's circle drawing algorithm. This algorithms takes advantage of the highly symmetrical properties of a circle (it only computes points from the 1st octant and draws the other points by taking advantage of symmetry). Therefore I was expecting it to be very fast. The Graphics programming black book, chapter #35 was titled "Bresenham is fast, and fast is good", and though it was about the line drawing algorithm, I could reasonably expect the circle drawing algorithm to also be fast (since the principle is the same).
Here is my java, swing implementation
public static void drawBresenhamsCircle(int r, double width, double height, Graphics g) {
int x,y,d;
y = r;
x = 0;
drawPoint(x, y, width, height,g);
d = (3-2*(int)r);
while (x <= y) {
if (d <= 0) {
d = d + (4*x + 6);
} else {
d = d + 4*(x-y) + 10;
y--;
}
x++;
drawPoint(x, y, width, height,g);
drawPoint(-x, y, width, height,g);
drawPoint(x, -y, width, height,g);
drawPoint(-x, -y, width, height,g);
drawPoint(y, x, width, height,g);
drawPoint(-y, x, width, height,g);
drawPoint(y, -x, width, height,g);
drawPoint(-y, -x, width, height,g);
}
}
This method uses the following drawPointmethod:
public static void drawPoint(double x, double y,double width,double height, Graphics g) {
double nativeX = getNativeX(x, width);
double nativeY = getNativeY(y, height);
g.fillRect((int)nativeX, (int)nativeY, 1, 1);
}
The two methods getNativeX and getNativeY are used to switch coordinates from originating in the upper left corner of the screen to a system that has it origin in the center of the panel with a more classic axis orientation.
public static double getNativeX(double newX, double width) {
return newX + (width/2);
}
public static double getNativeY(double newY, double height) {
return (height/2) - newY;
}
I have also created an implementation of a circle drawing algorithm based on trigonometrical formulaes (x=R*Math.cos(angle)and y= R*Math.sin(angle)) and a third implementation using a call to the standard drawArc method (available on the Graphics object). These additional implementations are for the sole purpose of comparing Bresenham's algorithm to them.
I then created methods to draw a bunch of circles in order to be able to get good measures of the spent time. Here is the method I use to draw a bunch of circles using Bresenham's algorithm
public static void drawABunchOfBresenhamsCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawBresenhamsCircle((int)r, width, height, g);
r += step;
}
}
Finally I override the paint method of the JPanel I am using, to draw the bunch of circles and to measure the time it took each type to draw. Here is the paint method:
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D)g;
g2D.setColor(Color.RED);
long trigoStartTime = System.currentTimeMillis();
drawABunchOfTrigonometricalCircles(1000, this.getWidth(), this.getHeight(), g);
long trigoEndTime = System.currentTimeMillis();
long trigoDelta = trigoEndTime - trigoStartTime;
g2D.setColor(Color.BLUE);
long bresenHamsStartTime = System.currentTimeMillis();
drawABunchOfBresenhamsCircles(1000, this.getWidth(), this.getHeight(), g);
long bresenHamsEndTime = System.currentTimeMillis();
long bresenDelta = bresenHamsEndTime - bresenHamsStartTime;
g2D.setColor(Color.GREEN);
long standardStarTime = System.currentTimeMillis();
drawABunchOfStandardCircles(1000, this.getWidth(), this.getHeight(),g);
long standardEndTime = System.currentTimeMillis();
long standardDelta = standardEndTime - standardStarTime;
System.out.println("Trigo : " + trigoDelta + " milliseconds");
System.out.println("Bresenham :" + bresenDelta + " milliseconds");
System.out.println("Standard :" + standardDelta + " milliseconds");
}
Here is the kind of rendering it would generate (drawing 1000 circles of each type)
Unfortunately my Bresenham's implementation is very slow. I took many comparatives measures, and the Bresenham's implementation is not only slower than the Graphics.drawArcbut also slower than the trigonometrical approach. Take a look at the following measures for a various number of circles drawn.
What part of my implementation is more time-consuming? Is there any workaround I could use to improve it? Thanks for helping.
[EDITION]: as requested by #higuaro, here is my trigonometrical algorithm for drawing a circle
public static void drawTrigonometricalCircle (double r, double width, double height, Graphics g) {
double x0 = 0;
double y0 = 0;
boolean isStart = true;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + Math.PI/36) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint((double)x, y, width, height, g);
if (!isStart) {
drawLine(x0, y0, x, y, width, height, g);
}
isStart = false;
x0 = x;
y0 = y;
}
}
And the method used to draw a bunch of trigonometrical circles
public static void drawABunchOfTrigonometricalCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawTrigonometricalCircle(r, width, height, g);
r += step;
}
}
Your Bresenham method isn't slow per se, it's just comparatively slow.
Swing's drawArc() implementation is machine-dependent, using native code. You'll never beat it using Java, so don't bother trying. (I'm actually surprised the Java Bresenham method is as fast as it is compared to drawArc(), a testament to the quality of the virtual machine executing the Java bytecode.)
Your trigonometric method, however, is unnecessarily fast, because you're not comparing it to Bresenham on an equal basis.
The trig method has a set angular resolution of PI/36 (~4.7 degrees), as in this operation at the end of the for statement:
angle = angle + Math.PI/36
Meanwhile, your Bresenham method is radius-dependent, computing a value at each pixel change. As each octant produces sqrt(2) points, multiplying that by 8 and dividing by 2*Pi will give you the equivalent angular resolution. So to be on equal footing with the Bresenham method, your trig method should therefore have:
resolution = 4 * r * Math.sqrt(2) / Math.PI;
somewhere outside the loop, and increment your for by it as in:
angle += resolution
Since we will now be back to pixel-level resolutions, you can actually improve the trig method and cut out the subsequent drawline call and assignments to x0 and y0, eliminate unnecessarily casts, and furthermore reduce calls to Math. Here's the new method in its entirety:
public static void drawTrigonometricalCircle (double r, double width, double height,
Graphics g) {
double localPi = Math.PI;
double resolution = 4 * r * Math.sqrt(2) / Math.PI;
for (double angle = 0; angle <= localPi; angle += resolution) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint(x, y, width, height, g);
}
}
The trig method will now be executing more often by several orders of magnitude depending on the size of r.
I'd be interested to see your results.
Your problem lies in that Bresenham's algorithm does a variable number of iterations depending on the size of the circle whereas your trigonometric approach always does a fixed number of iterations.
This also means that Bresenham's algorithm will always produce a smooth looking circle whereas your trigonometric approach will produce worse looking circles as the radius increases.
To make it more even, change the trigonometric approach to produce approximately as many points as the Bresenham implementation and you'll see just how much faster it is.
I wrote some code to benchmark this and also print the number of points produced and here are the initial results:
Trigonometric: 181 ms, 73 points average
Bresenham: 120 ms, 867.568 points average
After modifying your trigonometric class to do more iterations for smoother circles:
int totalPoints = (int)Math.ceil(0.7 * r * 8);
double delta = 2 * Math.PI / totalPoints;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {
These are the results:
Trigonometric: 2006 ms, 854.933 points average
Bresenham: 120 ms, 867.568 points average
I lately wrote a bresenham circle drawing implemenation myself for a sprite rasterizer and tried to optimize it a bit. I'm not sure if it will be faster or slower than what you did but i think it should have a pretty decent execution time.
Also unfortunately it is written in C++. If i have time tomorrow i might edit my answer with a ported Java version and an example picture for the result but for now you'd have to do it yourself if you want (or someone else who would want to take his time and edit it.)
Bascically, what it does is use the bresenham algorithm to aquire the positions for the outer edges of the circle, then perform the algorithm for 1/8th of the circle and mirror that for the the remaining 7 parts by drawing straight lines from the center to the outer edge.
Color is just an rgba value
Color* createCircleColorArray(const int radius, const Color& color, int& width, int& height) {
// Draw circle with custom bresenham variation
int decision = 3 - (2 * radius);
int center_x = radius;
int center_y = radius;
Color* data;
// Circle is center point plus radius in each direction high/wide
width = height = 2 * radius + 1;
data = new Color[width * height];
// Initialize data array for transparency
std::fill(data, data + width * height, Color(0.0f, 0.0f, 0.0f, 0.0f));
// Lambda function just to draw vertical/horizontal straight lines
auto drawLine = [&data, width, height, color] (int x1, int y1, int x2, int y2) {
// Vertical
if (x1 == x2) {
if (y2 < y1) {
std::swap(y1, y2);
}
for (int x = x1, y = y1; y <= y2; y++) {
data[(y * width) + x] = color;
}
}
// Horizontal
if (y1 == y2) {
if (x2 < x1) {
std::swap(x1, x2);
}
for (int x = x1, y = y1; x <= x2; x++) {
data[(y * width) + x] = color;
}
}
};
// Lambda function to draw actual circle split into 8 parts
auto drawBresenham = [color, drawLine] (int center_x, int center_y, int x, int y) {
drawLine(center_x + x, center_y + x, center_x + x, center_y + y);
drawLine(center_x - x, center_y + x, center_x - x, center_y + y);
drawLine(center_x + x, center_y - x, center_x + x, center_y - y);
drawLine(center_x - x, center_y - x, center_x - x, center_y - y);
drawLine(center_x + x, center_y + x, center_x + y, center_y + x);
drawLine(center_x - x, center_y + x, center_x - y, center_y + x);
drawLine(center_x + x, center_y - x, center_x + y, center_y - x);
drawLine(center_x - x, center_y - x, center_x - y, center_y - x);
};
for (int x = 0, y = radius; y >= x; x++) {
drawBresenham(center_x, center_y, x, y);
if (decision > 0) {
y--;
decision += 4 * (x - y) + 10;
}
else {
decision += 4 * x + 6;
}
}
return data;
}
//Edit
Oh wow, I just realized how old this question is.
The following code is called every 50ms.
// Start point
private double x;
private double y;
private double z;
private double y1;
#Override
public void run() {
double x1 = Math.cos(y1);
double z1 = Math.sin(y1);
double y2 = 4D - y1;
double x2 = Math.sin(y2);
double z2 = Math.cos(y2);
// First new point
double pX1 = x + x1;
double pY1 = y + y1;
double pZ1 = z + z1;
// Second new point
double pX2 = x + x2;
double pY2 = y + y2;
double pZ2 = z + z2;
if (y1 > 4D) {
y1 = 0D;
} else {
y1 = y1 + 0.1D;
}
}
Here is the output in a game. It generates two helices.
I cannot control more than the radius.
I am looking for code I can easily customize to fit my preferences.
How do I control the following aspects?
How fast the helix rises.
Where the helix begins.
helix is circular shape with 'linear' movement of the plane
you use plane xz as helix base and y axis as height
so you need:
r - radius
d - distance between two screws (the y movement after full circle)
t - parameter <0,1> determining the position on helix
h0,h1 - start end height of helix (y-axis)
a0 - angular start position [rad]
Now how to get the point on helix as function of parameter these parameters
aa=fabs(h1-h0)*2.0*M_PI/d; // angular speed coefficient
// if you need opposite angular direction then add aa=-aa;
x=r*cos(a0+aa*t);
z=r*sin(a0+aa*t);
y=h0+((h1-h0)*t);
aa can be precomputed once
now if t=0.0 then you get the start point of helix
if t=1.0 then you got the endpoint of helix
so speed is just how much you add to t during animation per timer cycle
d controls number of screw loops
h1-h0 is the helix height
code is in C++ (sorry I am not a JAVA coder)
One part of the helix begins at:
(x, y, z) = (1.0, 0.0, 0.0)
And the other at:
(x, y, z) = (-0.8, 4.0, -0.7)
And the particle rises at a rate of 0.1 (from y1 = y1 + 0.1D).
So, to control how fast the particles rise, just change this value.
To control where the helix begins you need to change the angle. For example, add some value to the sines and cosines. Like this:
Math.cos(y1 + dy);
To make more rotations before restarting from the ground you can multiply the angle. Make it twice as fast:
Math.cos(2 * y1);
Helix is circular shape with progressive Y value.
// Start point
private double x;
private double y;
private double z;
private double degree;
private double rY;
#Override
public void run() {
// We use the same formula that is used to find a point of a circumference
double rX = Math.cos(degree);
double rZ = Math.sin(degree);
// New point
double pX = x + rX;
double pY = y + rY;
double pZ = z + rZ;
if (degree > 2D * Math.PI) {
degree = 0D;
} else {
degree = degree + 0.2D;
}
if (pY > 2D) {
pY = 0D;
} else {
pY = pY + 0.02D;
}
}
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I need to draw a fractal swirl using the algorithm Iterated Function System.
There are coefficients for this fractal:
0.745455 -0.459091 0.406061 0.887121 1.460279 0.691072 0.912675
-0.424242 -0.065152 -0.175758 -0.218182 3.809567 6.741476 0.087325
And here is my code:
import java.awt.Graphics;
import javax.swing.JPanel;
public class Surface extends JPanel {
double a1 = 0.745455;
double b1 = -0.459091;
double d1 = 0.406061;
double e1 = 0.887121;
double c1 = 1.460279;
double f1 = 0.691072;
double p1 = 0.912675;
double a2 = -0.424242;
double b2 = -0.065152;
double d2 = -0.175758;
double e2 = -0.218182;
double c2 = 3.809567;
double f2 = 6.741476;
double p2 = 0.087325;
double x1(double x, double y) {
return a1 * x + b1 * y + c1;
}
double y1(double x, double y) {
return d1 * x + e1 * y + f1;
}
double x2(double x, double y) {
return a2 * x + b2 * y + c2;
}
double y2(double x, double y) {
return d2 * x + e2 * y + f2;
}
public void paint(Graphics g) {
drawFractal(g);
}
void drawFractal(Graphics g) {
double x1 = 300;
double y1 = 300;
double x2 = 0;
double y2 = 0;
g.fillOval(300 + (int) x1, 300 + (int) y1, 3, 3);
for (int i = 0; i < 10000; i++) {
double p = Math.random();
if (p < 0.91675) {
x2 = x1(x1, y1);
y2 = y1(x1, y1);
g.fillOval(300 + (int) x2, 300 + (int) y2, 3, 3);
x1 = x2;
y1 = y2;
} else {
x2 = x2(x1, y1);
y2 = y2(x1, y1);
g.fillOval(300 + (int) x2, 300 + (int) y2, 3, 3);
x1 = x2;
y1 = y2;
}
}
}
}
Unfortunately, with this code I get a wrong picture:
It would be great if someone could point out my mistake.
Your generation seems correct (i.e. don't do x1 = x2 +300; y1 = y2 +300;), but your problem is you're way off the scale for the purposes of rendering. This means there are very few points that fall outside very center of the image.
Your window is [0..600]x[0..600]. Try multiplying x2 and y2 with 50, so that you're rendering the [-6..6]x[-6..6] region instead of the [-300..300]x[-300..300] region of space.
Note that it should be sufficient to draw single pixels (as lines to itself) instead of 3x3 ovals.
int xp = 300 + (int) (x2 * scale);
int yp = 300 + (int) (y2 * scale);
g.drawLine(xp, yp, xp, yp);
Depending on what gets rendered, you might need to adjust the scale slightly to get the entire image with reasonable bounds. Note the second transformation offsets by -6.7, so a scale of 30 should be about right.
Also note that by using x1 = x2 +300; y1 = y2 +300; you change the transformations and get a different fractal (at a scale at which you expect).
This is great, I was wrong thinking that exponential runtime required! The fractals appeared more dimensional than my imagination!
Thanks #Jan Dvorak!
The following also works (in my coordinates, xcenter=300, ycenter=100 and radius=50 are global drawing parameters) and works faster:
void drawFractal2(Graphics g) {
double x1 = 0;
double y1 = 0;
double x2 = 0;
double y2 = 0;
double p;
g.fillOval(xcenter + (int) (x1 * radius), ycenter + (int) (y1 * radius), 3, 3);
for(int i=0; i<100000; ++i) {
p = Math.random();
if (p < p1) {
x2 = x1(x1, y1);
y2 = y1(x1, y1);
}
else {
x2 = x2(x1, y1);
y2 = y2(x1, y1);
}
g.fillOval(xcenter + (int) (x2 * radius), ycenter + (int) (y2 * radius), 3, 3);
x1 = x2;
y1 = y2;
}
}
and the picture is better
BELOW IS MY INCORRECT ANSWER
But it show how fractals are bigger than the intuition, so I keep it.
I guess your algorithm should be tree-like (recursive) while your one is linear. You are just drawing one chain of points, transforming it one after one. So you get some spiral-like chain. It can't generate any fractal picture in principle.
I GOT YOUR PICTURE
You have 2 mistakes:
1) you pass 300 both into iteration and as drawing shift. This is minor.
2) You algorithm is linear. Linear algorithm can't draw tree-like picture. If you use random values, you should run algorithm multiple times. One chain draws only one random portion of the picture.
I got your picture with following recursive algorithm. It works slow but you are to improve it.
void drawFractal(Graphics g, double x1, double y1, int depth) {
double x2 = 0;
double y2 = 0;
if( depth > 20 ) {
return;
}
g.fillOval(xcenter + (int) (x1 * radius), ycenter + (int) (y1 * radius), 3, 3);
x2 = x1(x1, y1);
y2 = y1(x1, y1);
drawFractal(g, x2, y2, depth+1);
x2 = x2(x1, y1);
y2 = y2(x1, y1);
drawFractal(g, x2, y2, depth+1);
}
to run it I used
public void paint(Graphics g) {
//drawFractal(g);
drawFractal(g, 0, 0, 0);
}
parameters are
int xcenter = 300;
int ycenter = 100;
int radius = 50;
the picture is follows: