/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.themes; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; import org.pepsoft.util.PerlinNoise; import static org.pepsoft.worldpainter.Constants.SMALL_BLOBS; import static org.pepsoft.worldpainter.Constants.TINY_BLOBS; import org.pepsoft.worldpainter.HeightTransform; import org.pepsoft.worldpainter.Terrain; import org.pepsoft.worldpainter.Tile; import org.pepsoft.worldpainter.layers.Frost; import org.pepsoft.worldpainter.layers.Layer; /** * * @author SchmitzP */ public class SimpleTheme implements Theme, Cloneable { @Deprecated public SimpleTheme(long seed, int waterHeight, Terrain[] terrainRangesTable, int maxHeight, boolean randomise, boolean beaches) { setSeed(seed); setWaterHeight(waterHeight); this.maxHeight = terrainRangesTable.length; this.terrainRangesTable = terrainRangesTable; fixTerrainRangesTable(); setMaxHeight(maxHeight, HeightTransform.IDENTITY); setRandomise(randomise); setBeaches(beaches); } public SimpleTheme(long seed, int waterHeight, SortedMap<Integer, Terrain> terrainRanges, Map<Filter, Layer> layerMap, int maxHeight, boolean randomise, boolean beaches) { setSeed(seed); setWaterHeight(waterHeight); this.maxHeight = maxHeight; this.terrainRangesTable = new Terrain[maxHeight]; setTerrainRanges(terrainRanges); fixTerrainRangesTable(); setLayerMap(layerMap); setRandomise(randomise); setBeaches(beaches); } @Override public void apply(Tile tile, int x, int y) { int height = tile.getIntHeight(x, y); Terrain terrain = getTerrain(x, y, clamp(height, maxHeight - 1)); if (tile.getTerrain(x, y) != terrain) { tile.setTerrain(x, y, terrain); } if (layerCache != null) { for (int i = 0; i < layerCache.length; i++) { int level = layerLevelCache[i][height]; if (level != tile.getLayerValue(layerCache[i], x, y)) { tile.setLayerValue(layerCache[i], x, y, level); } } } if (bitLayerCache != null) { for (int i = 0; i < bitLayerCache.length; i++) { int level = bitLayerLevelCache[i][height]; boolean set = (level > 0) && ((level == 15) || (random.nextInt(15) < level)); if (set != tile.getBitLayerValue(bitLayerCache[i], x, y)) { tile.setBitLayerValue(bitLayerCache[i], x, y, set); } } } } @Override public final long getSeed() { return seed; } @Override public final void setSeed(long seed) { this.seed = seed; perlinNoise = new PerlinNoise(seed); } public final SortedMap<Integer, Terrain> getTerrainRanges() { return terrainRanges; } public final void setTerrainRanges(SortedMap<Integer, Terrain> terrainRanges) { this.terrainRanges = terrainRanges; for (int i = 0; i < maxHeight; i++) { terrainRangesTable[i] = terrainRanges.get(terrainRanges.headMap(i).lastKey()); } } public final boolean isRandomise() { return randomise; } public final void setRandomise(boolean randomise) { this.randomise = randomise; } @Override public final int getWaterHeight() { return waterHeight; } @Override public final void setWaterHeight(int waterHeight) { this.waterHeight = waterHeight; } public final boolean isBeaches() { return beaches; } public final void setBeaches(boolean beaches) { this.beaches = beaches; } @Override public final int getMaxHeight() { return maxHeight; } public final void setMaxHeight(int maxHeight) { setMaxHeight(maxHeight, HeightTransform.IDENTITY); } @Override public final void setMaxHeight(int maxHeight, HeightTransform transform) { if (maxHeight != this.maxHeight) { this.maxHeight = maxHeight; waterHeight = clamp(transform.transformHeight(waterHeight), maxHeight - 1); Terrain[] oldTerrainRangesTable = terrainRangesTable; terrainRangesTable = new Terrain[maxHeight]; if (terrainRanges != null) { SortedMap<Integer, Terrain> oldTerrainRanges = this.terrainRanges; terrainRanges = new TreeMap<>(); for (Map.Entry<Integer, Terrain> oldEntry: oldTerrainRanges.entrySet()) { terrainRanges.put(oldEntry.getKey() < 0 ? oldEntry.getKey() : clamp(transform.transformHeight(oldEntry.getKey()), maxHeight - 1), oldEntry.getValue()); } for (int i = 0; i < maxHeight; i++) { terrainRangesTable[i] = terrainRanges.get(terrainRanges.headMap(i).lastKey()); } } else { // No terrain ranges map set; this is probably because it is // an old map. All we can do is extend the last entry System.arraycopy(oldTerrainRangesTable, 0, terrainRangesTable, 0, Math.min(oldTerrainRangesTable.length, terrainRangesTable.length)); if (terrainRangesTable.length > oldTerrainRangesTable.length) { for (int i = oldTerrainRangesTable.length; i < terrainRangesTable.length; i++) { terrainRangesTable[i] = oldTerrainRangesTable[oldTerrainRangesTable.length - 1]; } } } initCaches(); } } public final Map<Filter, Layer> getLayerMap() { return layerMap; } public final void setLayerMap(Map<Filter, Layer> layerMap) { this.layerMap = layerMap; initCaches(); } @Override public Theme clone() { try { SimpleTheme clone = (SimpleTheme) super.clone(); if (terrainRanges != null) { clone.terrainRanges = new TreeMap<>(terrainRanges); } clone.terrainRangesTable = terrainRangesTable.clone(); if (layerMap != null) { clone.setLayerMap(new HashMap<>(layerMap)); } clone.perlinNoise = (PerlinNoise) perlinNoise.clone(); return clone; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } protected Terrain getTerrain(int x, int y, int height) { if (beaches && (height >= (waterHeight - 2)) && (height <= (waterHeight + 1))) { return Terrain.BEACHES; } else { if (isRandomise()) { height += perlinNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, height / SMALL_BLOBS) * 5; height += perlinNoise.getPerlinNoise(x / TINY_BLOBS, y / TINY_BLOBS, height / TINY_BLOBS) * 5; return terrainRangesTable[clamp(height, getMaxHeight() - 1)]; } else { return terrainRangesTable[height]; } } } protected final int clamp(int value, int max) { return (value < 0) ? 0 : ((value > max) ? max : value); } private void initCaches() { if (layerMap != null) { List<Layer> layers = new ArrayList<>(layerMap.size()); List<Layer> bitLayers = new ArrayList<>(layerMap.size()); List<int[]> layerLevels = new ArrayList<>(layerMap.size()); List<int[]> bitLayerLevels = new ArrayList<>(layerMap.size()); layerLevelCache = new int[layerMap.size()][maxHeight]; for (Map.Entry<Filter, Layer> entry: layerMap.entrySet()) { Layer layer = entry.getValue(); Filter filter = entry.getKey(); int[] levels = new int[maxHeight]; for (int z = 0; z < maxHeight; z++) { levels[z] = filter.getLevel(0, 0, z, 15); } if (layer.getDataSize() == Layer.DataSize.BIT) { bitLayers.add(layer); bitLayerLevels.add(levels); } else if (layer.getDataSize() == Layer.DataSize.NIBBLE) { layers.add(layer); layerLevels.add(levels); } else { throw new IllegalArgumentException("Layer with unsupported data size " + layer.getDataSize() + " encountered"); } } if (! layers.isEmpty()) { layerCache = layers.toArray(new Layer[layers.size()]); layerLevelCache = layerLevels.toArray(new int[layerLevels.size()][]); } else { layerCache = null; layerLevelCache = null; } if (! bitLayers.isEmpty()) { bitLayerCache = bitLayers.toArray(new Layer[bitLayers.size()]); bitLayerLevelCache = bitLayerLevels.toArray(new int[bitLayerLevels.size()][]); } else { bitLayerCache = null; bitLayerLevelCache = null; } } else { layerCache = null; bitLayerCache = null; layerLevelCache = null; bitLayerLevelCache = null; } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); fixTerrainRangesTable(); perlinNoise = new PerlinNoise(seed); initCaches(); } /** * This ensures there are no nulls in the terrain ranges table. There * already shouldn't be, but we've had reports from the wild about it * happening, so as a workaround fix it here. TODO: find out how there can * be holes in the terrain ranges table. */ private void fixTerrainRangesTable() { for (int i = 0; i < terrainRangesTable.length; i++) { if (terrainRangesTable[i] == null) { // Least problematic default seems to be bare grass terrainRangesTable[i] = Terrain.BARE_GRASS; } } } public static SimpleTheme createDefault(Terrain topTerrain, int maxHeight, int waterHeight) { return createDefault(topTerrain, maxHeight, waterHeight, false, true); } public static SimpleTheme createDefault(Terrain topTerrain, int maxHeight, int waterHeight, boolean randomise, boolean beaches) { SortedMap<Integer, Terrain> terrainRanges = new TreeMap<>(); float factor = maxHeight / 128f; terrainRanges.put(-1 , topTerrain); terrainRanges.put((int) (32 * factor) + waterHeight, Terrain.PERMADIRT); terrainRanges.put((int) (48 * factor) + waterHeight, Terrain.ROCK); terrainRanges.put((int) (80 * factor) + waterHeight, Terrain.DEEP_SNOW); Map<Filter, Layer> layerMap = new HashMap<>(); layerMap.put(new HeightFilter(maxHeight, (int) (64 * factor) + waterHeight, maxHeight, true), Frost.INSTANCE); return new SimpleTheme(0, waterHeight, terrainRanges, layerMap, maxHeight, randomise, beaches); } private long seed; private int waterHeight, maxHeight; private SortedMap<Integer, Terrain> terrainRanges; private boolean randomise, beaches; private Terrain[] terrainRangesTable; private Map<Filter, Layer> layerMap; private transient PerlinNoise perlinNoise = new PerlinNoise(0); private Layer[] layerCache, bitLayerCache; private int[][] layerLevelCache, bitLayerLevelCache; private static final long SEED_OFFSET = 131; private static final Random random = new Random(); private static final long serialVersionUID = 1L; }