2015-05-03 7 views
7

Я пытаюсь рисовать дерево с помощью рекурсии. Дерево должно выглядеть следующим образом:Рисование дерева с использованием рекурсии

Desired output

Краткое резюме о том, как я должен это сделать:

  • ствол дерева имеет длину length и ширина width
  • Тунец разделяется на две ветви
  • Левый имеет 3/4 длины ствола и правый 2/3 длины ствола
  • левой ширина ветвь 3/4 ширины ствола и правая ширина ветвь 1/2 ширины ствола
  • Параметры, которые мы получаем: длина, min_length, ширину, альфа (все парное)
  • ветви растет до тех пор, пока ветви не будут длиннее min_length

Вот как я решил проблему. Я хотел просто нарисовать сундук, левую ветвь и правую ветвь. Мне удалось это сделать с помощью следующей функции:

public void drawTree(double length, double min_length, double width, double alpha) { 
     //Draws the trunk of the tree 
     StdDraw.setPenRadius(width); 
     StdDraw.line(250, 150, 250, 150+length);   

     //Left branch 
     double hypotenuse = (3.0/4.0)*length; 
     double opposite = Math.sin(alpha) * hypotenuse; 
     double adjacent = Math.sqrt(Math.pow(hypotenuse, 2)-Math.pow(opposite, 2)); 
     StdDraw.setPenRadius(width*3.0/4.0); 
     StdDraw.line(250,150+length,250-adjacent,150+length+opposite); 

     //Right branch 
     double hypotenuse2 = (2.0/3.0)*length; 
     double opposite2 = Math.sin(alpha) * hypotenuse2; 
     double adjacent2 = Math.sqrt(Math.pow(hypotenuse2, 2)-Math.pow(opposite2, 2)); 
     StdDraw.setPenRadius(width*1.0/2.0); 
     StdDraw.line(250,150+length,250+adjacent2,150+length+opposite2);  

    } 

Это выход, и это так же, как я хотел, чтобы это было:

First part

Я думал, остальное будет легко, но за последние 3 часа я ничего не добился: /. Я включил оператор if для условия остановки. Но я не имею представления об рекурсивной части. Я попытался это:

if (length > min_length) { 
    //Left branch 
    double hypotenuse = (3.0/4.0)*length; 
    double opposite = Math.sin(alpha) * hypotenuse; 
    double adjacent = Math.sqrt(Math.pow(hypotenuse, 2)-Math.pow(opposite, 2)); 
    StdDraw.setPenRadius(width*3.0/4.0); 
    StdDraw.line(250,150+length,250-adjacent,150+length+opposite); 
    //Right branch 
    double hypotenuse2 = (2.0/3.0)*length; 
    double opposite2 = Math.sin(alpha) * hypotenuse2; 
    double adjacent2 = Math.sqrt(Math.pow(hypotenuse2, 2)-Math.pow(opposite2, 2)); 
    StdDraw.setPenRadius(width*1.0/2.0); 
    StdDraw.line(250,150+length,250+adjacent2,150+length+opposite2); 
    //My first attempt 
    drawTree(hypotenuse*hypotenuse, min_length, width, alpha); 
    drawTree(hypotenuse2*hypotenuse2, min_length, width, alpha); 
    //My second attempt 
    drawTree(hypotenuse, min_length, width, alpha); 
    drawTree(hypotenuse2, min_length, width, alpha);  
} 

Я понимаю простую рекурсию, как факториалы, палиндром, и т.д., но я застрял на этом, и я был бы признателен за любую помощь.

+0

Вы кажетесь жесткого кодирования точек чертежа, то, что вы не можете сделать, так как их расположение будет относительным к начальной точке, текущей длине ветви и начальному углу. Вместо этого сосредоточьтесь на том, что потребуется для рисования произвольной ветви дерева. Сначала проведите это на бумаге, а не в коде. –

+0

@HovercraftFullOfEels Теперь это делается на бумаге. Я думал о создании переменных x0, y0, x1 и y1. Сначала я задаю им те же значения, с которых я начинаю, а затем обновляю их соответственно. x0 - x1 после того, как мы проведем линию, y0 - y1 ... Я иду в правильном направлении? В настоящий момент x0 вообще не изменяет значение, поэтому это (часть) проблемы? – mythic

+0

Я бы начал с того, что нужно передать в метод 'createBranch (...)'. Возможно, int generationNumber (чтобы вы могли остановить рекурсию при достижении MAX_GENERATION), double angle и double branchLength. Вам также понадобится двойная константа SCALE, которая используется для уменьшения размера следующей ветви. –

ответ

2

Как уже отмечался в комментариях и текущем ответ, это важно сделать drawTree метод агностика которого часть дерева в настоящее время обращаются.

Вы не можете использовать абсолютные координаты в этом методе. И вы должны отслеживать , где вы в настоящее время находитесь. Это можно сделать, например, путем передачи Point2D через рекурсивный метод, который описывает начальную точку текущей части дерева.

Вам даже не нужен явный код для рисования ветвей: Обратите внимание, что в одной строке уже есть дерево. Ветвям тогда являются просто «маленькие деревья»: они снова одиночные линии, но с разной длиной и шириной.

(И с определенным углом по сравнению с предыдущим деревом.Вы не упомянул об этом, но угол, кажется, Math.PI/5 согласно скриншоту)

RecursiveTree

import java.awt.BasicStroke; 
import java.awt.BorderLayout; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.GridLayout; 
import java.awt.RenderingHints; 
import java.awt.geom.Line2D; 
import java.awt.geom.Point2D; 
import java.util.function.DoubleConsumer; 

import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.JSlider; 
import javax.swing.SwingUtilities; 
import javax.swing.event.ChangeEvent; 
import javax.swing.event.ChangeListener; 

public class RecursiveTreeDrawing 
{ 
    public static void main(String[] args) 
    { 
     SwingUtilities.invokeLater(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       createAndShowGUI(); 
      } 
     }); 
    }  

    private static void createAndShowGUI() 
    { 
     JFrame f = new JFrame(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.getContentPane().setLayout(new BorderLayout()); 

     RecursiveTreeDrawingPanel p = new RecursiveTreeDrawingPanel(); 
     p.setPreferredSize(new Dimension(500,500)); 
     f.getContentPane().add(p, BorderLayout.CENTER); 

     JPanel c = new JPanel(new GridLayout(0,1)); 

     c.add(createControl("left length", 0, 0.9, 
      d -> p.setLeftLengthFactor(d))); 
     c.add(createControl("left width", 0, 0.9, 
      d -> p.setLeftWidthFactor(d))); 
     c.add(createControl("left angle", 0, Math.PI, 
      d -> p.setLeftAngleDelta(d))); 

     c.add(createControl("right length", 0, 0.9, 
      d -> p.setRightLengthFactor(d))); 
     c.add(createControl("right width", 0, 0.9, 
      d -> p.setRightWidthFactor(d))); 
     c.add(createControl("right angle", -Math.PI, 0, 
      d -> p.setRightAngleDelta(d))); 
     f.getContentPane().add(c, BorderLayout.SOUTH); 

     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
    } 

    private static JPanel createControl(
     String name, double min, double max, DoubleConsumer doubleConsumer) 
    { 
     JPanel p = new JPanel(new GridLayout(1,0)); 
     p.add(new JLabel(name)); 
     JSlider slider = new JSlider(0, 100, 0); 
     slider.addChangeListener(new ChangeListener() 
     { 

      @Override 
      public void stateChanged(ChangeEvent e) 
      { 
       int value = slider.getValue(); 
       double v = value/100.0; 
       double d = min + v * (max - min); 
       doubleConsumer.accept(d); 
      } 
     }); 
     p.add(slider); 

     return p; 
    } 

} 


class RecursiveTreeDrawingPanel extends JPanel 
{ 
    private double leftLengthFactor = 3.0/4.0; 
    private double leftWidthFactor = 3.0/4.0; 
    private double leftAngleDelta = Math.PI/5.0; 
    private double rightLengthFactor = 2.0/3.0; 
    private double rightWidthFactor = 1.0/2.0; 
    private double rightAngleDelta = - Math.PI/5.0; 

    @Override 
    protected void paintComponent(Graphics gr) 
    { 
     super.paintComponent(gr); 
     Graphics2D g = (Graphics2D)gr; 
     g.setColor(Color.BLACK); 
     g.fillRect(0,0,getWidth(),getHeight()); 
     g.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, 
      RenderingHints.VALUE_ANTIALIAS_ON); 
     Point2D start = new Point2D.Double(
      getWidth() * 0.5, 
      getHeight() * 0.7); 
     g.setColor(Color.GRAY); 
     drawTree(g, start, 100.0, 2.0, 10.0, 0); 
    } 

    private void drawTree(Graphics2D g, 
     Point2D start, double length, double minLength, 
     double width, double alpha) 
    { 
     if (length < minLength) 
     { 
      return; 
     } 
     g.setStroke(new BasicStroke((float)width, 
      BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 
     Point2D end = new Point2D.Double(
      start.getX() + Math.sin(alpha + Math.PI) * length, 
      start.getY() + Math.cos(alpha + Math.PI) * length); 
     g.draw(new Line2D.Double(start, end)); 
     drawTree(g, end, length * leftLengthFactor, minLength, 
      width * leftWidthFactor, alpha + leftAngleDelta); 
     drawTree(g, end, length * rightLengthFactor, minLength, 
      width * rightWidthFactor, alpha + rightAngleDelta); 
    } 

    public void setLeftLengthFactor(double leftLengthFactor) 
    { 
     this.leftLengthFactor = leftLengthFactor; 
     repaint(); 
    } 

    public void setLeftWidthFactor(double leftWidthFactor) 
    { 
     this.leftWidthFactor = leftWidthFactor; 
     repaint(); 
    } 

    public void setLeftAngleDelta(double leftAngleDelta) 
    { 
     this.leftAngleDelta = leftAngleDelta; 
     repaint(); 
    } 

    public void setRightLengthFactor(double rightLengthFactor) 
    { 
     this.rightLengthFactor = rightLengthFactor; 
     repaint(); 
    } 

    public void setRightWidthFactor(double rightWidthFactor) 
    { 
     this.rightWidthFactor = rightWidthFactor; 
     repaint(); 
    } 

    public void setRightAngleDelta(double rightAngleDelta) 
    { 
     this.rightAngleDelta = rightAngleDelta; 
     repaint(); 
    } 

} 
1

Ваш drawTree() слишком сложный. Назовите это drawTrunk и просто нарисуйте ствол дерева. Затем создайте drawTree процедуру, которая выглядит как:

drawTree(basePoint, length, width, angle) 
    if length > min_length 
    drawTrunk(length, width, angle) 
    newBasePoint = top of trunk 
    drawTree(newBasePoint, 3/4. * length, 3/4. * width, angle + 45) 
    drawTree(newBasePoint, 2/3. * length, 2/3. * width, angle - 45) 
Смежные вопросы