/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.themes.impl.fancy; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.vecmath.Point3i; import org.pepsoft.worldpainter.Dimension; import org.pepsoft.worldpainter.gardenofeden.Garden; import org.pepsoft.worldpainter.gardenofeden.PathNode; import org.pepsoft.worldpainter.layers.GardenCategory; import org.pepsoft.worldpainter.util.GeometryUtil; /** * * @author pepijn */ public class RiverNode extends PathNode { public RiverNode(Garden garden, Point3i location, int crossSectionalArea) { super(garden, 0, null, location, 1, GardenCategory.CATEGORY_WATER); this.crossSectionalArea = crossSectionalArea; // System.out.println("River started @ " + location); } public RiverNode(Garden garden, RiverNode parent, Point3i location) { super(garden, 0, parent, location, 1, GardenCategory.CATEGORY_WATER); parent.child = this; crossSectionalArea = parent.crossSectionalArea; // System.out.println("Extending river to " + location); } public RiverNode(Garden garden, RiverNode parent, Point3i location, RiverNode child) { super(garden, 0, parent, location, 1, GardenCategory.CATEGORY_WATER); parent.child = this; this.child = child; crossSectionalArea = parent.crossSectionalArea; // System.out.println("Connecting tributary to another river @ " + location); } @Override protected boolean sprout() { boolean endRiver = child != null; if (garden.isWater(location.x, location.y)) { // There is already water here; end the river here // TODO: grow lake? endRiver = true; } if (parent != null) { drawLine(parent.location, location, (int) Math.round(Math.sqrt(crossSectionalArea)), false, category); } if (endRiver) { return (parent != null); } // Find coordinates of lowest point on a surrounding circle // Midpoint circle ("Bresenham's") algorithm final boolean[] lowerPointFound = new boolean[1]; final int[] lowestHeightCoords = new int[2]; int radius = 0; for (int r = 3; (! lowerPointFound[0]) && (r <= 9); r += 2) { radius = r; final float[] lowestHeight = {garden.getHeight(location.x, location.y)}; GeometryUtil.visitCircle(r, (dx, dy, d) -> { float height = garden.getHeight(location.x + dx, location.y + dy); if (height < lowestHeight[0]) { lowestHeight[0] = height; lowestHeightCoords[0] = location.x + dx; lowestHeightCoords[1] = location.y + dy; lowerPointFound[0] = true; } return true; }); } if (lowerPointFound[0]) { // Find existing nodes to connect with (not belonging to the same // river) List<RiverNode> nearbyNodes = garden.findSeeds(RiverNode.class, lowestHeightCoords[0], lowestHeightCoords[1], radius); int ownNodesEncountered = 0; for (RiverNode riverNode: nearbyNodes) { if (riverNode.getOrigin() == getOrigin()) { // Another node of same river ownNodesEncountered++; if (ownNodesEncountered < 2) { continue; } else { // We seem to be circing the drain. Abort! Abort! return true; } } if (riverNode.child != null) { garden.plantSeed(new RiverNode(garden, this, riverNode.location, riverNode.child)); addCrossSectionalArea(riverNode.child, crossSectionalArea); } else { garden.plantSeed(new RiverNode(garden, this, riverNode.location)); } return true; } garden.plantSeed(new RiverNode(garden, this, new Point3i(lowestHeightCoords[0], lowestHeightCoords[1], -1))); return true; } else { // No lower point around; end the river here // TODO: start a lake? return (parent != null); } } public RiverNode getOrigin() { if (parent != null) { return ((RiverNode) parent).getOrigin(); } else { return this; } } public void apply(final Dimension dimension, final Dimension dimensionSnapshot, Set<RiverNode> processedNodes) { if (processedNodes.contains(this)) { logger.error("Loop in river!"); return; } else { processedNodes.add(this); } if (parent != null) { final int d = (int) Math.round(Math.sqrt(crossSectionalArea)); final int r = d / 2; final boolean eastWest = Math.abs(location.x - parent.location.x) > Math.abs(location.y - parent.location.y); doAlongLine(parent.location.x, parent.location.y, location.x, location.y, (x, y) -> { if (d == 1) { if (eastWest) { int lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x, y); dimension.setHeightAt(x, y, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x, y, Math.max(dimension.getWaterLevelAt(x, y), lowestSurroundingDryHeight)); lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x + 1, y); dimension.setHeightAt(x + 1, y, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x + 1, y, Math.max(dimension.getWaterLevelAt(x + 1, y), lowestSurroundingDryHeight)); } else { int lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x, y); dimension.setHeightAt(x, y, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x, y, Math.max(dimension.getWaterLevelAt(x, y), lowestSurroundingDryHeight)); lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x, y + 1); dimension.setHeightAt(x, y + 1, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x, y + 1, Math.max(dimension.getWaterLevelAt(x, y + 1), lowestSurroundingDryHeight)); } } else if (d == 2) { for (int dx = 0; dx < 2; dx++) { for (int dy = 0; dy < 2; dy++) { int lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x + dx, y + dy); dimension.setHeightAt(x + dx, y + dy, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x + dx, y + dy, Math.max(dimension.getWaterLevelAt(x + dx, y + dy), lowestSurroundingDryHeight)); } } } else { GeometryUtil.visitFilledCircle(r - 1, (dx, dy, d1) -> { int lowestSurroundingDryHeight = getLowestSurroundingDryHeight(dimensionSnapshot, x + dx, y + dy); dimension.setHeightAt(x + dx, y + dy, lowestSurroundingDryHeight - 1); dimension.setWaterLevelAt(x + dx, y + dy, Math.max(dimension.getWaterLevelAt(x + dx, y + dy), lowestSurroundingDryHeight)); return true; }); } return true; }); } if (child != null) { child.apply(dimension, dimensionSnapshot, processedNodes); } garden.removeSeed(this); } public int getSlope(int x, int y) { int slope1 = Math.abs(garden.getIntHeight(x - 1, y) - garden.getIntHeight(x + 1, y)); int slope2 = Math.abs(garden.getIntHeight(x - 1, y - 1) - garden.getIntHeight(x + 1, y + 1)); int slope3 = Math.abs(garden.getIntHeight(x, y - 1) - garden.getIntHeight(x, y + 1)); int slope4 = Math.abs(garden.getIntHeight(x + 1, y - 1) - garden.getIntHeight(x - 1, y + 1)); return Math.max(Math.max(slope1, slope2), Math.max(slope3, slope4)); } private void addCrossSectionalArea(RiverNode node, int crossSectionalArea) { Set<RiverNode> processedNodes = new HashSet<>(); while (node != null) { if (processedNodes.contains(node)) { logger.error("Loop in river!"); return; } else { processedNodes.add(node); } node.crossSectionalArea += crossSectionalArea; drawLine(node.parent.location, node.location, (int) Math.round(Math.sqrt(node.crossSectionalArea)), false, category); node = node.child; } } private int getLowestSurroundingDryHeight(Dimension snapshot, int x, int y) { int lowestSurroundingDryHeight = Integer.MAX_VALUE; for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if ((dx != 0) || (dy != 0)) { int height = snapshot.getIntHeightAt(x + dx, y + dy); if ((height >= snapshot.getWaterLevelAt(x + dx, y + dy)) && (height < lowestSurroundingDryHeight)) { if (height == 0) { return 0; } else { lowestSurroundingDryHeight = height; } } } } } return lowestSurroundingDryHeight; } /** * The surface area of the cross section of the river's profile */ private int crossSectionalArea; private RiverNode child; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(RiverNode.class); private static final long serialVersionUID = 1L; }