/* fractaltree.java -- Fractal tree Swing UI sample using the Graphics Turtle. * * Copyright (C) 2014 Eric Laroche. All rights reserved. * * @author Eric Laroche / laroche@lrdev.com / www.lrdev.com * * This program is free software; * you can redistribute it and/or modify it. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.Stroke; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; // see http://www.lrdev.com/lr/java/javaturtle.html // use http://www.lrdev.com/lr/java/javaturtle.zip (a jar, Java class bytecode and sources) import com.lrdev.turtle.PersistentTurtle; import com.lrdev.turtle.Turtle; public class fractaltree { protected final static double GR = Math.sqrt(1.25) + 0.5; // golden ratio: (r/1)==((r+1)/r) protected final static String TITLE = "fractal tree"; // draw modes, as samples for different approaches, and embedded use of Java graphics private final abstractdraw[] DRAWS = {new simpledraw(), new penupdraw(), new pushstatedraw(), new graphicsdraw(), new colorgraphicsdraw(),}; // parameters; in the Swing UI represented as sliders private final parameter initialDistance_ = new parameter(1, "distance"); // [start] distance is irrelevant -- turtle is centered and scaled private final parameter distanceFactor_ = new parameter(1/GR, 0, 1, 100, "distance factor"); // distance shortening factor private final parameter initialOmega_ = new parameter(45/GR, 0, 180, 1, "omega"); private final parameter omegaFactor_ = new parameter(1, 0, 1, 100, "omega factor"); // default: 1: keep omega constant private final parameter depth_ = new parameter(6, 0, 16, 1, "depth"); // using parameter's double for uniformity's sake private final parameter colorSaturation_ = new parameter(0, 0, 1, 100, "color saturation"); // default: 0: no color, can use PersistentTurtle private final parameter stemFactor_ = new parameter(1, 0, 1, 100, "stem factor"); // default: 1: keep stem constant, can use PersistentTurtle private final parameter drawMode_ = new parameter(0, 0, DRAWS.length - 1, 1, "draw mode"); // using parameter's double for uniformity's sake private final parameter[] parameters_ = {initialDistance_, distanceFactor_, initialOmega_, omegaFactor_, depth_, colorSaturation_, stemFactor_, drawMode_,}; private Rectangle geometry_ = null; private JPanel panel_ = null; private volatile PersistentTurtle turtle_ = null; private volatile int debug_ = 0; public fractaltree() { // } protected final JFrame createFrame() { final JFrame frame = new JFrame(TITLE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); int width = 0, height = 0, x = 0, y = 0; final Rectangle geometry = geometry_; if (geometry != null) { width = geometry.width; height = geometry.height; x = geometry.x; y = geometry.y; } final Rectangle graphicsBounds = frame.getGraphicsConfiguration().getBounds(); // unless specified: use half the screen in each direction // (we don't mind any insets at the moment) if (width == 0 || height == 0) { width = graphicsBounds.width / 2; height = graphicsBounds.height / 2; } // unless specified: centered; use half the screen in each direction if (x == 0 || y == 0) { x = graphicsBounds.x + graphicsBounds.width / 4; y = graphicsBounds.y + graphicsBounds.height / 4; } else { // check for specifications from right and/or bottom if (x < 0) { x += graphicsBounds.width; } if (y < 0) { y += graphicsBounds.height; } } frame.setBounds(x, y, width, height); frame.setPreferredSize(frame.getSize()); // hint to pack() return frame; } protected void createUI(final JFrame frame) { final JPanel panel = new JPanel() { // custom paint part protected void paintComponent(final Graphics g) { super.paintComponent(g); // let draw background fractaltree.this.paintComponent(g); } }; panel.setToolTipText(TITLE); panel_ = panel; // needed for repaint() and getBounds() final JPanel slidersPanel = new JPanel(new GridLayout(0, 1)); // in one column createSliders(slidersPanel); // horizontal split pane: on the left the tree, on the right the sliders for the tree parameters final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panel, slidersPanel); frame.getContentPane().add(splitPane, BorderLayout.CENTER); frame.pack(); SwingUtilities.invokeLater(new Runnable() { public void run() { splitPane.setDividerLocation(1/GR); // relative value doesn't take effect /before/ pack() } }); frame.setVisible(true); redoTree(); } protected final static int scaleUp(final double value, final int scale) { return (int)(value * scale); } protected final static double scaleDown(final int value, final int scale) { return (double)value / scale; } protected String title(final double value, final String name) { return title(String.valueOf(value), name); } protected String title(final String value, final String name) { return name + ": " + value; } protected abstractdraw drawMode() { return DRAWS[(int)drawMode_.getValue()]; } protected String drawModeName() { return drawMode().getClass().getSimpleName(); } protected void enable(final JComponent component, final boolean enabled) { if (component != null) { component.setEnabled(enabled); } } protected void createSliders(final JPanel sliderPanel) { // first parameter is, as mentioned, irrelevant for (int i = 1; i < parameters_.length; i++) { sliderPanel.add(createSlider(parameters_[i])); } } protected JSlider createSlider(final parameter parm) { final String name = parm.getName(); final int scale = parm.getScale(); final double oldValue = parm.getValue(); final JSlider slider = new JSlider(JSlider.HORIZONTAL, scaleUp(parm.getMin(), scale), scaleUp(parm.getMax(), scale), scaleUp(oldValue, scale)); final TitledBorder border = BorderFactory.createTitledBorder(title(oldValue, name)); slider.setBorder(border); parm.setSlider(slider); if (parm == drawMode_) { drawModeDependencies(); } slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { final double newValue = scaleDown(slider.getValue(), scale); border.setTitle(title(newValue, name)); if (!slider.getValueIsAdjusting()) { parm.setValue(newValue); if (debug_>= 1) System.out.println("changed: " + parm.getName() + ": " + parm.getValue()); if (parm == drawMode_) { drawModeDependencies(); } redoTree(); } } }); return slider; } protected void drawModeDependencies() { ((TitledBorder)drawMode_.getSlider().getBorder()).setTitle(title(drawModeName(), drawMode_.getName())); final boolean colorsSupported = (drawMode() instanceof colorgraphicsdraw); enable(colorSaturation_.getSlider(), colorsSupported); enable(stemFactor_.getSlider(), colorsSupported); } protected void redoTree() { SwingUtilities.invokeLater(new Runnable() { public void run() { doTree(); } }); } protected void doTree() { turtle_ = createTree(); final JPanel panel = panel_; if (panel != null) { panel.repaint(); } } protected PersistentTurtle createTree() { final long t = System.currentTimeMillis(); final PersistentTurtle turtle = new PersistentTurtle(); turtle.center(); turtle.setHeadingMode(Turtle.DEGREE); turtle.left(90); // up final Turtle turtle2 = draw(turtle); final long dt = System.currentTimeMillis() - t; if (debug_>= 1 && turtle2 == turtle) System.out.println("turtle: extent " + format(turtle.getExtent()) + " pivot " + format(turtle.getPivot()) + " box " + format(turtle.getMinMax())); if (debug_>= 2 && dt> 0) System.out.println("tree took: " + dt + " ms"); return (turtle2 == turtle ? turtle : null); } private static String format(final double[] a) { if (a == null || a.length == 0) { return "null"; } final StringBuilder sb = new StringBuilder(); for (int i = 0; i < a.length; i++) { if (i> 0) { sb.append(" "); } sb.append(format(a[i])); } return sb.toString(); } private static String format(final double a) { // round to 2 digits after final double rounded = (double)(int)(a * 100 + 0.5) / 100; return String.valueOf(rounded); } protected Turtle draw(final Turtle turtle) { return drawMode().draw(turtle, initialDistance_.getValue(), distanceFactor_.getValue(), initialOmega_.getValue(), omegaFactor_.getValue(), (int)depth_.getValue()); } protected Graphics draw(final Graphics graphics, final Rectangle bounds) { final abstractdraw d = drawMode(); if (d instanceof graphicsdraw) { // catch case the UI system tries to paint before drawn (turtle_==null but not graphicsdraw mode) final graphicsdraw gd = (graphicsdraw)d; if (debug_>= 2) System.out.println("graphics bounds: " + bounds.width + "x" + bounds.height + " at " + bounds.x + "," + bounds.y); final int targetExtent = Math.min(bounds.width, bounds.height) / 2; // extent from center to edge into direction of minimal extent // maximal extent with omega==0 (e.g. 16 with [maximal] factor 1 and [maximal] depth 16); // in the above case (16) the maximal extent into the other dimension is about 14, i.e. near the chosen extent; // maximal y extent in negative direction is about 10.5, also reasonably near to the chosen extent; // i.e. reasonable to use this as overall extent, and no transformation [away from 0,0]; // this mode is also illustrative (keeping transformation and scale independant from omega and omega factor); final double extent = initialDistance_.getValue() * maxExtent(distanceFactor_.getValue(), depth_.getValue()); final double scale = targetExtent / extent; if (debug_>= 2) System.out.println("target extent: " + targetExtent + "; maximal tree extent: " + format(extent) + "; scale: " + format(scale)); final Turtle turtle = new Turtle(graphics, bounds); // live turtle turtle.setHeadingMode(Turtle.DEGREE); turtle.left(90); // up // turtle's 0,0 is already centered, so no turtle.setTransform() needed turtle.setScale(scale, scale); gd.draw(graphics, turtle, initialDistance_.getValue(), distanceFactor_.getValue(), initialOmega_.getValue(), omegaFactor_.getValue(), (int)depth_.getValue()); } else { if (debug_>= 2) System.out.println("skip paint live"); } return graphics; } protected double maxExtent(final double factor, final double n) { // 1 + r + r**2 + r**3 + ... + r**(n-1) == ((1 - (r**n)) / (1 - r)) unless r==1 return (factor == 1 ? n : ((1 - Math.pow(factor, n)) / (1 - factor))); } protected void paintComponent(final Graphics g) { if (debug_>= 2) System.out.println("paintComponent"); final JPanel panel = panel_; if (g == null || panel == null) { return; } final int border = 2; // 2 pixel component border final Rectangle bounds = panel.getBounds(); bounds.width -= 2 * border; bounds.height -= 2 * border; bounds.x += border; bounds.y += border; if (bounds.width <= 0 || bounds.height <= 0) { return; // painting box is too small } final PersistentTurtle turtle = turtle_; if (turtle != null) { if (debug_>= 2) System.out.println("paint PersistentTurtle"); turtle.paint(g, bounds); // PersistentTurtle already drawn, paints now } else { if (debug_>= 2) System.out.println("paint live"); draw(g, bounds); // let a live turtle draw } } public static void main(final String[] args) { final fractaltree f = new fractaltree(); f.doArgs(args); f.createUI(f.createFrame()); } private void doArgs(final String[] args) { for (int i = 0; args != null && i < args.length; i++) { final String arg = args[i]; if (arg.startsWith("-")) { if (arg.equals("-d")) { debug_++; } else if (arg.equals("-geometry")) { geometry_ = parseGeometry(args[i + 1]); // may throw i++; } else if (doParmArg(arg, args, i + 1)) { i++; } } } } private static Rectangle parseGeometry(final String s) { final int x = s.indexOf('x'); if (x == -1) { return null; } final int p = s.indexOf('+', x + 1); final int m = s.indexOf('-', x + 1); if (p == -1 && m == -1) { return new Rectangle(Integer.parseInt(s.substring(0, x)), Integer.parseInt(s.substring(x + 1))); // may throw } final int d = (p == -1 ? m : m == -1 ? p : Math.min(p, m)); final int p2 = s.indexOf('+', d + 1); final int m2 = s.indexOf('-', d + 1); final int d2 = (p2 == -1 ? m2 : m2 == -1 ? p2 : Math.min(p2, m2)); if (d2 == -1) { return null; } return new Rectangle( Integer.parseInt(s.substring(d, d2)), Integer.parseInt(s.substring(d2)), Integer.parseInt(s.substring(0, x)), Integer.parseInt(s.substring(x + 1, d))); // may throw } private boolean doParmArg(final String arg, final String[] args, final int n) { final String a = (arg != null && arg.startsWith("-") ? arg.substring(1) : arg); for (int i = 0; i < parameters_.length; i++) { final parameter p = parameters_[i]; if (equalsIgnoreBlanks(a, p.getName())) { p.setValue(Double.parseDouble(args[n])); // may throw return true; } } return false; } private static boolean equalsIgnoreBlanks(final String a, final String b) { if (a == null && b == null) { return true; } else if (a == null || b == null) { return false; } String aa = a, bb = b; for (;;) { if (aa.equals(bb)) { return true; } else if (aa.startsWith(" ")) { aa = aa.substring(1); } else if (bb.startsWith(" ")) { bb = bb.substring(1); } else if (aa.equals("")) { return false; } else if (bb.equals("")) { return false; } else if (aa.charAt(0) != bb.charAt(0)) { return false; } else { aa = aa.substring(1); bb = bb.substring(1); } } } protected static class parameter { private double value_, min_, max_; private final String name_; private final int scale_; private JSlider slider_ = null; // min_,max_ are adjusted public parameter(final double value, final double min, final double max, final int scale, final String name) { min_ = min(min, max, value); max_ = max(max, min, value); value_ = value; scale_ = scale; name_ = name; } // [don't care about min,max] public parameter(final double value, final String name) { this (value, value, value, 1, name); } // setValue adjusts min_,max_ public void setValue(final double value) { min_ = min(min_, value); max_ = max(max_, value); value_ = value; } public void setSlider(final JSlider slider) { slider_ = slider; } public double getValue() { return value_; } public double getMin() { return min_; } public double getMax() { return max_; } public String getName() { return name_; } public int getScale() { return scale_; } public JSlider getSlider() { return slider_; } protected final static double min(final double a, final double b) { return (b < a ? b : a); } protected final static double max(final double a, final double b) { return (b> a ? b : a); } protected final static double min(final double a, final double b, final double c) { return min(min(a, b), c); } protected final static double max(final double a, final double b, final double c) { return max(max(a, b), c); } } protected abstract class abstractdraw { public abstract Turtle draw(final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth); } protected class simpledraw extends abstractdraw { // draws everything twice, as the pen isn't taken off when going back public Turtle draw(final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { if (depth> 0) { // recursion-end check if (debug_>= 3) System.out.println("forward " + format(distance)); turtle.forward(distance); // trunk if (debug_>= 3) System.out.println("left " + format(omega)); turtle.left(omega); // orient towards left branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); if (debug_>= 3) System.out.println("right " + format(2 * omega)); turtle.right(2 * omega); // orient towards right branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); if (debug_>= 3) System.out.println("left " + format(omega)); turtle.left(omega); // back to origin if (debug_>= 3) System.out.println("back " + format(distance)); turtle.back(distance); } return turtle; } } protected class penupdraw extends abstractdraw { public Turtle draw(final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { if (depth> 0) { // recursion-end check if (debug_>= 3) System.out.println("at depth " + depth + ": draw: " + format(new double[] {distance, distanceFactor, omega, omegaFactor,})); turtle.penDown(); turtle.forward(distance); // trunk turtle.penUp(); turtle.left(omega); // orient towards left branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); turtle.right(2 * omega); // orient towards right branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); turtle.left(omega); // back to origin turtle.back(distance); } return turtle; } } protected class pushstatedraw extends abstractdraw { public Turtle draw(final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { if (depth> 0) { // recursion-end check final double[] state0 = state(turtle); if (debug_>= 3) System.out.println("at depth " + depth + ": turtle state: " + format(state0)); turtle.forward(distance); // trunk final double[] state1 = state(turtle); turtle.left(omega); // orient towards left branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); state(turtle, state1); turtle.right(omega); // orient towards right branch draw(turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, depth - 1); state(turtle, state0); } return turtle; } protected double[] state(final Turtle turtle) { return new double[] { turtle.drawState() ? 1 : 0, turtle.heading(), turtle.getHeadingMode(), turtle.yCor(), turtle.xCor(), }; } protected void state(final Turtle turtle, final double[] state) { turtle.penUp(); // don't draw while restoring/moving (pen state will be [re]set at the end of method) turtle.setXY(state[4], state[3]); // must be before setHeading() turtle.setHeadingMode((int)state[2]); turtle.setHeading(state[1]); // [possible penDown()] must be after a [possible] move [i.e. towards this end] if (state[0] == 0) { turtle.penUp(); } else { turtle.penDown(); } } } protected class graphicsdraw extends abstractdraw { protected final pushstatedraw proxy_ = new pushstatedraw(); public Turtle draw(final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { return null; // signal not to use asynchronous PersistentTurtle } public Graphics draw(final Graphics graphics, final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { proxy_.draw(turtle, distance, distanceFactor, omega, omegaFactor, depth); return graphics; } } protected class colorgraphicsdraw extends graphicsdraw { public Graphics draw(final Graphics graphics, final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final int depth) { if (graphics instanceof Graphics2D) { draw((Graphics2D)graphics, turtle, distance, distanceFactor, omega, omegaFactor, maxStem(stemFactor_.getValue(), depth), stemFactor_.getValue(), depth, depth, colorSaturation_.getValue()); } return graphics; } protected double maxStem(final double factor, final double n) { return (factor == 0 ? Double.POSITIVE_INFINITY : Math.pow(1 / factor, n)); } private double hue(final Color color) { return Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null)[0]; } public Graphics2D draw(final Graphics2D graphics, final Turtle turtle, final double distance, final double distanceFactor, final double omega, final double omegaFactor, final double stem, final double stemFactor, final int depth, final int maxdepth, final double colorSaturation) { if (depth> 0) { // recursion-end check final Object[] gstate0 = state(graphics); final double[] state0 = proxy_.state(turtle); graphics.setStroke(new BasicStroke((float)stem, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); if (colorSaturation != 0) { // hue 0 to 120 deg (0 to 1/3) is red to green final double hue = ((double)(maxdepth - depth) / maxdepth) * (hue(Color.GREEN) - hue(Color.RED)) + hue(Color.RED); final double brightness = 1/GR; graphics.setColor(Color.getHSBColor((float)hue, (float)colorSaturation, (float)brightness)); } turtle.forward(distance); // trunk final double[] state1 = proxy_.state(turtle); turtle.left(omega); // orient towards left branch draw(graphics, turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, stem * stemFactor, stemFactor, depth - 1, maxdepth, colorSaturation); proxy_.state(turtle, state1); turtle.right(omega); // orient towards right branch draw(graphics, turtle, distance * distanceFactor, distanceFactor, omega * omegaFactor, omegaFactor, stem * stemFactor, stemFactor, depth - 1, maxdepth, colorSaturation); proxy_.state(turtle, state0); state(graphics, gstate0); } return graphics; } protected Object[] state(final Graphics2D graphics) { return new Object[] { graphics.getStroke(), graphics.getColor(), }; } protected void state(final Graphics2D graphics, final Object[] state) { graphics.setColor((Color)state[1]); graphics.setStroke((Stroke)state[0]); } } }

AltStyle によって変換されたページ (->オリジナル) /