/* * WorldPainter, a graphical and interactive map generator for Minecraft. * Copyright © 2011-2015 pepsoft.org, The Netherlands * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * 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.tools.scripts; import org.pepsoft.worldpainter.Dimension; import org.pepsoft.worldpainter.HeightMap; import org.pepsoft.worldpainter.Terrain; import org.pepsoft.worldpainter.World2; import org.pepsoft.worldpainter.heightMaps.TransformingHeightMap; import org.pepsoft.worldpainter.layers.Annotations; import org.pepsoft.worldpainter.layers.Biome; import org.pepsoft.worldpainter.layers.Layer; import org.pepsoft.worldpainter.operations.Filter; import org.pepsoft.worldpainter.panels.DefaultFilter; import java.awt.*; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static org.pepsoft.worldpainter.Constants.*; /** * * @author SchmitzP */ public class MappingOp extends AbstractOperation<Void> { public MappingOp(ScriptingContext context, HeightMap heightMap) throws ScriptException { super(context); if (heightMap == null) { throw new ScriptException("heightMap may not be null"); } this.heightMap = heightMap; Arrays.fill(mapping, -1); } public MappingOp(ScriptingContext context, Layer layer) throws ScriptException { super(context); if (layer == null) { throw new ScriptException("layer may not be null"); } this.layer = layer; switch (layer.dataSize) { case BIT: case BIT_PER_CHUNK: layerValue = 1; break; case NIBBLE: layerValue = 8; break; case BYTE: layerValue = 128; break; default: throw new ScriptException("Layer type " + layer.getClass().getSimpleName() + " not supported"); } Arrays.fill(mapping, -1); } public MappingOp(ScriptingContext context, int terrainIndex) throws ScriptException { super(context); if ((terrainIndex < 0) || (terrainIndex >= Terrain.VALUES.length)) { throw new ScriptException("Invalid terrain index specified"); } this.terrainIndex = terrainIndex; mode = Mode.SET_TERRAIN; Arrays.fill(mapping, -1); } public MappingOp applyToLayer(Layer layer) { this.layer = layer; if (mode == Mode.SET_TERRAIN) { mode = Mode.SET; } return this; } public MappingOp applyToTerrain() { layer = null; mode = Mode.SET_TERRAIN; return this; } public MappingOp toWorld(World2 world) { this.world = world; return this; } public MappingOp applyToSurface() { this.dimIndex = DIM_NORMAL; return this; } public MappingOp applyToNether() { this.dimIndex = DIM_NETHER; return this; } public MappingOp applyToEnd() { this.dimIndex = DIM_END; return this; } public MappingOp applyToSurfaceCeiling() { this.dimIndex = DIM_NORMAL_CEILING; return this; } public MappingOp applyToNetherCeiling() { this.dimIndex = DIM_NETHER_CEILING; return this; } public MappingOp applyToEndCeiling() { this.dimIndex = DIM_END_CEILING; return this; } public MappingOp fromLevel(int level) throws ScriptException { if (! colourMapping.isEmpty()) { throw new ScriptException("Cannot mix grey scale and colour mapping"); } storedLowerFrom = level; storedUpperFrom = level; return this; } public MappingOp fromLevels(int lower, int upper) throws ScriptException { if (! colourMapping.isEmpty()) { throw new ScriptException("Cannot mix grey scale and colour mapping"); } storedLowerFrom = lower; storedUpperFrom = upper; return this; } public MappingOp fromColour(int red, int green, int blue) throws ScriptException { if ((red < 0) || (red > 255)) { throw new ScriptException("Invalid red value " + red + " specified"); } if ((green < 0) || (green > 255)) { throw new ScriptException("Invalid green value " + green + " specified"); } if ((blue < 0) || (blue > 255)) { throw new ScriptException("Invalid blue value " + blue + " specified"); } storedColour = 0xff000000 | (red << 16) | (green << 8) | blue; return this; } public MappingOp fromColour(int alpha, int red, int green, int blue) throws ScriptException { if ((alpha < 0) || (alpha > 255)) { throw new ScriptException("Invalid alpha value " + alpha + " specified"); } if ((red < 0) || (red > 255)) { throw new ScriptException("Invalid red value " + red + " specified"); } if ((green < 0) || (green > 255)) { throw new ScriptException("Invalid green value " + green + " specified"); } if ((blue < 0) || (blue > 255)) { throw new ScriptException("Invalid blue value " + blue + " specified"); } storedColour = (alpha << 24) | (red << 16) | (green << 8) | blue; return this; } public MappingOp toLevel(int level) throws ScriptException { if ((level < 0) || (level > 255)) { throw new ScriptException("Illegal value for layer: " + level); } if (storedColour != -1) { colourMapping.put(storedColour, level); storedColour = -1; } else { for (int i = storedLowerFrom; i <= storedUpperFrom; i++) { mapping[i] = level; } } layerValue = level; return this; } public MappingOp toTerrain(int terrain) throws ScriptException { if ((terrain < 0) || (terrain >= Terrain.VALUES.length)) { throw new ScriptException("Illegal value for terrain index: " + terrain); } if (storedColour != -1) { colourMapping.put(storedColour, terrain); storedColour = -1; } else { for (int i = storedLowerFrom; i <= storedUpperFrom; i++) { mapping[i] = terrain; } } return this; } public MappingOp toLevels(int lower, int upper) { if (storedColour != -1) { throw new IllegalArgumentException("Cannot map a colour to a range"); } else if (storedLowerFrom == storedUpperFrom) { if (lower == upper) { mapping[storedLowerFrom] = lower; } else { throw new IllegalArgumentException("Cannot map a single value to a range"); } } else if (lower == upper) { for (int i = storedLowerFrom; i <= storedUpperFrom; i++) { mapping[i] = lower; } } else { float factor = (float) (upper - lower) / (storedUpperFrom - storedLowerFrom); for (int i = storedLowerFrom; i <= storedUpperFrom; i++) { mapping[i] = lower + (int) ((i - storedLowerFrom) * factor + 0.5f); } } return this; } public MappingOp setAlways() { mode = Mode.SET; return this; } public MappingOp setWhenLower() { mode = Mode.SET_WHEN_LOWER; return this; } public MappingOp setWhenHigher() { mode = Mode.SET_WHEN_HIGHER; return this; } public MappingOp scale(int scale) { this.scale = scale; return this; } public MappingOp shift(int x, int y) { this.offsetX = x; this.offsetY = y; return this; } public MappingOp withFilter(Filter filter) { this.filter = filter; return this; } @Override public Void go() throws ScriptException { goCalled(); // Check preconditions if ((heightMap == null) && (layer == null) && (terrainIndex == -1)) { throw new ScriptException("No data source (heightMap, layer or terrain) specified"); } if ((mode != Mode.SET_TERRAIN) && (layer == null)) { throw new ScriptException("layer not specified"); } if (world == null) { throw new ScriptException("world not specified"); } boolean greyScaleMapPresent = false; for (int mappedValue: mapping) { if (mappedValue != -1) { greyScaleMapPresent = true; break; } } final boolean colourMapPresent = ! colourMapping.isEmpty(); if ((greyScaleMapPresent || colourMapPresent) && (heightMap == null)) { throw new ScriptException("Mapping specified but no height map specified"); } else if (heightMap != null) { if ((! greyScaleMapPresent) && (! colourMapPresent)) { throw new ScriptException("mapping not specified"); } if (greyScaleMapPresent && colourMapPresent) { throw new ScriptException("Cannot mix grey scale and colour mapping"); } if (layer != null) { if (layer.dataSize == Layer.DataSize.NONE) { throw new ScriptException("Layer of unsupported type specified: " + layer); } int bits; switch (layer.dataSize) { case BIT: case BIT_PER_CHUNK: bits = 1; break; case NIBBLE: bits = 4; break; case BYTE: bits = 8; break; default: throw new InternalError(); } int maxValue = (1 << bits) - 1; if (greyScaleMapPresent) { for (int mappedValue: mapping) { if ((mappedValue < -1) || (mappedValue > maxValue)) { throw new ScriptException("Invalid destination level " + mappedValue + " specified for " + bits + "-bit layer " + layer); } } } else { for (Map.Entry<Integer, Integer> entry: colourMapping.entrySet()) { int mappedValue = entry.getValue(); if ((mappedValue < 0) || (mappedValue > maxValue)) { throw new ScriptException("Invalid destination level " + mappedValue + " specified for " + bits + "-bit layer " + layer); } } } } else { if (greyScaleMapPresent) { for (int mappedValue: mapping) { if ((mappedValue < -1) || (mappedValue >= Terrain.VALUES.length)) { throw new ScriptException("Invalid terrain index " + mappedValue + " specified"); } } } else { for (Map.Entry<Integer, Integer> entry: colourMapping.entrySet()) { int mappedValue = entry.getValue(); if ((mappedValue < 0) || (mappedValue >= Terrain.VALUES.length)) { throw new ScriptException("Invalid terrain index " + mappedValue + " specified"); } } } } } final Dimension dimension = world.getDimension(dimIndex); if (dimension == null) { throw new ScriptException("Non existent dimension specified"); } final HeightMap scaledHeightMap; Rectangle extent = new Rectangle(dimension.getLowestX() << TILE_SIZE_BITS, dimension.getLowestY() << TILE_SIZE_BITS, dimension.getWidth() << TILE_SIZE_BITS, dimension.getHeight() << TILE_SIZE_BITS); final boolean smoothScalingAllowed = greyScaleMapPresent && (mode != Mode.SET_TERRAIN) && (! Biome.INSTANCE.equals(layer)) && (! Annotations.INSTANCE.equals(layer)); if (heightMap != null) { if ((scale != 100) || (offsetX != 0) || (offsetY != 0)) { boolean smoothScaling = (scale != 100) && smoothScalingAllowed; // TODO ? scaledHeightMap = TransformingHeightMap.build().withHeightMap(heightMap).withScale(scale).withOffset(offsetX, offsetY).now(); } else { scaledHeightMap = heightMap; } if (scaledHeightMap.getExtent() != null) { extent = extent.intersection(scaledHeightMap.getExtent()); } } else { scaledHeightMap = null; } final int x1 = extent.x, y1 = extent.y; final int x2 = extent.x + extent.width, y2 = extent.y + extent.height; final boolean bitLayer = (layer != null) && ((layer.getDataSize() == Layer.DataSize.BIT) || (layer.getDataSize() == Layer.DataSize.BIT_PER_CHUNK)); if (filter instanceof DefaultFilter) { ((DefaultFilter) filter).setDimension(dimension); } for (int x = x1; x < x2; x++) { for (int y = y1; y < y2; y++) { int valueOut; if (scaledHeightMap != null) { if (colourMapPresent) { int colour = scaledHeightMap.getColour(x, y); if (colourMapping.containsKey(colour)) { valueOut = colourMapping.get(colour); } else { continue; } } else { int valueIn = (int) (scaledHeightMap.getHeight(x, y) + 0.5f); if ((valueIn < 0) || (valueIn > 65535)) { continue; } valueOut = mapping[valueIn]; if (valueOut == -1) { continue; } } } else if (layer != null) { valueOut = layerValue; } else { valueOut = terrainIndex; } if (filter != null) { float filterValue = filter.modifyStrength(x, y, 1.0f); if (filterValue == 0.0f) { continue; } else if (smoothScalingAllowed && (filterValue != 1.0f)) { valueOut = (int) (filterValue * valueOut + 0.5f); } } switch (mode) { case SET_TERRAIN: dimension.setTerrainAt(x, y, Terrain.VALUES[valueOut]); break; case SET: if (bitLayer) { dimension.setBitLayerValueAt(layer, x, y, (valueOut != 0)); } else { dimension.setLayerValueAt(layer, x, y, valueOut); } break; case SET_WHEN_HIGHER: if (bitLayer) { if (valueOut != 0) { dimension.setBitLayerValueAt(layer, x, y, true); } } else { if (dimension.getLayerValueAt(layer, x, y) < valueOut) { dimension.setLayerValueAt(layer, x, y, valueOut); } } break; case SET_WHEN_LOWER: if (bitLayer) { if (valueOut == 0) { dimension.setBitLayerValueAt(layer, x, y, false); } } else { if (dimension.getLayerValueAt(layer, x, y) > valueOut) { dimension.setLayerValueAt(layer, x, y, valueOut); } } break; } } } return null; } private final int[] mapping = new int[65536]; private final Map<Integer, Integer> colourMapping = new HashMap<>(); private HeightMap heightMap; private Layer layer; private World2 world; private int dimIndex, storedLowerFrom, storedUpperFrom, scale = 100, offsetX, offsetY, terrainIndex, layerValue, storedColour = -1; private Mode mode = Mode.SET; private Filter filter; enum Mode { SET, SET_WHEN_LOWER, SET_WHEN_HIGHER, SET_TERRAIN } }