I wrote this program last Christmas Eve, having forgotten to buy any cards for my family, and thought I might share it this year. The code isn't particularly good, as I wrote most of it when I was a year younger and patched in the tree generator this year (as well as the colour - it was greyscale last year).
Hope you like it.
The jar is here: http://domclark.allalla.com/files/snows … wscape.jar
And the source is here:
Snowscape.java
package org.snowscape; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Random; import javax.imageio.ImageIO; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; @SuppressWarnings("serial") public class Snowscape extends Canvas{ public static final int snowColor = 0xffffff; public static final int skyColor = 0xeeeef5; public static final int hillColor = 0xddddff; public static final int treeColor = 0x776565; public static final int groundColor = 0xe9e9ee; private static final int LEN = 30; private static final double BEND = 0.6; public static final int TREES = 0; public static final int SNOW = 1; public static final int WIDTH = 2; public static final int HEIGHT = 3; public static final int DEPTH = 4; public static final int HILLS = 5; public static final int MIN_RAD = 6; public static final int MAX_RAD = 7; public static final int AVG_HEIGHT = 8; public static final int HEIGHT_RANGE = 9; public static final int SEED = 10; public static int[] configValues = new int[] {7, 8, 480, 360, 150, 30, 40, 50, 30, 3, -1}; public BufferedImage img; public JFrame frame; public ControlPanel panel; public Listener listener; public Snowscape(){ this.listener = new Listener(this); this.panel = new ControlPanel(this.listener); this.frame = new JFrame("Snowscape Generator"); this.frame.setPreferredSize(new Dimension(960, 720)); this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.frame.setLayout(new BorderLayout()); this.frame.add(this, BorderLayout.CENTER); this.frame.add(this.panel, BorderLayout.SOUTH); generate(); this.frame.pack(); this.frame.setVisible(true); } public void generate(){ Random rand; if(configValues[SEED] == -1) rand = new Random(); else rand = new Random(configValues[SEED]); int[] config = Snowscape.configValues; int w = config[WIDTH]; int h = config[HEIGHT]; int[] pixels = new int[w * h]; Arrays.fill(pixels, groundColor); int its = config[SNOW] * w * h / 8192; double[][] hillMap = new double[w][config[DEPTH]]; int hills = config[HILLS]; for(int a = 0; a < hills; a++){ int hr = rand.nextInt(config[MAX_RAD] - config[MIN_RAD]) + config[MIN_RAD]; int hillX = rand.nextInt(w); int hillZ = rand.nextInt(config[DEPTH]); int hillY = rand.nextInt(config[AVG_HEIGHT] - config[HEIGHT_RANGE]) + config[HEIGHT_RANGE] / 2; for(int x = 0; x < 2 * hr; x++){ for(int z = 0; z < 2 * hr; z++){ int xc = hillX + x - hr; int zc = hillZ + z - hr; if(xc >= 0 && xc < w && zc >= 0 && zc < config[DEPTH]){ int dx = xc - hillX; int dz = zc - hillZ; double yc = Math.max(0, (hr * hr - (dx * dx + dz * dz)) / 1000d); hillMap[xc][zc] += hillY * yc; } } } } for(int y = 0; y < config[DEPTH] + 1; y++){ for(int p = 0; p < w; p++){ int col = hillColor; int poscol = skyColor; for(int b = config[DEPTH] - 1; b >= 0; b--){ if(hillMap[p][b] > y){ poscol = col; } if(b % 2 == 0) col -= 0x010101; } pixels[p + (config[DEPTH] - y) * w] = poscol; } } for(int f = 0; f < its; f++){ int flakeX = rand.nextInt(w - 2) + 1; int flakeY = rand.nextInt(h - 4) + 2; pixels[flakeX + flakeY * w] = snowColor; pixels[flakeX + (flakeY - 1) * w] = snowColor; pixels[flakeX + 1 + (flakeY - 2) * w] = snowColor; pixels[flakeX - 1 + (flakeY - 2) * w] = snowColor; pixels[flakeX + 1 + (flakeY + 2) * w] = snowColor; pixels[flakeX - 1 + (flakeY + 2) * w] = snowColor; pixels[flakeX + (flakeY + 1) * w] = snowColor; pixels[flakeX + 1 + flakeY * w] = snowColor; pixels[flakeX - 1 + flakeY * w] = snowColor; pixels[flakeX + 2 + flakeY * w] = snowColor; pixels[flakeX - 2 + flakeY * w] = snowColor; } this.img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); this.img.setRGB(0, 0, w, h, pixels, 0, w); Graphics g = this.img.getGraphics(); g.setColor(new Color(treeColor)); for(int i = 0; i < config[TREES]; i++) tree(3, rand.nextInt(config[WIDTH]), config[DEPTH] + 80, Math.PI, rand, g); g.dispose(); this.repaint(); } public void save(){ JFileChooser jfc = new JFileChooser(); jfc.setDialogTitle("Save Snowscape"); jfc.setMultiSelectionEnabled(false); if(! (jfc.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)) return; File f = jfc.getSelectedFile(); try { ImageIO.write(img, "png", f); } catch (IOException e) { JDialog error = new JDialog(this.frame, "Error"); error.setModal(true); error.setLayout(new java.awt.GridLayout(1, 1)); error.add(new java.awt.Label("Error saving file!")); error.pack(); error.setVisible(true); e.printStackTrace(); } } public void paint(Graphics g){ g.drawImage(img, (this.getWidth() - configValues[WIDTH]) / 2, (this.getHeight() - configValues[HEIGHT]) / 2, null); g.drawRect((this.getWidth() - configValues[WIDTH]) / 2 - 1, (this.getHeight() - configValues[HEIGHT]) / 2 - 1, configValues[WIDTH] + 1, configValues[HEIGHT] + 1); } public void tree(int depth, int x, int y, double angle, Random rand, Graphics g){ int ex = (int) (x - Math.sin(angle) * LEN); int ey = (int) (y + Math.cos(angle) * LEN); g.drawLine(x, y, ex, ey); if(depth <= 0) return; if(rand.nextInt(3) == 0) tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g); tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g); tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g); } private double rand(Random rand){ return (1 - (rand.nextDouble() * rand.nextDouble())) * (rand.nextInt(2) * 2 - 1); } public static void main(String[] args) { new Snowscape(); } }
ConfigDialog.java
package org.snowscape; import java.awt.Button; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Label; import java.awt.TextField; import javax.swing.JDialog; import javax.swing.JFrame; public class ConfigDialog extends JDialog { private static final long serialVersionUID = 1L; public static String[] fields = new String[] {"Trees", "Snow density", "Width", "Height", "Terrain depth", "Hills", "Min hill radius", "Max hill radius", "Average hill height", "Hill height range", "Random seed"}; public Label[] labels; public TextField[] boxes; public ConfigDialog(JFrame f, Listener l) { super(f); this.setTitle("Configure Snowscape"); this.setModal(true); this.setLayout(new GridLayout(fields.length + 1, 2)); this.setPreferredSize(new Dimension(230, (fields.length + 1) * 24 + 3)); labels = new Label[fields.length]; boxes = new TextField[fields.length]; for(int i = 0; i < fields.length; i++){ labels[i] = new Label(fields[i]); boxes[i] = new TextField("" + Snowscape.configValues[i]); this.add(labels[i]); this.add(boxes[i]); } Button ok = new Button("OK"); ok.addActionListener(l); Button cancel = new Button("Cancel"); cancel.addActionListener(l); this.add(ok); this.add(cancel); this.pack(); } public void writeValues() { try{ for(int i = 0; i < Snowscape.configValues.length; i++){ Snowscape.configValues[i] = Integer.parseInt(this.boxes[i].getText()); } } catch(NumberFormatException e) { } } }
ControlPanel.java
package org.snowscape; import java.awt.Button; import java.awt.Panel; public class ControlPanel extends Panel { private static final long serialVersionUID = 1L; public Button[] buttons; public static String[] titles = new String[] {"Configure", "Regenerate", "Save...", "Quit"}; public ControlPanel(Listener l){ for(String s : ControlPanel.titles){ Button b = new Button(s); b.addActionListener(l); this.add(b); } } }
Listener.java
package org.snowscape; import java.awt.Button; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.HashMap; public class Listener implements MouseListener, ActionListener { public Snowscape owner; public HashMap<String, Integer> eventMap = new HashMap<String, Integer>(); public ConfigDialog dialog; public Listener(Snowscape snowscape) { this.owner = snowscape; eventMap.put(null, -1); String[] events = ControlPanel.titles; for(int i = 0; i < events.length; i++){ eventMap.put(events[i], i); } eventMap.put("OK", 100); eventMap.put("Cancel", 101); } public void mouseClicked(MouseEvent arg0) { } public void mouseEntered(MouseEvent arg0) { } public void mouseExited(MouseEvent arg0) { } public void mousePressed(MouseEvent arg0) { } public void mouseReleased(MouseEvent arg0) { } public void actionPerformed(ActionEvent arg0) { switch(idForEvent(arg0)){ case 0: this.dialog = new ConfigDialog(owner.frame, owner.listener); this.dialog.setVisible(true); break; case 1: owner.generate(); break; case 2: owner.save(); break; case 3: System.exit(0); break; case 100: this.dialog.writeValues(); case 101: this.dialog.dispose(); break; } } public int idForEvent(ActionEvent event){ Object source = event.getSource(); if(!(source instanceof Button)) return -1; Button b = (Button) source; Object ret = eventMap.get(b.getLabel()); if (ret == null) return -1; return (Integer) ret; } }
Offline
Nice - I love the hills in the background.
Offline
Nice! The trees are cool, though sometimes you get two close to each other and it gets confusing with too many lines.
Offline