/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers;
import org.pepsoft.minecraft.Chunk;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.MixedMaterial;
import org.pepsoft.worldpainter.Terrain;
import org.pepsoft.worldpainter.Tile;
import org.pepsoft.worldpainter.exporting.*;
import org.pepsoft.worldpainter.layers.exporters.ExporterSettings;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.*;
import java.util.List;
import static org.pepsoft.worldpainter.Constants.TILE_SIZE;
import static org.pepsoft.worldpainter.layers.Layer.DataSize.*;
/**
*
* @author pepijn
*/
public class CombinedLayer extends CustomLayer implements LayerContainer {
public CombinedLayer(String name, String description, int colour) {
super(name, description, NIBBLE, 55, colour);
}
public Set<Layer> apply(Dimension dimension) {
Set<Layer> addedLayers = new HashSet<>();
for (Tile tile : dimension.getTiles()) {
addedLayers.addAll(apply(tile));
}
return addedLayers;
}
public Set<Layer> apply(Tile tile) {
Set<Layer> addedLayers = new HashSet<>();
if (!tile.hasLayer(this)) {
return Collections.emptySet();
}
tile.inhibitEvents();
try {
for (Layer layer : layers) {
boolean layerAdded = false;
final float factor = factors.get(layer);
DataSize dataSize = layer.getDataSize();
if ((dataSize == BIT) || (dataSize == BIT_PER_CHUNK)) {
for (int x = 0; x < TILE_SIZE; x++) {
for (int y = 0; y < TILE_SIZE; y++) {
float strength = Math.min(tile.getLayerValue(this, x, y) / 15.0f * factor, 1.0f);
if ((strength > 0.95f) || (Math.random() < strength)) {
tile.setBitLayerValue(layer, x, y, true);
layerAdded = true;
}
}
}
} else {
int maxValue = (dataSize == NIBBLE) ? 15 : 255;
for (int x = 0; x < TILE_SIZE; x++) {
for (int y = 0; y < TILE_SIZE; y++) {
int value = Math.min((int) (tile.getLayerValue(this, x, y) * factor + 0.5f), maxValue);
if (value > 0) {
tile.setLayerValue(layer, x, y, value);
layerAdded = true;
}
}
}
}
if (layerAdded) {
addedLayers.add(layer);
}
}
tile.clearLayerData(this);
} finally {
tile.releaseEvents();
}
return addedLayers;
}
@Override
public void setName(String name) {
super.setName(name);
}
public Terrain getTerrain() {
return terrain;
}
public void setTerrain(Terrain terrain) {
this.terrain = terrain;
}
@Override
public List<Layer> getLayers() {
return layers;
}
public void setLayers(List<Layer> layers) {
if (layers == null) {
throw new NullPointerException();
}
this.layers = layers;
}
public Map<Layer, Float> getFactors() {
return factors;
}
public void setFactors(Map<Layer, Float> factors) {
if (factors == null) {
throw new NullPointerException();
}
this.factors = factors;
}
/**
* Returns a dummy exporter, all methods of which throw an
* {@link UnsupportedOperationException}, since combined layers must be
* exported by {@link #apply(Dimension) applying} them and then exporting
* its constituent layers, if any.
*
* <p>The exporter does implement {@link FirstPassLayerExporter} and
* {@link SecondPassLayerExporter} though, to signal the fact that it can
* contain layers for both phases.
*
* @return A dummy exporter which always throws
* <code>UnsupportedOperationException</code>.
*/
@Override
public LayerExporter getExporter() {
return EXPORTER;
}
@Override
public List<Action> getActions() {
List<Action> actions = new ArrayList<>();
List<Action> superActions = super.getActions();
if (superActions != null) {
actions.addAll(superActions);
}
actions.add(new AbstractAction("Apply") {
@Override
public void actionPerformed(ActionEvent e) {
Dimension dimension = (Dimension) getValue(KEY_DIMENSION);
if ((dimension != null) && dimension.getAllLayers(false).contains(CombinedLayer.this)) {
dimension.armSavePoint();
apply(dimension);
}
}
private static final long serialVersionUID = 1L;
});
return actions;
}
/**
* If this combined layer contains a custom terrain, make sure that it is
* installed. If the original material is available as a custom terrain
* type, use that, otherwise install the material as a new custom terrain
* type, assuming a slot is available. Should be invoked once, after a
* combined layer has been loaded.
*
* <p>Returns <code>false</code> if a custom terrain was present
* but it could not be restored because all custom terrain slots are in use,
* <code>true</code> in all other circumstances.
*
* <p><strong>Please note:</strong> if this returns <code>true</code> a new
* custom terrain <em>may</em> have been installed, so in that case the
* invoker MUST check whether a button already exists for the custom terrain
* used by this combined layer, and add one if not.
*
* @return <code>false</code> if a custom terrain was present but it could
* not be restored because all custom terrain slots are in use,
* <code>true</code> otherwise.
*/
public boolean restoreCustomTerrain() {
if (customTerrainPresent) {
if (customTerrainMaterial == null) {
// This should not be possible, but due to earlier bugs there
// are worlds in the wild with a custom terrain without a stored
// custom material. Not much we can do
terrain = null;
return false;
} else if (customTerrainMaterial.equals(Terrain.getCustomMaterial(terrain.getCustomTerrainIndex()))) {
// The exact same custom terrain is present, in the same slot.
// Keep using it
return true;
} else if (Terrain.getCustomMaterial(terrain.getCustomTerrainIndex()) == null) {
// The slot that was previously used is empty, store the custom
// terrain in it
Terrain.setCustomMaterial(terrain.getCustomTerrainIndex(), customTerrainMaterial);
return true;
} else {
// The slot that was previously used contains a different mixed
// material. Find another empty slot
for (int i = 0; i < Terrain.CUSTOM_TERRAIN_COUNT; i++) {
if (Terrain.getCustomMaterial(i) == null) {
Terrain.setCustomMaterial(i, customTerrainMaterial);
terrain = Terrain.getCustomTerrain(i);
return true;
}
}
// No more slots available. Not much we can do
terrain = null;
return false;
}
} else {
return (terrain == null) || (! terrain.isCustom());
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (customTerrainPresent) {
customTerrainMaterial = (MixedMaterial) in.readObject();
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
// Make sure that the custom terrain definition gets saved along with
// the layer
customTerrainPresent = (terrain != null) && terrain.isCustom();
out.defaultWriteObject();
if (customTerrainPresent) {
out.writeObject(Terrain.getCustomMaterial(terrain.getCustomTerrainIndex()));
}
}
private static final long serialVersionUID = 1L;
private Terrain terrain;
private List<Layer> layers = Collections.emptyList();
private Map<Layer, Float> factors = Collections.emptyMap();
private boolean customTerrainPresent;
private transient MixedMaterial customTerrainMaterial;
private static final LayerExporter EXPORTER = new CombinedLayerExporter();
static class CombinedLayerExporter implements FirstPassLayerExporter, SecondPassLayerExporter {
@Override
public Layer getLayer() {
throw new UnsupportedOperationException();
}
@Override
public void setSettings(ExporterSettings settings) {
throw new UnsupportedOperationException();
}
@Override
public void render(Dimension dimension, Tile tile, Chunk chunk) {
throw new UnsupportedOperationException();
}
@Override
public List<Fixup> render(Dimension dimension, Rectangle area, Rectangle exportedArea, MinecraftWorld minecraftWorld) {
throw new UnsupportedOperationException();
}
}
}