如何迭代绘制分形?



我目前有一个使用递归绘制分形树的工作代码。但是,当我尝试迭代绘制它时,它不起作用。

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
/*
-Used built in Math trig methods to accomodate angling...looked this up online

https://stackoverflow.com/questions/30032635/java-swing-draw-a-line-at-a-specific-angle

*/

public class Test extends JFrame {
public Test() {
setBounds(100, 100, 800, 600);  //sets the boundary for drawing
}
public void drawTree(Graphics g, int x1, int y1, double angle, int depth) {
System.out.println("x");
if (depth == 6){
return; //base case here to prevent infinite recursion..
}
else {

System.out.println("y1");
//embedded portion '(Math.toRadians(angle) * depth * 10.0)'represents angle...
int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0);  //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0);  //hor. shift calculated using the Cos..PARSED to int

//  System.out.println("x2: " + x2);//will reflect the change in vertical shift
//System.out.println("y2: " + y2);//will reflect change in hor. shift
g.drawLine(x1, y1, x2, y2);//value x1 equals previous line...in other word, start at where previously left off
// notice that the end point (x2 and y2) becomes starting point for each successive call

drawTree(g, x2, y2, angle - 20, depth - 1); //DRAWS LEFT SIDE?
// drawTree(g, x2, y2, angle + 20, depth - 1); //DRAWS RIGHT SIDE?
}

if (depth == 6){
return; //base case here to prevent infinite recursion..
}
else {

System.out.println("y2");

int x2 = x1 + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0);  //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = y1 + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0);  //hor. shift calculated using the Cos..PARSED to int

// System.out.println("x2: " + x2);//will reflect the change in vertical shift
//System.out.println("y2: " + y2);//will reflect change in hor. shift
g.drawLine(x1, y1, x2, y2);//value x1 equals previous line...in other word, start at where previously left off
// notice that the end point (x2 and y2) becomes starting point for each successive call

// drawTree(g, x2, y2, angle - 20, depth - 1); //DRAWS LEFT SIDE?
drawTree(g, x2, y2, angle + 20, depth - 1); //DRAWS RIGHT SIDE?
}
}


public void drawIteratively(Graphics g, int x1A, int y1A, int x1B, int y1B, double angleA, double angleB, int depthA, int depthB){
while (depthA != 4) {

int x2A = x1A + (int) (Math.cos(Math.toRadians(angleA)) * depthA * 10.0);
int y2A = y1A + (int) (Math.sin(Math.toRadians(angleA)) * depthA * 10.0);
g.drawLine(x1A, y1A, x2A, y2A);   //remember it must continue drawing from where it left off
angleA = angleA - 20;
depthA = depthA - 1;
x1A = x2A;
y1A = y2A;
}
while (depthA != 4) {

int x2A = x1A + (int) (Math.cos(Math.toRadians(angleA)) * depthA * 10.0);
int y2A = y1A + (int) (Math.sin(Math.toRadians(angleA)) * depthA * 10.0);
g.drawLine(x1A, y1A, x2A, y2A);   //remember it must continue drawing from where it left off
angleA = angleA - 20;
depthA = depthA - 1;
x1A = x2A;
y1A = y2A;
}
/*
while(depthB != 4){

int x2B = x1B + (int) (Math.cos(Math.toRadians(angleB)) * depthB * 10.0);
int y2B = y1B + (int) (Math.sin(Math.toRadians(angleB)) * depthB * 10.0);
g.drawLine(x1B, y1B, x2B, y2B);
angleB = angleB + 20;
depthB = depthB - 1;
x1B = x2B;
y1B = y2B;
}
*/
}



@Override
public void paint(Graphics g) {
g.setColor(Color.BLUE);

//drawTree(g, 400, 400, -90, 9); //these values corresponding to original line aka trunk during initial method call
drawIteratively(g, 400, 500, 400,500 ,-90 , -90, 9,9);


}
public static void main(String[] args) {
new Test().setVisible(true);
}
}

忽略分形数学:如果你想保留递归绘图,用 Swingworker 扭曲长过程(递归计算),让它更新 GUI。下面是一个示例:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class RecursiveDraw extends JFrame {
private int x1A, y1A, x2A, y2A;
private final int W = 700, H = 500;
private Random random = new Random();
private Color randomColor = Color.BLUE;
private JPanel panel;
public RecursiveDraw() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
panel = new MyPanel();
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
new Task().run();
}
public void recursiveDraw(int x1A, int y1A, int depth){
if(depth > 15) { return;}
this.x1A = x1A; this.y1A = y1A;
x2A = random.nextInt(W);
y2A = random.nextInt(H);
randomColor = new Color(random.nextInt(0xFFFFFF));
panel.repaint();
try {
Thread.sleep(1000); //delay
} catch (InterruptedException ex) { ex.printStackTrace();}
recursiveDraw(x2A, y2A, ++depth );
}
class MyPanel extends JPanel{
public MyPanel() {
setPreferredSize(new Dimension(W,H));
}
@Override
public void paintComponent(Graphics g) {
//super.paintComponent(g);  //requires storing all points calculated
//so they can be redrawn. recommended
g.setColor(randomColor);
g.drawLine(x1A, y1A, x2A, y2A);
}
}
class Task extends SwingWorker<Void,Void> {
@Override
public Void doInBackground() {
recursiveDraw(W/2, H/2, 0);
return null;
}
}
public static void main(String[] args) {
new RecursiveDraw();
}
}

使用计时器的迭代绘图的基本结构,如@MadProgrammer所提出的,可能如下所示:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random; 
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TimerIterativeDraw extends JFrame {
private final static int W = 700, H = 500;
private final static int DELAY= 1000;
private final static int NUMBER_OF_DRAWS_LIMIT = 50;
private int x2A = W/2, y2A = H/2, x1A, y1A, numberOfDraws;
private Random random = new Random();
private Color randomColor = Color.BLUE;
private JPanel panel;
private Timer timer;
public TimerIterativeDraw() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
panel = new MyPanel();
add(panel, BorderLayout.CENTER);
pack();
setVisible(true);
timer = new Timer(DELAY,new Task());
timer.start();
}
public void upDateGui(){
if(numberOfDraws++ >= NUMBER_OF_DRAWS_LIMIT){
timer.stop();
}
x1A = x2A; y1A = y2A;
x2A = random.nextInt(W);
y2A = random.nextInt(H);
randomColor = new Color(random.nextInt(0xFFFFFF));
//for better implementation store all points in an array list
//so they can be redrawn
panel.repaint();
}
class MyPanel extends JPanel{
public MyPanel() {
setPreferredSize(new Dimension(W,H));
}
@Override
public void paintComponent(Graphics g) {
//super.paintComponent(g);  //requires storing all points calculated
//so they can be redrawn. recommended
g.setColor(randomColor);
g.drawLine(x1A, y1A, x2A, y2A);
}
}
class Task implements ActionListener {
@Override
public void actionPerformed(ActionEvent arg0) {
upDateGui();
}
}
public static void main(String[] args) {
new TimerIterativeDraw();
}
}

可能有几种方法可以做到这一点,但是...

  • 你需要某种类,你可以调用它来计算下一步并记录它
  • 您需要确保仅从事件调度线程的上下文中更新状态。这很重要,因为您不想更新 UI 或 UI 可能依赖 EDT 之外的任何内容,否则您将面临竞争条件和脏油漆的风险

因此,首先,我们需要某种方法来以某种阶梯方式创建分支。 这个想法是每次告诉类更新时只生成一个新分支。

该类将仅包含它的状态和管理,但将提供对它创建的点List的访问,也许类似于......

public class Generator  {
private List<Point> points;
private double angle;
private double delta;
private int depth = 9;
private Timer timer;
public Generator(Point startPoint, double startAngle, double delta) {
points = new ArrayList<>(25);
points.add(startPoint);
angle = startAngle;
this.delta = delta;
}
public List<Point> getPoints() {
return new ArrayList<Point>(points);
}
public boolean tick() {
Point next = updateTree(points.get(points.size() - 1), angle);
angle += delta;
depth--;
if (next != null) {
points.add(next);
}
return next != null;
}
public Point updateTree(Point p, double angle) {
if (depth == 6) {
return null;
}
System.out.println("depth = " + depth + "; angle = " + angle);
//embedded portion '(Math.toRadians(angle) * depth * 10.0)'represents angle...
int x2 = p.x + (int) (Math.cos(Math.toRadians(angle)) * depth * 10.0);  //vertical shift calculated using the Sine...PARSED to int because method drawLine() accepts only ints as params
int y2 = p.y + (int) (Math.sin(Math.toRadians(angle)) * depth * 10.0);  //hor. shift calculated using the Cos..PARSED to int
return new Point(x2, y2);
}
}

现在,这个类只生成一个分支,为了制作一棵树,你需要这个类的两个实例,具有不同的delta

接下来,我们需要以某种方式要求这个生成器定期生成下一步。 对我来说,这通常使用 SwingTimer调用。

原因是:

  • 很简单。 说真的,这真的很简单
  • 它不会阻止 EDT,因此不会冻结 UI
  • 它在 EDT 的上下文中更新,从而可以安全地从内部更新 UI 的状态。

将这两件事放在一起,形成一个简单的JPanel来控制Timer并绘制要点......

public class TestPane extends JPanel {
private Generator left;
private Generator right;
public TestPane() {
Point startPoint = new Point(200, 400);
left = new Generator(startPoint, -90, -20);
right = new Generator(startPoint, -90, 20);
Timer timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean shouldContinue = left.tick() && right.tick();
if (!shouldContinue) {
((Timer)(e.getSource())).stop();
}
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
render(g2d, left.getPoints());
g2d.setColor(Color.BLUE);
render(g2d, right.getPoints());
g2d.dispose();
}
protected void render(Graphics2D g2d, List<Point> points) {
Point start = points.remove(0);
while (points.size() > 0) {
Point end = points.remove(0);
g2d.draw(new Line2D.Double(start, end));
start = end;
}
}
}

最新更新