/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm3d.fractals;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import org.freehep.graphics2d.VectorGraphics;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.event.ChangeEvent;
import vmm3d.actions.AbstractActionVMM;
import vmm3d.actions.ActionList;
import vmm3d.actions.ActionRadioGroup;
import vmm3d.core.BasicMouseTask2D;
import vmm3d.core.Complex;
import vmm3d.core.Decoration;
import vmm3d.core.Display;
import vmm3d.core.Exhibit;
import vmm3d.core.I18n;
import vmm3d.core.IntegerParam;
import vmm3d.core.MouseTask;
import vmm3d.core.Parameter;
import vmm3d.core.RealParam;
import vmm3d.core.TaskManager;
import vmm3d.core.Transform;
import vmm3d.core.Util;
import vmm3d.core.VMMSave;
import vmm3d.core.View;
/**
* The Mandelbrot set. The exhibit has an associated decoration that can show
* an approximation of the outline of the
* Julia set and the zero-orbit associated with a point (cx,cy).
* This decoration is not visible by default, but can be made visible in a view of the
* exhibit. If it is visible, the start point (cx,cy) is marked with a white cross,
* and the user can drag this cross; in addition to the cross, either the Julia set
* or the zero-orbit or both can be shown. The coordinates cx, cy are given
* by the values of two parameters that are associated with the exhibit, so they can
* also be set directly.
* This exhibit differs from others in that the basic data needed to
* draw the exhibit is a bitmap. To make this work properly, a nested View class is
* defined that works only for Mandelbrot exhibits. The view class overrides the render()
* method from the base {@link View} class, which means that it is using a completly
* different rendering system from other exhibits. Various other changes were made
* that are unique to the Mandelbrot exhibit. Since this might be the only exhibit
* where the basic data is a bitmap, no attempt was made to make the modifications in
* a general way. Note that the View for a Mandelbrot exhibit MUST be of type
* Mandelbrot.MandelbrotView.
*/
public class Mandelbrot extends Exhibit {
/**
* The decoration that is responsible for showing the Julia set and zero-orbit. What is actually
* shown depends on the setting of the showJuliaAndOrbit property of the View class, but the
* decoration is always present on the exhibit, even if nothing is visible.
*/
private JuliaSetAndOrbitDecoration juliaAndOrbitDecoration;
/**
* The number of points that are shown on an orbit.
*/
private IntegerParam pointsOnOrbit = new IntegerParam("vmm.fractals.Mandelbrot.PointsOnOrbit", 100);
/**
* The x-coord of the point for which the Julia set and zero-orbit are shown.
*/
private RealParam juliaPointX = new RealParam("vmm.fractals.Mandelbrot.juliaPointX", 0.25);
/**
* The y-coord of the point for which the Julia set and zero-orbit are shown.
*/
private RealParam juliaPointY = new RealParam("vmm.fractals.Mandelbrot.juliaPointY", 0.45);
private TaskManager taskManager; // used for computing the image asynchronously
private double[][] exampleData = {
{ -0.7241608526756182,-0.7241608526694334,0.36158285101080495,0.3615828510154436,1000,250 },
{ -1.9072326638218555,-1.9072326346322426,-1.0859414558092964E-8,1.103279515362283E-8,500,100 },
{ 0.35471345463684467,0.35473822122425647,0.095401040922098,0.09541961586265685,4000,2500 },
{ 0.2726031397857463,0.2726031483804942,0.0053565210436868176,0.005356527489747753,7500,2500 },
{ 0.28601560167064516,0.2860156017191516,0.011537485975923616,0.011537486012303432,3000,250 },
{ -1.6744096756044493,-1.6744096717769306,4.716419197284976E-5,4.7167062611931696E-5,7500,1000 },
{ 0.25989953593561266,0.259899641345385,0.001612579325834812,0.0016126583831640785,2000,250 },
{ -1.674409674093473,-1.6744096740931858,4.716540768697223E-5,4.716540790246652E-5,10000,250 },
{ -1.4035289973308978,-1.4035289973294278,0.02930868838864931,0.029308688389751928,2500,500 },
{ -1.9072311984370052,-1.9072311928858665,1.5197004423572747E-5,1.5201167777464862E-5,7500,250 },
{ -0.753231765876289,-0.7532317565038811,0.04633550302364065,0.046335510285468194,10000,250 },
{ -1.9963806954442953,-1.996380695443582,2.62870483517615E-7,2.628710361171417E-7,1500,250 },
{ 0.29768460024540017,0.297684633418743,0.020961285679467805,0.020961312432163624,5000,1000}
};
public Mandelbrot() {
addParameter(juliaPointY);
addParameter(juliaPointX);
addParameter(pointsOnOrbit);
pointsOnOrbit.setMinimumValueForInput(1);
pointsOnOrbit.setMaximumValueForInput(1000);
juliaPointX.setMinimumValueForInput(-4);
juliaPointX.setMaximumValueForInput(3);
juliaPointY.setMinimumValueForInput(-3);
juliaPointY.setMaximumValueForInput(3);
juliaAndOrbitDecoration = new JuliaSetAndOrbitDecoration();
juliaAndOrbitDecoration.setStartPoint(new Point2D.Double(juliaPointX.getValue(),juliaPointY.getValue()));
addDecoration(juliaAndOrbitDecoration);
setDefaultWindow(-2.25, 0.85, -1.25, 1.25);
}
public void removeView(View view) {
super.removeView(view);
if (taskManager != null && (getViews() == null || getViews().size() == 0)) {
taskManager.shutDown();
taskManager = null;
}
}
/**
* This method is overridden so that in addition to redrawing the Exhibit, it also sets the
* Julia set point, (cx,cy), to match the values of the associated parameters.
*/
public void parameterChanged(Parameter param, Object oldValue, Object newValue) {
super.parameterChanged(param, oldValue, newValue);
juliaAndOrbitDecoration.setStartPoint(new Point2D.Double(juliaPointX.getValue(),juliaPointY.getValue()));
}
/**
* Draws the Mandelbrot set to the view's bitmap.
*/
private void drawMandelbrot(final View view, final Transform transform) {
int height = transform.getHeight();
MandelbrotView mView = (MandelbrotView)view;
mView.cancelAsyncComputeJob();
if (view.getFastDrawing()) {
int max = mView.maxCount.getValue();
int paletteLength = mView.paletteLength.getValue();
int width = transform.getWidth();
Point2D pt = new Point2D.Double();
int blockSize = 10;
if (max <= 50)
blockSize = 3;
else if (max <= 500)
blockSize = 6;
for (int r = blockSize; r < height + blockSize; r += blockSize*2)
for (int c = blockSize; c < width + blockSize; c += blockSize*2) {
pt.setLocation(c,r);
transform.viewportToWindow(pt);
double x = pt.getX();
double y = pt.getY();
int ct = iterate(x,y,max);
Color color;
if (ct == max)
color = Color.BLACK;
else {
float hue;
if (paletteLength > 0)
hue = (float)(ct % paletteLength)/paletteLength;
else
hue = (float)ct/max;
color = Color.getHSBColor(hue, 1, 1);
}
mView.setColor(color);
mView.fillRectDirect(c-blockSize,r-blockSize,blockSize*2,blockSize*2);
}
}
else {
view.getDisplay().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (taskManager == null)
taskManager = new TaskManager();
mView.fillCanvasWithGray();
TaskManager.Job job = taskManager.createJob();
int jobNumber = ++mView.asyncJobNum;
for (int r = 0; r < height; r++)
job.add(new ComputeRow(r, mView, jobNumber));
job.close();
if (!job.await(500))
mView.startAsyncComputeJob(job);
else
view.getDisplay().setCursor(Cursor.getDefaultCursor());
}
}
private class ComputeRow implements Runnable {
int jobNum;
int row;
MandelbrotView view;
Transform transform;
int max;
int paletteLength;
ComputeRow(int row, MandelbrotView view, int jobNum) {
this.row = row;
this.view = view;
this.jobNum = jobNum;
transform = (Transform)view.getTransform().clone();
max = view.maxCount.getValue();
paletteLength = view.paletteLength.getValue();
}
public void run() {
Point2D pt = new Point2D.Double();
int width = transform.getWidth();
int[] rgb = new int[width];
for (int c = 0; c < width; c++) {
pt.setLocation(c,row);
transform.viewportToWindow(pt);
double x = pt.getX();
double y = pt.getY();
int ct = iterate(x,y,max);
if (ct == max)
rgb[c] = 0;
else {
float hue;
if (paletteLength > 0)
hue = (float)(ct % paletteLength)/paletteLength;
else
hue = (float)ct/max;
rgb[c] = Color.HSBtoRGB(hue, 1, 1);
}
}
if (jobNum == view.asyncJobNum)
view.pixelRow(rgb,row);
}
}
/**
* Does the Mandelbrot iteration, z = z^2 + c, and counts the number of steps to
* get 2 units or more from the origin.
* @param cx x-coord of the constant c
* @param cy y-coord of the constant c
* @param max the maximum number of iterations.
* @return the number of iterations to get more than 2 units from the origin, or the max
* value if the process does not stop before that many iterations
*/
private int iterate(double cx, double cy, int max) {
int ct = 0;
double zx = cx;
double zy = cy;
while (ct < max && zx*zx + zy*zy <= 4) {
double t = zx*zx - zy*zy + cx;
zy = 2*zx*zy + cy;
zx = t;
ct++;
}
return ct;
}
public ActionList getActionsForView(final View view) {
ActionList actions = super.getActionsForView(view);
actions.add( new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.DragToShowCoords")) {
public void actionPerformed(ActionEvent evt) {
view.getDisplay().installOneShotMouseTask(new ShowCoordsMouseTask());
}
});
actions.add( new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.RecenterOnPointMenuItem")+"...") {
public void actionPerformed(ActionEvent evt) {
doCenterOnUserPoint(view);
}
});
ActionList zoomIn = new ActionList(I18n.tr("vmm.fractals.Mandelbrot.ZoomIn"));
for (int zoom = 2; zoom <= 32; zoom *= 2) {
final double z = zoom;
zoomIn.add(new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.ZoomIn") + " " + zoom + "X") {
public void actionPerformed(ActionEvent evt) {
((MandelbrotView)view).doZoom(z);
}
});
}
actions.add(zoomIn);
ActionList zoomOut = new ActionList(I18n.tr("vmm.fractals.Mandelbrot.ZoomOut"));
for (int zoom = 2; zoom <= 32; zoom *= 2) {
final double z = 1.0/zoom;
zoomOut.add(new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.ZoomOut") + " " + zoom + "X") {
public void actionPerformed(ActionEvent evt) {
((MandelbrotView)view).doZoom(z);
}
});
}
actions.add(zoomOut);
actions.add( new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.RestoreDefaults")) {
public void actionPerformed(ActionEvent evt) {
MandelbrotView mView = (MandelbrotView)view;
mView.maxCount.setValue(mView.maxCount.getDefaultValue());
mView.paletteLength.setValue(mView.paletteLength.getDefaultValue());
mView.setWindow(getDefaultWindow());
mView.setShowJuliaAndOrbit(MandelbrotView.SHOW_NONE);
}
});
ActionList examples = new ActionList(I18n.tr("vmm.fractals.Mandelbrot.Examples"));
for (int i = 0; i < exampleData.length; i++) {
final int exampleNum = i;
examples.add(new AbstractActionVMM(" " + (i+1) + " ") {
public void actionPerformed(ActionEvent evt) {
double[] data = exampleData[exampleNum];
MandelbrotView mView = (MandelbrotView)view;
mView.setWindow(data[0],data[1],data[2],data[3]);
mView.maxCount.setValue((int)data[4]);
mView.paletteLength.setValue((int)data[5]);
mView.setShowJuliaAndOrbit(MandelbrotView.SHOW_NONE);
}
});
}
actions.add(examples);
actions.add(null);
actions.add( new AbstractActionVMM(I18n.tr("vmm.fractals.Mandelbrot.MoveJuliaPoint")) {
public void actionPerformed(ActionEvent evt) {
view.getDisplay().installOneShotMouseTask(new MoveJuliaStartByMouseClick());
}
});
return actions;
}
/**
* Lets user enter the x and y coords of the point at the center of the display.
*/
private void doCenterOnUserPoint(View view) {
Point2D newCenter = Util.getPoint2DFromUser(view.getDisplay(), I18n.tr("vmm.fractals.Mandelbrot.RecenterOnPointPrompt"));
if (newCenter == null)
return;
Transform tr = view.getTransform();
double xCenter = (tr.getXmax() + tr.getXmin()) / 2;
double yCenter = (tr.getYmax() + tr.getYmin()) / 2;
double newX = newCenter.getX();
double newY = newCenter.getY();
double offsetX = newX - xCenter;
double offsetY = newY - yCenter;
if (offsetX == 0 && offsetY == 0)
return;
tr.setLimits( tr.getXmin() + offsetX, tr.getXmax() + offsetX, tr.getYmin() + offsetY, tr.getYmax() + offsetY);
}
/**
* Overridden to return an object belonging to the nested class MandelbrotView. Note that
* the View for a Mandelbrot exhibit MUST be of this type.
*/
public View getDefaultView() {
return new MandelbrotView();
}
/**
* A View class suitable for viewing a Mandelbrot exhibit. The view for a Mandelbrot exhibit MUST
* be of this type. This class modifies the usual behavior of a View extensively to account for
* the fact that the basic data for a Mandelbrot exhibit is the bitmap on which the Mandelbrot is
* drawn. This view class has been rigged to make sure that this bitmap is not redrawn more
* often than necessary. For example, decorations are not drawn to the bitmap; instead, they
* are drawn over the bitmap each time the view is repainted.
*/
public class MandelbrotView extends View {
public static final int SHOW_JULIA = 0;
public static final int SHOW_ORBIT = 1;
public static final int SHOW_BOTH = 2;
public static final int SHOW_NONE = 3;
@VMMSave private int showJuliaAndOrbit = SHOW_NONE;
private ActionRadioGroup showJuliaAndOrbitSelect;
private Transform previousTransform = null;
private boolean bitmapNeedsRedraw = true;
private volatile int asyncJobNum;
private volatile Thread asyncComputeThread;
private volatile TaskManager.Job asyncComputeJob;
private IntegerParam maxCount = new IntegerParam("vmm.fractals.Mandelbrot.MaxIters", 50);
private IntegerParam paletteLength = new IntegerParam("vmm.fractals.Mandelbrot.PaletteLength", 0);
public MandelbrotView() {
addParameter(paletteLength);
addParameter(maxCount);
maxCount.setMinimumValueForInput(10);
maxCount.setMaximumValueForInput(100000);
paletteLength.setMinimumValueForInput(0);
paletteLength.setMaximumValueForInput(100000);
showJuliaAndOrbitSelect = new ActionRadioGroup() {
public void optionSelected(int selectedIndex) {
setShowJuliaAndOrbit(selectedIndex);
}
};
showJuliaAndOrbitSelect.addItem(I18n.tr("vmm.fractals.Mandelbrot.showJuliaAndOrbit.julia"));
showJuliaAndOrbitSelect.addItem(I18n.tr("vmm.fractals.Mandelbrot.showJuliaAndOrbit.orbit"));
showJuliaAndOrbitSelect.addItem(I18n.tr("vmm.fractals.Mandelbrot.showJuliaAndOrbit.both"));
showJuliaAndOrbitSelect.addItem(I18n.tr("vmm.fractals.Mandelbrot.showJuliaAndOrbit.none"));
showJuliaAndOrbitSelect.setSelectedIndex(showJuliaAndOrbit);
backgroundCommands.setEnabled(false);
}
/**
* Zooms the display by a specified magnification factor. A magnification factor greater than 1 is a zoom in;
* a magnification factor less than 1 is a zoom out. The magnification factor must be greater than zero
* (this is not checked).
*/
public void doZoom(double magnificationFactor) {
double[] window = getRequestedWindow();
double width = window[1] - window[0];
double height = window[3] - window[2];
double centerX = window[0] + width/2;
double centerY = window[2] + height/2;
double newWidth = width / magnificationFactor;
double newHeight = height / magnificationFactor;
setWindow(centerX - newWidth/2, centerX + newWidth/2, centerY - newHeight/2, centerY + newHeight/2);
}
/**
* Make a string showing the coordinates of the image point that corresponds
* to pixel coordinates (x,y).
*/
String getCoordString(int x, int y) {
double [] window = getWindow();
double xmin = window[0];
double xmax = window[1];
double ymin = window[2];
double ymax = window[3];
double width = getTransform().getWidth();
double height = getTransform().getHeight();
double xCoord = xmin + x/width*(xmax-xmin);
double yCoord = ymax - y/height*(ymax-ymin);
// The next 10 lines try to avoid more digits after the decimal
// points than makes sense. If it succeeds the coordinates
// that are shown should differ only in their last few digits.
double diff = xmax - xmin;
int scale = 4;
if (diff > 0) {
while (diff < 1) {
scale++;
diff *= 10;
}
}
String xStr = String.format("%1." + scale + "f", xCoord);
String yStr = String.format("%1." + scale + "f", yCoord);
return "(" + xStr + ", " + yStr + ")";
}
/**
* Returns the value of the showJuliaAndOrbit property.
* @see #setShowJuliaAndOrbit(int)
*/
public int getShowJuliaAndOrbit() {
return showJuliaAndOrbit;
}
/**
* Sets the value of the showJuliaAndOrbit property. A Mandelbrot exhibit has an associated decoration
* that is capable of showing an outline of the Julia set and the zero orbit associated with some
* value of c. The showJuliaAndOrbit property of the view determines what is actually shown of this
* decoration in this view. The values are given by constants SHOW_NONE, SHOW_BOTH, SHOW_JUILA,
* and SHOW_ORBIT which are defined in this class.
*/
public void setShowJuliaAndOrbit(int showJuliaAndOrbit) {
if (this.showJuliaAndOrbit == showJuliaAndOrbit)
return;
this.showJuliaAndOrbit = showJuliaAndOrbit;
showJuliaAndOrbitSelect.setSelectedIndex(showJuliaAndOrbit);
if (getDisplay() != null)
getDisplay().setStatusText();
forceRedraw();
}
public ActionList getActions() {
ActionList actions = super.getActions();
actions.add(showJuliaAndOrbitSelect);
return actions;
}
public String getStatusText() {
if (showJuliaAndOrbit == SHOW_NONE) {
if (! Util.isMacOS())
return I18n.tr("vmm.fractals.Mandelbrot.statusText.DragToZoom");
else
return I18n.tr("vmm.fractals.Mandelbrot.statusText.DragToZoomMac");
}
else
return I18n.tr("vmm.fractals.Mandelbrot.statusText.dragCross");
}
/**
* Overridden to force a redraw of the Mandelbrot bitmap.
*/
public void setFastDrawing(boolean fast, boolean redrawSynchronizedViews) {
super.setFastDrawing(fast,redrawSynchronizedViews);
bitmapNeedsRedraw = true;
forceRedraw();
}
/**
* Overridden to force a redraw of the Mandelbrot bitmap if it is the transform that
* is changing.
*/
public void stateChanged(ChangeEvent evt) {
super.stateChanged(evt);
if (evt.getSource() instanceof Transform)
bitmapNeedsRedraw = true;
}
/**
* Overridden to force a redraw of the Mandelbrot bitmap if it is the maximum iteration
* count that is changing. (In fact, this is the only parameter defined for this class.)
*/
public void parameterChanged(Parameter param, Object oldValue, Object newValue) {
super.parameterChanged(param, oldValue, newValue);
if (param == maxCount || param == paletteLength)
bitmapNeedsRedraw = true;
}
public void drawOvalDirect(Color c, int x, int y, int width, int height) {
Graphics g = getTransform().getUntransformedGraphics();
g.setColor(c);
g.drawOval(x,y,width,height);
}
public void fillOvalDirect(Color c, int x, int y, int width, int height) {
Graphics g = getTransform().getUntransformedGraphics();
g.setColor(c);
g.fillOval(x,y,width,height);
}
/**
* The render method has been modified to draw the mandelbrot set directly to the bitmap.
* The usual rendering is not used; in particular, the computeDrawData and doDraw methods
* of the Mandelbrot exhibit are not called.
*/
public void render(VectorGraphics g, int width, int height) {
VectorGraphics saveGraphics = g;
getTransform().setUpDrawInfo(g,0,0,width,height,getPreserveAspect(),getApplyGraphics2DTransform());
if (fullOSI == null || getTransform().getWidth() != fullOSI.getWidth() || getTransform().getHeight() != fullOSI.getHeight()) {
bitmapNeedsRedraw = true;
try {
createOSI(width,height);
}
catch (OutOfMemoryError e) {
saveGraphics.setColor(Color.WHITE);
saveGraphics.fillRect(0,0,width,height);
saveGraphics.setColor(Color.RED);
saveGraphics.drawString(I18n.tr("vmm3d.core.OutOfMemoryError"),20,35);
return;
}
}
prepareOSIForDrawing();
if (bitmapNeedsRedraw)
drawMandelbrot(this,getTransform());
bitmapNeedsRedraw = false;
saveGraphics.drawImage(fullOSI,0,0,null);
g.setColor(getForeground());
g.setBackground(getBackground());
if (getAntialiased())
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
getTransform().setUpDrawInfo(saveGraphics,0,0,width,height,getPreserveAspect(),getApplyGraphics2DTransform());
currentGraphics = saveGraphics;
Decoration[] dec = getDecorations();
for (int i = 0; i < dec.length; i++) {
dec[i].computeDrawData(this,exhibitNeedsRedraw,previousTransform,getTransform());
dec[i].doDraw(saveGraphics,this,getTransform());
}
dec = Mandelbrot.this.getDecorations();
for (int i = 0; i < dec.length; i++) {
dec[i].computeDrawData(this,exhibitNeedsRedraw,previousTransform,getTransform());
dec[i].doDraw(saveGraphics,this,getTransform());
}
getTransform().finishDrawing();
needsRedraw = false;
}
/**
* Has been overridden because the usual bitmap for this view does not contain the
* decorations. A new BufferedImage is created that shows both the andelbrot set bitmap and
* the decorations.
*/
public BufferedImage getImage(boolean alwaysCopy) {
if (getDisplay() == null || getExhibit() == null || fullOSI == null)
return null;
BufferedImage image;
org.freehep.graphics2d.VectorGraphics g;
image = new BufferedImage(fullOSI.getWidth(),fullOSI.getHeight(),offscreenImageType);
g = (VectorGraphics)image.getGraphics();
render(g,fullOSI.getWidth(),fullOSI.getHeight());
g.dispose();
return image;
}
/**
* Has been overridden to copy the maximum iteration count, if the shareTransform
* parameter is true. (Otherwise, if the view has been zoomed in a lot, the
* synchronized view might be entirely black.)
*/
public void takeExhibit(View view, boolean shareTransform) {
super.takeExhibit(view,shareTransform);
if (shareTransform && view instanceof MandelbrotView) {
maxCount.setValue(((MandelbrotView)view).maxCount.getValue());
paletteLength.setValue(((MandelbrotView)view).paletteLength.getValue());
}
}
/**
* Overridden to return a mouse task that will let the user drag the Julia set
* point. If the user does not click near the start point, the mouse task behaves
* the same as a usual BasicMouseTask2D.
*/
public MouseTask getDefaultMouseTask() {
return new DragJuliaStartMouseTask();
}
private void fillCanvasWithGray() {
Graphics g = fullOSI.getGraphics();
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0,0,fullOSI.getWidth(),fullOSI.getHeight());
g.dispose();
}
synchronized private void pixelRow(int[] rgb, int rowNum) {
fullOSI.setRGB(0, rowNum, fullOSI.getWidth(), 1, rgb, 0, fullOSI.getWidth());
}
synchronized private void startAsyncComputeJob(TaskManager.Job job) {
asyncComputeJob = job;
asyncComputeThread = new Thread() {
TaskManager.Job myJob = asyncComputeJob;
public void run() {
while (!myJob.await(1000))
doRepaint();
synchronized(MandelbrotView.this) {
if (myJob == asyncComputeJob && !myJob.isCanceled()) {
doRepaint();
if (getDisplay() != null)
getDisplay().setCursor(Cursor.getDefaultCursor());
}
if (myJob == asyncComputeJob) {
asyncComputeJob = null;
asyncComputeThread = null;
}
}
}
synchronized void doRepaint() {
if (getDisplay() != null) {
getDisplay().repaint();
try {
wait(10);
}
catch (InterruptedException e) {
}
}
}
};
asyncComputeThread.start();
}
synchronized private void cancelAsyncComputeJob() {
if (asyncComputeJob != null) {
asyncComputeJob.cancel();
asyncComputeJob = null;
asyncComputeThread = null;
if (getDisplay() != null)
getDisplay().setCursor(Cursor.getDefaultCursor());
}
}
} // end class MandelbrotView
private class ShowCoordsMouseTask extends MouseTask {
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
display.setStatusText("(x,y) = " + ((MandelbrotView)view).getCoordString(evt.getX(), evt.getY()));
return true;
}
public void doMouseDrag(MouseEvent evt, Display display, View view, int width, int height) {
display.setStatusText("(x,y) = " + ((MandelbrotView)view).getCoordString(evt.getX(), evt.getY()));
}
public Cursor getCursor(Display display, View view) {
return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
}
public String getStatusText() {
return I18n.tr("vmm.fractals.Mandelbrot.DragToShowCoordsStatusText");
}
}
/**
* Defines the mouse task that is used in a MandelbrotView. Lets the user
* drag the point c that tells which Julia set/zero-orbit to draw.
*/
private class DragJuliaStartMouseTask extends BasicMouseTask2D {
boolean draggingStartPoint;
int offsetX, offsetY;
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
draggingStartPoint = false;
if (((MandelbrotView)view).getShowJuliaAndOrbit() == MandelbrotView.SHOW_NONE)
return super.doMouseDown(evt,display,view,width,height);
Point2D pt = new Point2D.Double(juliaPointX.getValue(),juliaPointY.getValue());
view.getTransform().windowToViewport(pt);
offsetX = (int)pt.getX() - evt.getX();
offsetY = (int)pt.getY() - evt.getY();
if (Math.abs(offsetX) <= 7 && Math.abs(offsetY) <= 7) {
draggingStartPoint = true;
return true;
}
else {
return super.doMouseDown(evt, display, view, width, height);
}
}
public void doMouseDrag(MouseEvent evt, Display display, View view, int width, int height) {
if (!draggingStartPoint) {
super.doMouseDrag(evt, display, view, width, height);
return;
}
Point2D pt = new Point2D.Double( evt.getX() + offsetX, evt.getY() + offsetY );
view.getTransform().viewportToWindow(pt);
juliaPointX.setValue(pt.getX());
juliaPointY.setValue(pt.getY());
display.setStatusText("(cx,cy) = " + ((MandelbrotView)view).getCoordString(evt.getX(),evt.getY()));
}
public void doMouseUp(MouseEvent evt, Display display, View view, int width, int height) {
if (!draggingStartPoint)
super.doMouseUp(evt, display, view, width, height);
else
display.setStatusText(null);
}
public void drawWhileDragging(org.freehep.graphics2d.VectorGraphics g, Display display, View view, int width, int height) {
if (!draggingStartPoint)
super.drawWhileDragging(g, display, view, width, height);
}
public Cursor getCursorForDragging(MouseEvent evt, Display display, View view) {
if (draggingStartPoint)
return Cursor.getDefaultCursor();
else
return super.getCursorForDragging(evt, display,view);
}
}
private class MoveJuliaStartByMouseClick extends MouseTask {
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
Point2D pt = new Point2D.Double(evt.getX(), evt.getY());
view.getTransform().viewportToWindow(pt);
juliaPointX.setValue(pt.getX());
juliaPointY.setValue(pt.getY());
if ( ((MandelbrotView)view).getShowJuliaAndOrbit() == MandelbrotView.SHOW_NONE)
((MandelbrotView)view).setShowJuliaAndOrbit(MandelbrotView.SHOW_BOTH);
return false;
}
public Cursor getCursor(Display display,View view) {
return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
}
}
/**
* Defines the decoration that shows the outline of a julia set and zero-orbit.
*/
private class JuliaSetAndOrbitDecoration extends Decoration {
Complex[] juliaStart = new Complex[1024];
Complex[] juliaAux = new Complex[1024];
Complex firstFixedPoint, secondFixedPoint;
int currentJuliaLength = 0;
double cx,cy;
public void setStartPoint(Point2D pt) {
cx = pt.getX();
cy = pt.getY();
forceRedraw();
}
private void computeFixedPoints() {
Complex z = new Complex(0.25-cx,-cy);
z = z.power(0.5);
firstFixedPoint = z.plus(0.5);
secondFixedPoint = z.times(-1.0).plus(0.5);
if (firstFixedPoint.r() < secondFixedPoint.r()) {
z = secondFixedPoint;
secondFixedPoint = firstFixedPoint;
firstFixedPoint = z;
}
}
private void makeJuliaStart() {
Complex z;
Complex c = new Complex(cx,cy);
computeFixedPoints();
juliaStart[0] = firstFixedPoint;
juliaStart[2] = firstFixedPoint.times(-1.0);
z = juliaStart[2].minus(c).power(0.5);
if ( (z.re*juliaStart[0].im - z.im*juliaStart[0].re) < 0 )
z = z.times(-1.0);
juliaStart[1] = z;
juliaStart[3] = z.times(-1.0);
currentJuliaLength = 4;
for (int i=1; i < 9; i++) {
juliaAux[0] = juliaStart[0];
juliaAux[0+currentJuliaLength] = juliaStart[0].times(-1.0);
for (int j=1; j < currentJuliaLength; j++){
juliaAux[j] = (juliaStart[j].minus(c)).squareRootNearer(juliaAux[j-1]);
juliaAux[j + currentJuliaLength] = juliaAux[j].times(-1.0);
}
currentJuliaLength = 2*currentJuliaLength;
for (int j=1; j < currentJuliaLength; j++)
juliaStart[j] = juliaAux[j];
}
}
public void doDraw(org.freehep.graphics2d.VectorGraphics g, View view, Transform transform) {
MandelbrotView mbView = (MandelbrotView)view;
if (mbView.getShowJuliaAndOrbit() == MandelbrotView.SHOW_NONE)
return;
Color saveColor = g.getColor();
double zx = cx;
double zy = cy;
Point2D pt = new Point2D.Double();
pt.setLocation(cx,cy);
transform.windowToViewport(pt);
int cxInt = (int)(pt.getX() + 0.499);
int cyInt = (int)(pt.getY() + 0.499);
g.setColor(Color.WHITE);
if (mbView.getShowJuliaAndOrbit() == MandelbrotView.SHOW_JULIA ||
mbView.getShowJuliaAndOrbit() == MandelbrotView.SHOW_BOTH) {
// // From here to jjj have Julia set as mouse task
makeJuliaStart();
for (int i = 0; i < currentJuliaLength; i++) {
zx = juliaStart[i].re;
zy = juliaStart[i].im;
pt.setLocation(zx,zy);
transform.windowToViewport(pt);
int zxInt = (int)(pt.getX() + 0.499);
int zyInt = (int)(pt.getY() + 0.499);
mbView.drawLineDirect( zxInt - 1, zyInt, zxInt + 1, zyInt);
mbView.drawLineDirect( zxInt, zyInt - 1, zxInt, zyInt + 1);
} // // jjj
}
int x1 = cxInt - 7;
int x2 = cxInt + 7;
int y1 = cyInt - 7;
int y2 = cyInt + 7;
g.setColor(Color.BLACK);
mbView.drawLineDirect( cxInt-2, y1, cxInt-2, y2 );
mbView.drawLineDirect( cxInt+1, y1, cxInt+1, y2 );
mbView.drawLineDirect( x1, cyInt-2, x2, cyInt-2 );
mbView.drawLineDirect( x1, cyInt+1, x2, cyInt+1 );
if (mbView.getShowJuliaAndOrbit() == MandelbrotView.SHOW_ORBIT ||
mbView.getShowJuliaAndOrbit() == MandelbrotView.SHOW_BOTH) {
// // From here to end 0-orbit in mouse task
zx = cx;
zy = cy;
int ct = pointsOnOrbit.getValue();
for (int i = 0; i < ct; i++) {
pt.setLocation(zx,zy);
transform.windowToViewport(pt);
int x = (int)pt.getX();
int y = (int)pt.getY();
mbView.fillOvalDirect(Color.getHSBColor( (float)(0.75*(ct-i))/ct, 0.6F, 1),x-3,y-3,5,5);
mbView.drawOvalDirect(Color.GRAY,x-3,y-3,6,6);
double t = zx*zx - zy*zy + cx;
zy = 2*zx*zy + cy;
zx = t;
}
}
g.setColor(Color.WHITE);
mbView.drawLineDirect( cxInt-1, y1, cxInt-1, y2 );
mbView.drawLineDirect( cxInt, y1, cxInt, y2 );
mbView.drawLineDirect( x1, cyInt-1, x2, cyInt-1 );
mbView.drawLineDirect( x1, cyInt, x2, cyInt );
g.setColor(saveColor);
}
} // end class JuliaSetAndOrbitDecoration
}
Ads help maintain this website.