/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.gardenofeden; import java.awt.Point; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Random; import javax.vecmath.Point3i; import org.pepsoft.worldpainter.CoordinateTransform; import org.pepsoft.worldpainter.Dimension; import org.pepsoft.worldpainter.Tile; import org.pepsoft.worldpainter.exporting.MinecraftWorld; import org.pepsoft.worldpainter.layers.GardenCategory; /** * A seed for planting in a {@link Garden} for creating complex random * structures that follow rules. A seed has a germination time, which is the * number of ticks after planting when it will try to sprout. When it germinates * it checks whether there is room to sprout and if so does so, optionally * planting new seeds in the process. * * <p>Seeds may have parents and form a hierarchy, in which case parents will * always sprout before children (and therefore children will not sprout if * their parent hasn't), and parents will also be exported before their * children during the export process. * * @author pepijn */ public abstract class Seed implements Serializable, org.pepsoft.util.undo.Cloneable<Seed> { /** * Create a new seed. * * @param garden The garden in which the seed will be planted. * @param seed The random seed which it may use for seeding pseudo random * number generators. * @param parent The parent of this seed, if any. May be <code>null</code>. * @param location The location of the seed. The z coordinate may be -1, * meaning "on the surface", or it may be zero or higher to * indicate a specific height. * @param germinationTime The number of ticks after planting when the seed * will germinate. If positive a random deviation * will be applied. If negative it will be made * positive <em>without</em> applying a random * deviation. * @param category The category of seed, as one of the * <code>CATEGORY_*</code> constants in the {@link * GardenCategory} class. */ public Seed(Garden garden, long seed, Seed parent, Point3i location, int germinationTime, int category) { this.id = nextId++; this.garden = garden; this.parent = parent; this.location = location; if (germinationTime == 0) { this.germinationTime = 0; sprouted = true; } else if (germinationTime < 0) { this.germinationTime = -germinationTime; } else { this.germinationTime = Math.max(1, (int) (germinationTime * (staticRandom.nextDouble() + 0.6))); } this.category = category; this.seed = seed; } /** * Get the garden in which the seed is planted. * * @return The garden in which the seed is planted. */ public final Garden getGarden() { return garden; } /** * Get this seed's parent, if any, * * @return This seed's parent, or <code>null</code> if it has none. */ public final Seed getParent() { return parent; } /** * Get this seed's location. The z coordinate may be -1, meaning "on the * surface", or it may be zero or higher to indicate a specific height. * * @return This seed's location. */ public final Point3i getLocation() { return location; } /** * Tick the seed over. This involves determining whether it is time to * sprout yet, and if so, attempt to sprout. It is time to sprout when the * germination time has passed, and the parent (if it exits) has sprouted. * If sprouting fails, the seed is removed from the garden. */ public final void tick() { if ((parent != null) && (! parent.sprouted)) { if (! parent.isFinished()) { // Parent is alive and not yet sprouted. Wait until it has // sprouted before sprouting ourselves return; } else { // Parent has died without sprouting. Don't sprout ourselves germinationTime = 0; return; } } else if (germinationTime > 0) { // We have no parent, or our parent has sprouted germinationTime--; if (germinationTime == 0) { sprouted = sprout(); if (! sprouted) { garden.removeSeed(this); } } } } /** * Determine whether the seed is "alive", i.e. has not tried to sprout yet. * * @return <code>true</code> if the seed is dead, i.e. it has attempted to * sprout, which either succeeded or failed. */ public final boolean isFinished() { return germinationTime <= 0; } /** * Determine whether the seed has successfully sprouted. * * @return <code>true</code> if the seed has successfully sprouted. */ public final boolean isSprouted() { return sprouted; } /** * "Kill" the seed, i.e. stop if from ever germinating. */ public final void neutralise() { germinationTime = 0; } /** * Perform the first export pass. The seed may make what changes it wishes * to the passed in Minecraft map. At this point the terrain has been * rendered, all custom layers have been exported, and if the seed has a * parent, its first (but not second) export pass has been executed. * * <p>For buildings it may be advantageous to export the exteriors in the * first pass. * * @param dimension The dimension which is being exported. * @param tile The tile which is being exported. Note that the seed does * <em>not</em> have to constrain its changes to the area of the * tile. * @param minecraftWorld The Minecraft map to which the seed should export * itself. */ public void buildFirstPass(Dimension dimension, Tile tile, MinecraftWorld minecraftWorld) { // Do nothing } /** * Perform the second export pass. The seed may make what changes it wishes * to the passed in Minecraft map. At this point the terrain has been * rendered, all custom layers have been exported, the first pass of all * seeds has been executed, and if the seed has a parent, its second export * pass has been executed. * * <p>For buildings it may be advantageous to export the interiors in the * second pass. * * @param dimension The dimension which is being exported. * @param tile The tile which is being exported. Note that the seed does * <em>not</em> have to constrain its changes to the area of the * tile. * @param minecraftWorld The Minecraft map to which the seed should export * itself. */ public void buildSecondPass(Dimension dimension, Tile tile, MinecraftWorld minecraftWorld) { // Do nothing } /** * Transform the seed's location and rotation according to some coordinate * transform. * * @param rotation The coordinate transform to apply to the seed's location * and rotation. */ public void transform(CoordinateTransform rotation) { rotation.transformInPlace(location); } @Override public Seed clone() { try { return (Seed) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } @Override public boolean equals(Object obj) { return (obj instanceof Seed) && (id == ((Seed) obj).id); } @Override public int hashCode() { // Note that the ID changes whenever this class is deserialized. This // should be no problem, because (in this codebase) the hash code is // only used by the collection classes, and their serialized format is // independent of the hash codes of their contents return (int) (this.id ^ (this.id >>> 32)); } /** * Try to sprout the seed. This may fail if not all conditions are met, such * as if there is no space for it to sprout. Will only be invoked once, * after the parent seed (if any) has sprouted and the germination time has * elapsed. If it returns <code>false</code>, the seed will be considered to * have died, and be removed from the garden automatically. * * @return <code>true</code> if the seed sprouted successfully, * <code>false</code> if it could not sprout for whatever reason. */ protected abstract boolean sprout(); // protected final void reset(int germinationTime) { // this.germinationTime = Math.max(1, (int) (germinationTime * (staticRandom.nextDouble() + 0.6))); // } /** * Utility method for setting the seed category on the garden along a * straight line. * * @param location1 The start location of the line. * @param location2 The end location of the line. * @param diameter The thickness of the line. * @param stopWhenOccupied Whether to stop drawing the line upon reaching a * location which is already occupied. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(Point location1, Point location2, int diameter, boolean stopWhenOccupied, int category) { int length = Integer.MAX_VALUE; if (stopWhenOccupied) { length = scanLine(location1.x, location1.y, location2.x, location2.y); } drawLine(location1, location2, diameter, length, category); } /** * Utility method for setting the seed category on the garden along a * straight line. * * @param location1 The start location of the line. * @param location2 The end location of the line. * @param diameter The thickness of the line. * @param stopWhenOccupied Whether to stop drawing the line upon reaching a * location which is already occupied. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(Point3i location1, Point3i location2, int diameter, boolean stopWhenOccupied, int category) { int length = Integer.MAX_VALUE; if (stopWhenOccupied) { length = scanLine(location1.x, location1.y, location2.x, location2.y); } drawLine(location1, location2, diameter, length, category); } /** * Utility method for setting the seed category on the garden along a * straight line. * * @param location1 The start location of the line. * @param location2 The end location of the line. * @param diameter The thickness of the line. * @param maxLength The maximum lengh of the line. If this is shorter than * the distance between <code>location1</code> and * <code>location2</code>, only the first * <code>maxLength</code> blocks of the line will be * painted. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(Point location1, Point location2, int diameter, int maxLength, int category) { if (diameter > 1) { int offset = diameter / 2; for (int dx = 0; dx < diameter; dx++) { for (int dy = 0; dy < diameter; dy++) { if ((dx != offset) || (dy != offset)) { drawLine(location1.x + dx - offset, location1.y + dy - offset, location2.x + dx - offset, location2.y + dy - offset, maxLength, category); } } } } else { drawLine(location1.x, location1.y, location2.x, location2.y, maxLength, category); } } /** * Utility method for setting the seed category on the garden along a * straight line. * * @param location1 The start location of the line. * @param location2 The end location of the line. * @param diameter The thickness of the line. * @param maxLength The maximum lengh of the line. If this is shorter than * the distance between <code>location1</code> and * <code>location2</code>, only the first * <code>maxLength</code> blocks of the line will be * painted. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(Point3i location1, Point3i location2, int diameter, int maxLength, int category) { if (diameter > 1) { int offset = diameter / 2; for (int dx = 0; dx < diameter; dx++) { for (int dy = 0; dy < diameter; dy++) { if ((dx != offset) || (dy != offset)) { drawLine(location1.x + dx - offset, location1.y + dy - offset, location2.x + dx - offset, location2.y + dy - offset, maxLength, category); } } } } else { drawLine(location1.x, location1.y, location2.x, location2.y, maxLength, category); } } /** * Utility method for testing how much of a straight line is unoccupied. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @return The number of blocks along the line, from the start, that are * unoccupied. Equal to the length of the line if it is entirely * unoccupied. <strong>Note</strong> that if the <em>start</em> of the * line is already occupied, that location is not counted as occupied, * nor are any subsequent occupied locations until an unoccupied * location is encountered. */ protected final int scanLine(int x1, int y1, int x2, int y2) { if ((x1 == x2) && (y1 == y2)) { return 1; } int dx = x2 - x1; int dy = y2 - y1; boolean armed = false; if (Math.abs(dx) > Math.abs(dy)) { float y = y1, offset = (float) dy / Math.abs(dx); dx = (dx < 0) ? -1 : 1; for (int x = x1; x != x2; x += dx) { if (armed && garden.isOccupied(x, (int) y)) { return Math.abs(x - x1); } else { armed = true; } y += offset; } return Math.abs(x2 - x1); } else { float x = x1, offset = (float) dx / Math.abs(dy); dy = (dy < 0) ? -1 : 1; for (int y = y1; y != y2; y += dy) { if (armed && garden.isOccupied((int) x, y)) { return Math.abs(y - y1); } else { armed = true; } x += offset; } return Math.abs(y2 - y1); } } /** * Utility method for setting the seed category on the garden along a * straight line. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param maxLength The maximum lengh of the line. If this is shorter than * the distance between <code>location1</code> and * <code>location2</code>, only the first * <code>maxLength</code> blocks of the line will be * painted. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(int x1, int y1, int x2, int y2, int maxLength, int category) { if ((x1 == x2) && (y1 == y2)) { garden.setCategory(x1, y1, category); return; } int dx = x2 - x1; int dy = y2 - y1; if (Math.abs(dx) > Math.abs(dy)) { float y = y1, offset = (float) dy / Math.abs(dx); dx = (dx < 0) ? -1 : 1; for (int x = x1; (x != x2) && (maxLength > 0); x += dx) { garden.setCategory(x, (int) y, category); y += offset; maxLength--; } } else { float x = x1, offset = (float) dx / Math.abs(dy); dy = (dy < 0) ? -1 : 1; for (int y = y1; (y != y2) && (maxLength > 0); y += dy) { garden.setCategory((int) x, y, category); maxLength--; x += offset; } } } /** * Utility method for setting the seed category on the garden along a * straight line, regardless of what is already there. * * @param location1 The start location of the line. * @param location2 The end location of the line. * @param category The seed category with which to mark the line, as one of * the <code>CATEGORY_*</code> constants in the * {@link GardenCategory} class. */ protected final void drawLine(Point location1, Point location2, int category) { drawLine(location1.x, location1.y, location2.x, location2.y, Integer.MAX_VALUE, category); } protected final void fill(int x1, int y1, int width, int height, int category) { for (int x = x1; x < x1 + width; x++) { for (int y = y1; y < y1 + height; y++) { garden.setCategory(x, y, category); } } } /** * Utility method for performing some arbitrary task along a straight line, * regardless of whether it is occupied or not. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param task The task to perform at each loction along the specified line. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, Task task) { doAlongLine(x1, y1, x2, y2, task, 1); } /** * Utility method for performing some arbitrary task at some interval along * a straight line, regardless of whether it is occupied or not. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param task The task to perform at each specified interval along the * specified line. * @param every The interval between performances of the specified task. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, Task task, int every) { doAlongLine(x1, y1, x2, y2, Integer.MAX_VALUE, task, every); } /** * Utility method for performing some arbitrary task along a straight line. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param stopWhenOccupied Whether to stop performing the task upon reaching * a location which is occupied. * @param task The task to perform at each location along the specified line. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, boolean stopWhenOccupied, Task task) { doAlongLine(x1, y1, x2, y2, stopWhenOccupied, task, 1); } /** * Utility method for performing some arbitrary task at some interval along * a straight line. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param stopWhenOccupied Whether to stop performing the task upon reaching * a location which is occupied. * @param task The task to perform at each specified interval along the * specified line. * @param every The interval between performances of the specified task. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, boolean stopWhenOccupied, Task task, int every) { int length = stopWhenOccupied ? scanLine(x1, y1, x2, y2) : Integer.MAX_VALUE; doAlongLine(x1, y1, x2, y2, length, task, every); } /** * Utility method for performing some arbitrary task along a straight line. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param maxLength The maximum length for which to perform the task. If * this is shorter than the distance between * <code>location1</code> and <code>location2</code>, the * task will only be performed for the first * <code>maxLength</code> blocks of the line. * @param task The task to perform at each location along the specified * line. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, int maxLength, Task task) { doAlongLine(x1, y1, x2, y2, maxLength, task, 1); } /** * Utility method for performing some arbitrary task at some interval along * a straight line. * * @param x1 The X coordinate of the start of the line. * @param y1 The Y coordinate of the start of the line. * @param x2 The X coordinate of the end of the line. * @param y2 The Y coordinate of the end of the line. * @param maxLength The maximum length for which to perform the task. If * this is shorter than the distance between * <code>location1</code> and <code>location2</code>, the * task will only be performed for the first * <code>maxLength</code> blocks of the line. * @param task The task to perform at each specified interval along the * specified line. * @param every The interval between performances of the specified task. */ protected final void doAlongLine(int x1, int y1, int x2, int y2, int maxLength, Task task, int every) { if ((x1 == x2) && (y1 == y2)) { task.perform(x1, y1); return; } int dx = x2 - x1; int dy = y2 - y1; int count = every / 2; if (Math.abs(dx) > Math.abs(dy)) { float y = y1, offset = (float) dy / Math.abs(dx); dx = (dx < 0) ? -1 : 1; for (int x = x1; (x != x2) && (maxLength > 0); x += dx) { if (((count % every) == 0) && (! task.perform(x, (int) y))) { return; } y += offset; maxLength--; count++; } } else { float x = x1, offset = (float) dx / Math.abs(dy); dy = (dy < 0) ? -1 : 1; for (int y = y1; (y != y2) && (maxLength > 0); y += dy) { if (((count % every) == 0) && (! task.perform((int) x, y))) { return; } x += offset; maxLength--; count++; } } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // Reassign unique ID. Don't store it, because that might cause ID // clashes when adding seeds to a world with existing seeds. This will // cause the hash code to change, but that should be no problem because // the collection classes' serialized formats are independent of the // hash codes of their contents id = nextId++; } public final Point3i location; public transient Garden garden; public final Seed parent; public final int category; public final long seed; /** * Unique ID field used to keep equals() and hashcode() working even when * the instance is cloned. Looking at the location and possibly the type * instead seems like it should be good enough, but it is hard to predict * whether in the future there might be a reason to have multiple seeds at * the same location, possibly even of the same type. */ private transient long id; private int germinationTime; private boolean sprouted = false; private static long nextId = 1; private static final Random staticRandom = new Random(); private static final long serialVersionUID = 1L; @FunctionalInterface public interface Task { boolean perform(int x, int y); } }