/*
* 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.importing;
import org.pepsoft.util.MathUtils;
import org.pepsoft.util.PerlinNoise;
import org.pepsoft.util.ProgressReceiver;
import org.pepsoft.worldpainter.*;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.heightMaps.BitmapHeightMap;
import org.pepsoft.worldpainter.heightMaps.TransformingHeightMap;
import org.pepsoft.worldpainter.history.HistoryEntry;
import org.pepsoft.worldpainter.layers.Frost;
import org.pepsoft.worldpainter.layers.Layer;
import org.pepsoft.worldpainter.layers.exporters.ExporterSettings;
import org.pepsoft.worldpainter.layers.exporters.FrostExporter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Map;
import static org.pepsoft.minecraft.Constants.DEFAULT_MAX_HEIGHT_2;
import static org.pepsoft.worldpainter.Constants.MEDIUM_BLOBS;
import static org.pepsoft.worldpainter.Constants.TILE_SIZE;
import static org.pepsoft.worldpainter.Constants.TILE_SIZE_BITS;
/**
*
* @author SchmitzP
*/
public class HeightMapImporter {
/**
* Create a new WorldPainter world from the configured height map and import
* settings.
*
* @param progressReceiver The progress receiver to report progress to and
* check for cancellation with.
* @return A new WorldPainter world based on the specified height map.
* @throws org.pepsoft.util.ProgressReceiver.OperationCancelled If and when
* the specified progress received throws it (when the user cancels the
* operation).
*/
public World2 importToNewWorld(ProgressReceiver progressReceiver) throws ProgressReceiver.OperationCancelled {
Rectangle extent = heightMap.getExtent();
logger.info("Importing world from height map {} (size: {}x{})", name, extent.width, extent.height);
final boolean highRes = (imageHighLevel >= maxHeight) && (worldHighLevel < maxHeight);
final World2 world = new World2(World2.DEFAULT_OCEAN_SEED, tileFactory, maxHeight);
world.addHistoryEntry(HistoryEntry.WORLD_IMPORTED_FROM_HEIGHT_MAP, imageFile);
int p = name.lastIndexOf('.');
if (p != -1) {
name = name.substring(0, p);
}
world.setName(name);
final Dimension dimension = world.getDimension(0);
// Export settings
final Configuration config = Configuration.getInstance();
final boolean minecraft11Only = dimension.getMaxHeight() != DEFAULT_MAX_HEIGHT_2;
world.setCreateGoodiesChest(config.isDefaultCreateGoodiesChest());
Generator generator = config.getDefaultGenerator();
if (minecraft11Only && (generator == Generator.LARGE_BIOMES)) {
generator = Generator.DEFAULT;
} else if ((! minecraft11Only) && (generator == Generator.DEFAULT)) {
generator = Generator.LARGE_BIOMES;
}
world.setGenerator(generator);
if (generator == Generator.FLAT) {
world.setGeneratorOptions(config.getDefaultGeneratorOptions());
}
world.setMapFeatures(config.isDefaultMapFeatures());
world.setGameType(config.getDefaultGameType());
world.setAllowCheats(config.isDefaultAllowCheats());
// Turn off smooth snow, except for high res imports
if (! highRes) {
FrostExporter.FrostSettings frostSettings = new FrostExporter.FrostSettings();
frostSettings.setMode(FrostExporter.FrostSettings.MODE_FLAT);
dimension.setLayerSettings(Frost.INSTANCE, frostSettings);
}
importToDimension(dimension, true, progressReceiver);
Dimension defaults = config.getDefaultTerrainAndLayerSettings();
dimension.setBorder(defaults.getBorder());
dimension.setBorderSize(defaults.getBorderSize());
dimension.setBorderLevel(worldWaterLevel);
dimension.setBedrockWall(defaults.isBedrockWall());
dimension.setSubsurfaceMaterial(defaults.getSubsurfaceMaterial());
dimension.setPopulate(defaults.isPopulate());
dimension.setTopLayerMinDepth(defaults.getTopLayerMinDepth());
dimension.setTopLayerVariation(defaults.getTopLayerVariation());
dimension.setBottomless(defaults.isBottomless());
for (Map.Entry<Layer, ExporterSettings> entry: defaults.getAllLayerSettings().entrySet()) {
dimension.setLayerSettings(entry.getKey(), entry.getValue().clone());
}
dimension.setGridEnabled(config.isDefaultGridEnabled());
dimension.setGridSize(config.getDefaultGridSize());
dimension.setContoursEnabled(config.isDefaultContoursEnabled());
dimension.setContourSeparation(config.getDefaultContourSeparation());
world.setSpawnPoint(new Point(extent.x + extent.width / 2, extent.y + extent.height / 2));
dimension.setLastViewPosition(world.getSpawnPoint());
world.setDirty(false);
return world;
}
public void importToDimension(Dimension dimension, boolean createTiles, ProgressReceiver progressReceiver) throws ProgressReceiver.OperationCancelled {
// Sanity checks
if (dimension.getMaxHeight() != maxHeight) {
throw new IllegalArgumentException(String.format("Dimension has different maxHeight (%d) than configured (%d)", dimension.getMaxHeight(), maxHeight));
}
Rectangle extent = heightMap.getExtent();
if (dimension.getWorld() != null) {
dimension.getWorld().addHistoryEntry(HistoryEntry.WORLD_HEIGHT_MAP_IMPORTED_TO_DIMENSION, dimension.getName(), imageFile);
}
final boolean useVoidBelow = voidBelowLevel > 0;
final int x1 = extent.x;
final int x2 = extent.x + extent.width - 1;
final int y1 = extent.y;
final int y2 = extent.y + extent.height - 1;
final int tileX1 = x1 >> TILE_SIZE_BITS;
final int tileY1 = y1 >> TILE_SIZE_BITS;
final int tileX2 = x2 >> TILE_SIZE_BITS;
final int tileY2 = y2 >> TILE_SIZE_BITS;
final int widthInTiles = tileX2 - tileX1 + 1;
final int heightInTiles = tileY2 - tileY1 + 1;
final int totalTileCount = widthInTiles * heightInTiles;
final int floor = Math.max(worldWaterLevel - 20, 0);
final int variation = Math.min(15, (worldWaterLevel - floor) / 2);
final PerlinNoise noiseGenerator = new PerlinNoise(0);
noiseGenerator.setSeed(dimension.getSeed());
int tileCount = 0;
calculateFlags();
for (int tileX = tileX1; tileX <= tileX2; tileX++) {
for (int tileY = tileY1; tileY <= tileY2; tileY++) {
boolean tileIsNew;
Tile tile = dimension.getTileForEditing(tileX, tileY);
if (tile == null) {
if (createTiles) {
tile = tileFactory.createTile(tileX, tileY);
tileIsNew = true;
} else {
tileCount++;
if (progressReceiver != null) {
progressReceiver.setProgress((float) tileCount / totalTileCount);
}
continue;
}
} else {
tileIsNew = false;
tile.inhibitEvents();
}
final int xOffset = tileX << TILE_SIZE_BITS;
final int yOffset = tileY << TILE_SIZE_BITS;
for (int x = 0; x < TILE_SIZE; x++) {
for (int y = 0; y < TILE_SIZE; y++) {
final int imageX = xOffset + x;
final int imageY = yOffset + y;
if ((imageX >= x1) && (imageX <= x2) && (imageY >= y1) && (imageY <= y2)) {
final float imageLevel = heightMap.getHeight(imageX, imageY);
final float height = calculateHeight(imageLevel);
if (onlyRaise && (! tileIsNew)) {
if (height > tile.getHeight(x, y)) {
tile.setHeight(x, y, height);
tileFactory.applyTheme(tile, x, y);
}
} else {
tile.setHeight(x, y, height);
tile.setWaterLevel(x, y, worldWaterLevel);
if (useVoidBelow && (imageLevel < voidBelowLevel)) {
tile.setBitLayerValue(org.pepsoft.worldpainter.layers.Void.INSTANCE, x, y, true);
}
tileFactory.applyTheme(tile, x, y);
}
} else if (tileIsNew) {
tile.setHeight(x, y, floor + (noiseGenerator.getPerlinNoise(imageX / MEDIUM_BLOBS, imageY / MEDIUM_BLOBS) + 0.5f) * variation);
tile.setTerrain(x, y, Terrain.BEACHES);
tile.setWaterLevel(x, y, worldWaterLevel);
if (useVoidBelow) {
tile.setBitLayerValue(org.pepsoft.worldpainter.layers.Void.INSTANCE, x, y, true);
}
}
}
}
if (tileIsNew) {
dimension.addTile(tile);
} else {
tile.releaseEvents();
}
tileCount++;
if (progressReceiver != null) {
progressReceiver.setProgress((float) tileCount / totalTileCount);
}
}
}
}
public BufferedImage getPreview() {
if ((heightMap == null) || (maxHeight != DEFAULT_MAX_HEIGHT_2)) {
return null;
}
BufferedImage preview = new BufferedImage(DEFAULT_MAX_HEIGHT_2, DEFAULT_MAX_HEIGHT_2, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = preview.createGraphics();
try {
for (int x = 0; x < DEFAULT_MAX_HEIGHT_2; x++) {
// TODO
}
} finally {
g2.dispose();
}
return preview;
}
// Properties
public HeightMap getHeightMap() {
return heightMap;
}
public void setHeightMap(HeightMap heightMap) {
if ((heightMap != null) && (heightMap.getExtent() == null)) {
throw new IllegalArgumentException("Height map must have an extent");
}
this.heightMap = heightMap;
}
public int getWorldLowLevel() {
return worldLowLevel;
}
public void setWorldLowLevel(int worldLowLevel) {
this.worldLowLevel = worldLowLevel;
}
public int getWorldWaterLevel() {
return worldWaterLevel;
}
public void setWorldWaterLevel(int worldWaterLevel) {
this.worldWaterLevel = worldWaterLevel;
}
public int getWorldHighLevel() {
return worldHighLevel;
}
public void setWorldHighLevel(int worldHighLevel) {
this.worldHighLevel = worldHighLevel;
}
public int getImageLowLevel() {
return imageLowLevel;
}
public void setImageLowLevel(int imageLowLevel) {
this.imageLowLevel = imageLowLevel;
}
public int getImageHighLevel() {
return imageHighLevel;
}
public void setImageHighLevel(int imageHighLevel) {
this.imageHighLevel = imageHighLevel;
}
public int getMaxHeight() {
return maxHeight;
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
public int getVoidBelowLevel() {
return voidBelowLevel;
}
public void setVoidBelowLevel(int voidBelowLevel) {
this.voidBelowLevel = voidBelowLevel;
}
public TileFactory getTileFactory() {
return tileFactory;
}
public void setTileFactory(TileFactory tileFactory) {
this.tileFactory = tileFactory;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public File getImageFile() {
return imageFile;
}
public void setImageFile(File imageFile) {
this.imageFile = imageFile;
}
public boolean isOnlyRaise() {
return onlyRaise;
}
public void setOnlyRaise(boolean onlyRaise) {
this.onlyRaise = onlyRaise;
}
private void calculateFlags() {
// If the height map is a bitmap height map, or a transforming height map with a scale of 100% and based on a
// bitmap height map, then it is definitely unscaled, meaning we can apply a delta to the bitmap values to make
// each block height 1/8 higher, in order to make smooth snow work less surprisingly
mayBeScaled = ! ((heightMap instanceof BitmapHeightMap)
|| ((heightMap instanceof TransformingHeightMap)
&& (((TransformingHeightMap) heightMap).getScaleX() == 100)
&& (((TransformingHeightMap) heightMap).getScaleY() == 100)
&& (((TransformingHeightMap) heightMap).getBaseHeightMap() instanceof BitmapHeightMap)));
oneOnOne = (worldLowLevel == imageLowLevel) && (worldHighLevel == imageHighLevel);
highRes = (imageHighLevel > 2047) && (! oneOnOne);
levelScale = (float) (worldHighLevel - worldLowLevel) / (imageHighLevel - imageLowLevel);
maxZ = maxHeight - 1;
}
private float calculateHeight(final float imageLevel) {
if (highRes) {
return MathUtils.clamp(0.0f, (imageLevel - imageLowLevel) * levelScale + worldLowLevel, maxZ);
} else {
return MathUtils.clamp(0.0f, oneOnOne
? (mayBeScaled ? imageLevel : (imageLevel - 0.4375f))
: ((imageLevel - imageLowLevel) * levelScale + worldLowLevel), maxZ);
}
}
private HeightMap heightMap;
private int worldLowLevel, worldWaterLevel = 62, worldHighLevel = DEFAULT_MAX_HEIGHT_2 - 1, imageLowLevel, imageHighLevel = DEFAULT_MAX_HEIGHT_2 - 1, maxHeight = DEFAULT_MAX_HEIGHT_2, voidBelowLevel, maxZ;
private TileFactory tileFactory;
private String name;
private boolean onlyRaise, oneOnOne, highRes, mayBeScaled;
private File imageFile;
private float levelScale;
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HeightMapImporter.class);
}