/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers.exporters;
import org.pepsoft.util.PerlinNoise;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.exporting.*;
import org.pepsoft.worldpainter.layers.DeciduousForest;
import org.pepsoft.worldpainter.layers.GardenCategory;
import org.pepsoft.worldpainter.layers.PineForest;
import org.pepsoft.worldpainter.layers.TreeLayer;
import org.pepsoft.worldpainter.layers.trees.TreeType;
import javax.vecmath.Point3i;
import java.awt.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.util.List;
import java.util.Random;
import static org.pepsoft.minecraft.Block.BLOCKS;
import static org.pepsoft.minecraft.Constants.*;
import static org.pepsoft.worldpainter.Constants.SMALL_BLOBS;
/**
*
* @author pepijn
*/
public class TreesExporter<T extends TreeLayer> extends AbstractLayerExporter<T> implements SecondPassLayerExporter, IncidentalLayerExporter {
public TreesExporter(T layer) {
super(layer, new TreeLayerSettings<>(layer));
}
@Override
public List<Fixup> render(Dimension dimension, Rectangle area, Rectangle exportedArea, MinecraftWorld minecraftWorld) {
TreeLayerSettings<T> settings = (TreeLayerSettings<T>) getSettings();
int minimumLevel = settings.getMinimumLevel();
int treeChance = settings.getTreeChance();
int maxWaterDepth = settings.getMaxWaterDepth();
int layerStrengthCap = settings.getLayerStrengthCap();
int maxZ = dimension.getMaxHeight() - 1;
for (int chunkX = area.x; chunkX < area.x + area.width; chunkX += 16) {
for (int chunkY = area.y; chunkY < area.y + area.height; chunkY += 16) {
// Set the seed and randomizer according to the chunk
// coordinates to make sure the chunk is always rendered the
// same, no matter how often it is rendererd
long seed = dimension.getSeed() + (chunkX >> 4) * 65537 + (chunkY >> 4) * 4099 + layer.hashCode();
Random random = new Random(seed);
for (int x = chunkX; x < chunkX + 16; x++) {
for (int y = chunkY; y < chunkY + 16; y++) {
int height = dimension.getIntHeightAt(x, y);
if ((height == -1) || (height >= maxZ)) {
// height == -1 means there is no tile there
continue;
}
int strength = Math.max(minimumLevel, dimension.getLayerValueAt(layer, x, y));
int cappedStrength = Math.min(strength, layerStrengthCap);
if ((strength > 0) && (random.nextInt(treeChance) <= (cappedStrength * cappedStrength))) {
int waterDepth = dimension.getWaterLevelAt(x, y) - height;
if (waterDepth > maxWaterDepth) {
continue;
}
// Don't build trees on air, or in lava or water, or where there is already a solid block (from another layer)
int blockTypeUnderTree = minecraftWorld.getBlockTypeAt(x, y, height);
int blockTypeAtTree = minecraftWorld.getBlockTypeAt(x, y, height + 1);
if ((blockTypeUnderTree == BLK_AIR)
|| (blockTypeUnderTree == BLK_WATER)
|| (blockTypeUnderTree == BLK_STATIONARY_WATER)
|| (blockTypeAtTree == BLK_LAVA)
|| (blockTypeAtTree == BLK_STATIONARY_LAVA)
|| (! BLOCKS[blockTypeAtTree].veryInsubstantial)) {
continue;
}
// Don't build trees directly next to each other, or
// where there are structures blocking the location
// or extremely near
if (room(dimension, x, y, minecraftWorld)) {
// Plant a tree
renderTree(layer, x, y, height, strength, minecraftWorld, dimension, new Random(seed + x * 65537 + y), seed);
}
}
}
}
}
}
return null;
}
@Override
public Fixup apply(Dimension dimension, Point3i location, int intensity, Rectangle exportedArea, MinecraftWorld minecraftWorld) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
private boolean room(Dimension dimension, int x, int y, MinecraftWorld minecraftWorld) {
return room(dimension, x, y, -1, -1, minecraftWorld)
&& room(dimension, x, y, -1, 0, minecraftWorld)
&& room(dimension, x, y, -1, 1, minecraftWorld)
&& room(dimension, x, y, 0, 1, minecraftWorld)
&& room(dimension, x, y, 0, 0, minecraftWorld)
&& room(dimension, x, y, 1, 1, minecraftWorld)
&& room(dimension, x, y, 1, 0, minecraftWorld)
&& room(dimension, x, y, 1, -1, minecraftWorld)
&& room(dimension, x, y, 0, -1, minecraftWorld);
}
private boolean room(Dimension dimension, int x, int y, int dx, int dy, MinecraftWorld minecraftWorld) {
final int height = dimension.getIntHeightAt(x + dx, y + dy);
return (height >= 0)
&& (height < (dimension.getMaxHeight() - 1))
&& (minecraftWorld.getBlockTypeAt(x + dx, y + dy, height + 1) != BLK_WOOD)
&& (minecraftWorld.getBlockTypeAt(x + dx, y + dy, height + 1) != BLK_WOOD2)
&& (dimension.getLayerValueAt(GardenCategory.INSTANCE, x + dx, y + dy) == GardenCategory.CATEGORY_UNOCCUPIED);
}
private void renderTree(TreeLayer layer, int x, int y, int height, int strength, MinecraftWorld minecraftWorld, Dimension dimension, Random random, long seed) {
TreeType treeRenderer = layer.pickTree(random);
treeRenderer.renderTree(x, y, height, strength, minecraftWorld, dimension, random);
renderMushrooms(x, y, height, strength, minecraftWorld, random, seed);
}
private void renderMushrooms(int blockInWorldX, int blockInWorldY, int height, int strength, MinecraftWorld minecraftWorld, Random random, long seed) {
if (height > (minecraftWorld.getMaxHeight() - 2)) {
return;
}
TreeLayerSettings<T> settings = (TreeLayerSettings<T>) getSettings();
int mushroomIncidence = settings.getMushroomIncidence();
float mushroomChance = settings.getMushroomChance();
PerlinNoise perlinNoise = perlinNoiseRef.get();
if (perlinNoise == null) {
perlinNoise = new PerlinNoise(seed);
perlinNoiseRef.set(perlinNoise);
}
if (perlinNoise.getSeed() != (seed + SEED_OFFSET)) {
perlinNoise.setSeed(seed + SEED_OFFSET);
}
int size = Math.min(2 + strength / 3, 5) + random.nextInt(3);
int r = Math.min(size / 2, 3);
if (r > 0) {
for (int dx = -r; dx <= r; dx++) {
for (int dy = -r; dy <= r; dy++) {
if ((dx != 0) || (dy != 0)) {
int rnd = random.nextInt(mushroomIncidence);
int x = blockInWorldX + dx, y = blockInWorldY + dy;
if ((rnd == 0) && (minecraftWorld.getBlockTypeAt(x, y, height) != BLK_AIR) && (minecraftWorld.getBlockTypeAt(x, y, height + 1) == BLK_AIR)) {
float chance = perlinNoise.getPerlinNoise(x / SMALL_BLOBS, y / SMALL_BLOBS, height / SMALL_BLOBS);
if (chance > mushroomChance) {
minecraftWorld.setBlockTypeAt(x, y, height + 1, BLK_BROWN_MUSHROOM);
} else if (chance < -mushroomChance) {
minecraftWorld.setBlockTypeAt(x, y, height + 1, BLK_RED_MUSHROOM);
}
}
}
}
}
}
}
private final ThreadLocal<PerlinNoise> perlinNoiseRef = new ThreadLocal<>();
private static final long SEED_OFFSET = 61380672;
public static class TreeLayerSettings<T extends TreeLayer> implements ExporterSettings {
public TreeLayerSettings(T layer) {
this.layer = layer;
treeChance = layer.getDefaultTreeChance();
maxWaterDepth = layer.getDefaultMaxWaterDepth();
mushroomChance = layer.getDefaultMushroomChance();
mushroomIncidence = layer.getDefaultMushroomIncidence();
layerStrengthCap = layer.getDefaultLayerStrengthCap();
}
@Override
public boolean isApplyEverywhere() {
return minimumLevel > 0;
}
public int getMinimumLevel() {
return minimumLevel;
}
public void setMinimumLevel(int minimumLevel) {
this.minimumLevel = minimumLevel;
}
public int getMaxWaterDepth() {
return maxWaterDepth;
}
public void setMaxWaterDepth(int maxWaterDepth) {
this.maxWaterDepth = maxWaterDepth;
}
public int getTreeChance() {
return treeChance;
}
public void setTreeChance(int treeChance) {
this.treeChance = treeChance;
}
public int getMushroomIncidence() {
return mushroomIncidence;
}
public void setMushroomIncidence(int mushroomIncidence) {
this.mushroomIncidence = mushroomIncidence;
}
public float getMushroomChance() {
return mushroomChance;
}
public void setMushroomChance(float mushroomChance) {
this.mushroomChance = mushroomChance;
}
public int getLayerStrengthCap() {
return layerStrengthCap;
}
public void setLayerStrengthCap(int layerStrengthCap) {
this.layerStrengthCap = layerStrengthCap;
}
@Override
public T getLayer() {
return layer;
}
@Override
public ExporterSettings clone() {
TreeLayerSettings<T> clone = new TreeLayerSettings<>(layer);
clone.minimumLevel = minimumLevel;
return clone;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Legacy
if (treeChance == 0) {
treeChance = layer.getDefaultTreeChance();
maxWaterDepth = layer.getDefaultMaxWaterDepth();
mushroomChance = layer.getDefaultMushroomChance();
mushroomIncidence = layer.getDefaultMushroomIncidence();
layerStrengthCap = layer.getDefaultLayerStrengthCap();
}
}
private final T layer;
private int minimumLevel, maxWaterDepth, treeChance, mushroomIncidence, layerStrengthCap;
private float mushroomChance;
private static final long serialVersionUID = 1L;
}
// Legacy
@Deprecated
public static class DeciduousSettings implements ExporterSettings {
@Override
public boolean isApplyEverywhere() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public DeciduousForest getLayer() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public ExporterSettings clone() {
throw new UnsupportedOperationException("Not supported");
}
private Object readResolve() throws ObjectStreamException {
TreeLayerSettings<DeciduousForest> settings = new TreeLayerSettings<>(DeciduousForest.INSTANCE);
settings.setMinimumLevel(minimumLevel);
return settings;
}
private int minimumLevel;
private static final long serialVersionUID = 2011060801L;
}
@Deprecated
public static class PineSettings implements ExporterSettings {
@Override
public boolean isApplyEverywhere() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public PineForest getLayer() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public PineSettings clone() {
throw new UnsupportedOperationException("Not supported");
}
private Object readResolve() throws ObjectStreamException {
TreeLayerSettings<PineForest> settings = new TreeLayerSettings<>(PineForest.INSTANCE);
settings.setMinimumLevel(minimumLevel);
return settings;
}
private int minimumLevel;
private static final long serialVersionUID = 2011071601L;
}
}