/*
* 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.layers.tunnel;
import org.pepsoft.minecraft.Constants;
import org.pepsoft.minecraft.Material;
import org.pepsoft.util.MathUtils;
import org.pepsoft.util.PerlinNoise;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.MixedMaterial;
import org.pepsoft.worldpainter.exporting.*;
import org.pepsoft.worldpainter.heightMaps.NoiseHeightMap;
import org.pepsoft.worldpainter.layers.Layer;
import javax.vecmath.Point3i;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.pepsoft.minecraft.Block.BLOCKS;
/**
*
* @author SchmitzP
*/
public class TunnelLayerExporter extends AbstractLayerExporter<TunnelLayer> implements SecondPassLayerExporter {
public TunnelLayerExporter(TunnelLayer layer) {
super(layer);
if (layer.getFloorNoise() != null) {
floorNoise = new NoiseHeightMap(layer.getFloorNoise(), FLOOR_NOISE_SEED_OFFSET);
floorNoiseOffset = layer.getFloorNoise().getRange();
} else {
floorNoise = null;
floorNoiseOffset = 0;
}
if (layer.getRoofNoise()!= null) {
roofNoise = new NoiseHeightMap(layer.getRoofNoise(), ROOF_NOISE_SEED_OFFSET);
roofNoiseOffset = layer.getRoofNoise().getRange();
} else {
roofNoise = null;
roofNoiseOffset = 0;
}
// if (layer.getWallNoise() != null) {
// wallNoise = new PerlinNoise(layer.getWallNoise().getSeed());
// } else {
// wallNoise = null;
// }
}
@Override
public List<Fixup> render(Dimension dimension, Rectangle area, Rectangle exportedArea, MinecraftWorld world) {
final TunnelLayer.Mode floorMode = layer.getFloorMode(), roofMode = layer.getRoofMode();
final int floorWallDepth = layer.getFloorWallDepth(), roofWallDepth = layer.getRoofWallDepth(),
floorLevel = layer.getFloorLevel(), roofLevel = layer.getRoofLevel(),
maxWallDepth = Math.max(floorWallDepth, roofWallDepth) + 1,
floorMin = layer.getFloorMin(), floorMax = layer.getFloorMax(), roofMin = layer.getRoofMin(), roofMax = layer.getRoofMax(),
floodLevel = layer.getFloodLevel();
final int minZ = dimension.isBottomless() ? 0 : 1, maxZ = dimension.getMaxHeight() - 1;
final boolean removeWater = layer.isRemoveWater(), floodWithLava = layer.isFloodWithLava();
final MixedMaterial floorMaterial = layer.getFloorMaterial(), wallMaterial = layer.getWallMaterial(), roofMaterial = layer.getRoofMaterial();
if (floorNoise != null) {
floorNoise.setSeed(dimension.getSeed());
}
if (roofNoise != null) {
roofNoise.setSeed(dimension.getSeed());
}
if ((floorMaterial == null) && (wallMaterial == null) && (roofMaterial == null)) {
// One pass: just remove blocks
for (int x = area.x; x < area.x + area.width; x++) {
for (int y = area.y; y < area.y + area.height; y++) {
if (dimension.getBitLayerValueAt(layer, x, y)) {
final int terrainHeight = dimension.getIntHeightAt(x, y);
int actualFloorLevel = calculateLevel(floorMode, floorLevel, terrainHeight, floorMin, floorMax, minZ, maxZ, (floorNoise != null) ? ((int) floorNoise.getHeight(x, y) - floorNoiseOffset) : 0);
int actualRoofLevel = calculateLevel(roofMode, roofLevel, terrainHeight, roofMin, roofMax, minZ, maxZ, (roofNoise != null) ? ((int) roofNoise.getHeight(x, y) - roofNoiseOffset) : 0);
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final float distanceToWall = dimension.getDistanceToEdge(layer, x, y, maxWallDepth) - 1;
final int floorLedgeHeight = calculateLedgeHeight(floorWallDepth, distanceToWall);
final int roofLedgeHeight = calculateLedgeHeight(roofWallDepth, distanceToWall);
actualFloorLevel += floorLedgeHeight;
actualRoofLevel -= roofLedgeHeight;
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final int waterLevel = dimension.getWaterLevelAt(x, y);
for (int z = Math.min(removeWater ? Math.max(terrainHeight, waterLevel) : terrainHeight, actualRoofLevel); z > actualFloorLevel; z--) {
if (removeWater || (z <= terrainHeight) || (z > waterLevel)) {
if (z <= floodLevel) {
world.setMaterialAt(x, y, z, floodWithLava ? Material.LAVA : Material.WATER);
} else {
world.setMaterialAt(x, y, z, Material.AIR);
}
}
}
if (actualFloorLevel == 0) {
// Bottomless world, and cave extends all the way to
// the bottom. Remove the floor block, as that is
// probably what the user wants
if ((floodLevel > 0) && (0 <= floodLevel)) {
world.setMaterialAt(x, y, 0, floodWithLava ? Material.STATIONARY_LAVA: Material.STATIONARY_WATER);
} else {
world.setMaterialAt(x, y, 0, Material.AIR);
}
}
}
}
}
} else {
// Two passes: first place floor, wall and roof materials, then
// excavate the interior
for (int x = area.x; x < area.x + area.width; x++) {
for (int y = area.y; y < area.y + area.height; y++) {
if (dimension.getBitLayerValueAt(layer, x, y)) {
int terrainHeight = dimension.getIntHeightAt(x, y);
int actualFloorLevel = calculateLevel(floorMode, floorLevel, terrainHeight, floorMin, floorMax, minZ, maxZ, (floorNoise != null) ? ((int) floorNoise.getHeight(x, y) - floorNoiseOffset) : 0);
int actualRoofLevel = calculateLevel(roofMode, roofLevel, terrainHeight, roofMin, roofMax, minZ, maxZ, (roofNoise != null) ? ((int) roofNoise.getHeight(x, y) - roofNoiseOffset) : 0);
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final float distanceToWall = dimension.getDistanceToEdge(layer, x, y, maxWallDepth) - 1;
final int floorLedgeHeight = calculateLedgeHeight(floorWallDepth, distanceToWall);
final int roofLedgeHeight = calculateLedgeHeight(roofWallDepth, distanceToWall);
actualFloorLevel += floorLedgeHeight;
actualRoofLevel -= roofLedgeHeight;
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
int waterLevel = dimension.getWaterLevelAt(x, y);
boolean flooded = waterLevel > terrainHeight;
final int startZ = Math.min(removeWater ? Math.max(terrainHeight, waterLevel) : terrainHeight, actualRoofLevel);
for (int z = startZ; z > actualFloorLevel; z--) {
if ((floorLedgeHeight == 0) && (floorMaterial != null)) {
setIfSolid(world, x, y, z - 1, minZ, maxZ, floorMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
if (wallMaterial != null) {
if (floorLedgeHeight > 0) {
setIfSolid(world, x, y, z - 1, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
if (roofLedgeHeight > 0) {
setIfSolid(world, x, y, z + 1, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
}
if ((roofLedgeHeight == 0) && (roofMaterial != null)) {
setIfSolid(world, x, y, z + 1, minZ, maxZ, roofMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
}
if (wallMaterial != null) {
terrainHeight = dimension.getIntHeightAt(x - 1, y);
waterLevel = dimension.getWaterLevelAt(x - 1, y);
flooded = waterLevel > terrainHeight;
for (int z = startZ; z > actualFloorLevel; z--) {
setIfSolid(world, x - 1, y, z, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
terrainHeight = dimension.getIntHeightAt(x, y - 1);
waterLevel = dimension.getWaterLevelAt(x, y - 1);
flooded = waterLevel > terrainHeight;
for (int z = startZ; z > actualFloorLevel; z--) {
setIfSolid(world, x, y - 1, z, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
terrainHeight = dimension.getIntHeightAt(x + 1, y);
waterLevel = dimension.getWaterLevelAt(x + 1, y);
flooded = waterLevel > terrainHeight;
for (int z = startZ; z > actualFloorLevel; z--) {
setIfSolid(world, x + 1, y, z, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
terrainHeight = dimension.getIntHeightAt(x, y + 1);
waterLevel = dimension.getWaterLevelAt(x, y + 1);
flooded = waterLevel > terrainHeight;
for (int z = startZ; z > actualFloorLevel; z--) {
setIfSolid(world, x, y + 1, z, minZ, maxZ, wallMaterial, flooded, terrainHeight, waterLevel, removeWater);
}
}
}
}
}
// Second pass: excavate interior
for (int x = area.x; x < area.x + area.width; x++) {
for (int y = area.y; y < area.y + area.height; y++) {
if (dimension.getBitLayerValueAt(layer, x, y)) {
final int terrainHeight = dimension.getIntHeightAt(x, y);
int actualFloorLevel = calculateLevel(floorMode, floorLevel, terrainHeight, floorMin, floorMax, minZ, maxZ, (floorNoise != null) ? ((int) floorNoise.getHeight(x, y) - floorNoiseOffset) : 0);
int actualRoofLevel = calculateLevel(roofMode, roofLevel, terrainHeight, roofMin, roofMax, minZ, maxZ, (roofNoise != null) ? ((int) roofNoise.getHeight(x, y) - roofNoiseOffset) : 0);
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final float distanceToWall = dimension.getDistanceToEdge(layer, x, y, maxWallDepth) - 1;
final int floorLedgeHeight = calculateLedgeHeight(floorWallDepth, distanceToWall);
final int roofLedgeHeight = calculateLedgeHeight(roofWallDepth, distanceToWall);
actualFloorLevel += floorLedgeHeight;
actualRoofLevel -= roofLedgeHeight;
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final int waterLevel = dimension.getWaterLevelAt(x, y);
for (int z = actualRoofLevel; z > actualFloorLevel; z--) {
if (removeWater || (z <= terrainHeight) || (z > waterLevel)) {
if (z <= floodLevel) {
world.setMaterialAt(x, y, z, floodWithLava ? Material.LAVA : Material.WATER);
} else {
world.setMaterialAt(x, y, z, Material.AIR);
}
}
}
if (actualFloorLevel == 0) {
// Bottomless world, and cave extends all the way to
// the bottom. Remove the floor block, as that is
// probably what the user wants
if ((floodLevel > 0) && (0 <= floodLevel)) {
world.setMaterialAt(x, y, 0, floodWithLava ? Material.LAVA : Material.WATER);
} else {
world.setMaterialAt(x, y, 0, Material.AIR);
}
}
}
}
}
}
// Second/third pass: render floor layers
List<Fixup> fixups = new ArrayList<>();
final Map<Layer, TunnelLayer.LayerSettings> floorLayers = layer.getFloorLayers();
if ((floorLayers != null) && (! floorLayers.isEmpty())) {
final IncidentalLayerExporter[] floorExporters = new IncidentalLayerExporter[floorLayers.size()];
final TunnelLayer.LayerSettings[] floorLayerSettings = new TunnelLayer.LayerSettings[floorLayers.size()];
final NoiseHeightMap[] floorLayerNoise = new NoiseHeightMap[floorLayers.size()];
int index = 0;
for (Layer floorLayer: floorLayers.keySet()) {
floorExporters[index] = (IncidentalLayerExporter) floorLayer.getExporter();
TunnelLayer.LayerSettings layerSettings = floorLayers.get(floorLayer);
floorLayerSettings[index] = layerSettings;
if (layerSettings.getVariation() != null) {
floorLayerNoise[index] = new NoiseHeightMap(layerSettings.getVariation(), index);
floorLayerNoise[index].setSeed(dimension.getSeed());
}
index++;
}
final TunnelFloorDimension floorDimension = new TunnelFloorDimension(dimension, layer);
for (int x = area.x; x < area.x + area.width; x++) {
for (int y = area.y; y < area.y + area.height; y++) {
if (dimension.getBitLayerValueAt(layer, x, y)) {
final int terrainHeight = dimension.getIntHeightAt(x, y);
int actualFloorLevel = calculateLevel(floorMode, floorLevel, terrainHeight, floorMin, floorMax, minZ, maxZ, (floorNoise != null) ? ((int) floorNoise.getHeight(x, y) - floorNoiseOffset) : 0);
int actualRoofLevel = calculateLevel(roofMode, roofLevel, terrainHeight, roofMin, roofMax, minZ, maxZ, (roofNoise != null) ? ((int) roofNoise.getHeight(x, y) - roofNoiseOffset) : 0);
if (actualRoofLevel <= actualFloorLevel) {
continue;
}
final float distanceToWall = dimension.getDistanceToEdge(layer, x, y, maxWallDepth) - 1;
final int floorLedgeHeight = calculateLedgeHeight(floorWallDepth, distanceToWall);
final int roofLedgeHeight = calculateLedgeHeight(roofWallDepth, distanceToWall);
actualFloorLevel += floorLedgeHeight;
actualRoofLevel -= roofLedgeHeight;
if ((actualRoofLevel <= actualFloorLevel) || (actualFloorLevel == 0)) {
continue;
}
final int z = actualFloorLevel + 1;
final Point3i location = new Point3i(x, y, z);
for (int i = 0; i < floorExporters.length; i++) {
if ((z >= floorLayerSettings[i].getMinLevel()) && (z <= floorLayerSettings[i].getMaxLevel())) {
final int intensity = floorLayerNoise[i] != null
? MathUtils.clamp(0, (int) (floorLayerSettings[i].getIntensity() + floorLayerNoise[i].getValue(x, y, z) + 0.5f), 100)
: floorLayerSettings[i].getIntensity();
if (intensity > 0) {
Fixup fixup = floorExporters[i].apply(floorDimension, location, intensity, exportedArea, world);
if (fixup != null) {
fixups.add(fixup);
}
}
}
}
}
}
}
}
return fixups.isEmpty() ? null : fixups;
}
// private void excavateDisc(final MinecraftWorld world, final int x, final int y, final int z, int r, final Material materialAbove, final Material materialBesides, final Material materialBelow) {
// GeometryUtil.visitFilledCircle(r, new PlaneVisitor() {
// @Override
// public boolean visit(int dx, int dy, float d) {
// world.setMaterialAt(x + dx, y + dy, z, Material.AIR);
// if (materialAbove != null) {
// setIfSolid(world, x + dx, y + dy, z - 1, 0, Integer.MAX_VALUE, materialAbove);
// }
// if (materialBesides != null) {
// setIfSolid(world, x + dx - 1, y + dy, z, 0, Integer.MAX_VALUE, materialBesides);
// setIfSolid(world, x + dx, y + dy - 1, z, 0, Integer.MAX_VALUE, materialBesides);
// setIfSolid(world, x + dx + 1, y + dy, z, 0, Integer.MAX_VALUE, materialBesides);
// setIfSolid(world, x + dx, y + dy + 1, z, 0, Integer.MAX_VALUE, materialBesides);
// }
// if (materialBelow != null) {
// setIfSolid(world, x + dx, y + dy, z + 1, 0, Integer.MAX_VALUE, materialBelow);
// }
// return true;
// }
// });
// }
public BufferedImage generatePreview(int width, int height, int waterLevel, int baseHeight, int heightDifference) {
final TunnelLayer.Mode floorMode = layer.getFloorMode(), roofMode = layer.getRoofMode();
final int floorWallDepth = layer.getFloorWallDepth(), roofWallDepth = layer.getRoofWallDepth(),
floorLevel = layer.getFloorLevel(), roofLevel = layer.getRoofLevel(), tunnelExtent = width - 24,
floorMin = layer.getFloorMin(), floorMax = layer.getFloorMax(), roofMin = layer.getRoofMin(), roofMax = layer.getRoofMax(),
floodLevel = layer.getFloodLevel();
final boolean removeWater = layer.isRemoveWater(), floodWithLava = layer.isFloodWithLava();
final PerlinNoise noise = new PerlinNoise(0);
final BufferedImage preview = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
// Clear the sky
final int terrainHeight = MathUtils.clamp(0, (int) (Math.sin((x / (double) width * 1.5 + 1.25) * Math.PI) * heightDifference / 2 + heightDifference / 2 + baseHeight + noise.getPerlinNoise(x / 20.0) * 32 + noise.getPerlinNoise(x / 10.0) * 16 + noise.getPerlinNoise(x / 5.0) * 8), baseHeight + heightDifference - 1);
for (int z = height - 1; z > terrainHeight; z--) {
preview.setRGB(x, height - 1 - z, (z <= waterLevel) ? 0x0000ff : 0xffffff);
}
if (x <= tunnelExtent) {
// Draw the tunnel
int actualFloorLevel = calculateLevel(floorMode, floorLevel, terrainHeight, floorMin, floorMax, 1, height - 1, (floorNoise != null) ? ((int) floorNoise.getHeight(x, 0) - floorNoiseOffset) : 0);
int actualRoofLevel = calculateLevel(roofMode, roofLevel, terrainHeight, roofMin, roofMax, 1, height - 1, (roofNoise != null) ? ((int) roofNoise.getHeight(x, 0) - roofNoiseOffset) : 0);
if (actualRoofLevel > actualFloorLevel) {
final float distanceToWall = tunnelExtent - x;
final int floorLedgeHeight = calculateLedgeHeight(floorWallDepth, distanceToWall);
final int roofLedgeHeight = calculateLedgeHeight(roofWallDepth, distanceToWall);
actualFloorLevel += floorLedgeHeight;
actualRoofLevel = Math.min(actualRoofLevel - roofLedgeHeight, Math.max(terrainHeight, 62));
for (int z = actualFloorLevel + 1; z <= actualRoofLevel; z++) {
if (z <= floodLevel) {
if (floodWithLava) {
preview.setRGB(x, height - 1 - z, 0xff8000);
} else {
preview.setRGB(x, height - 1 - z, 0x0000ff);
}
} else {
if (z > terrainHeight) {
if (removeWater) {
preview.setRGB(x, height - 1 - z, 0x7f7fff);
}
} else {
preview.setRGB(x, height - 1 - z, 0x7f7f7f);
}
}
}
}
}
}
return preview;
}
static int calculateLevel(TunnelLayer.Mode mode, int level, int terrainHeight, int minLevel, int maxLevel, int minZ, int maxZ, int offset) {
switch (mode) {
case CONSTANT_DEPTH:
return MathUtils.clamp(minZ, MathUtils.clamp(minLevel, terrainHeight - level, maxLevel) + offset, maxZ);
case FIXED_HEIGHT:
return MathUtils.clamp(minZ, level + offset, maxZ);
case INVERTED_DEPTH:
return MathUtils.clamp(minZ, MathUtils.clamp(minLevel, level - (terrainHeight - level), maxLevel) + offset, maxZ);
default:
throw new InternalError();
}
}
static int calculateLedgeHeight(int wallDepth, float distanceToWall) {
if (distanceToWall > wallDepth) {
return 0;
} else {
final float a = wallDepth - distanceToWall;
return (int) (wallDepth - Math.sqrt(wallDepth * wallDepth - a * a) + 0.5);
}
}
private void setIfSolid(MinecraftWorld world, int x, int y, int z, int minZ, int maxZ, MixedMaterial material, boolean flooded, int terrainHeight, int waterLevel, boolean removeWater) {
if ((z >= minZ) && (z <= maxZ)) {
if (removeWater || (! flooded) || (z <= terrainHeight) || (z > waterLevel)) {
final int existingBlock = world.getBlockTypeAt(x, y, z);
if ((existingBlock != Constants.BLK_AIR)
&& (! BLOCKS[existingBlock].insubstantial)) {
// The coordinates are within bounds and the existing block is solid
world.setMaterialAt(x, y, z, material.getMaterial(MATERIAL_SEED, x, y, z));
}
}
}
}
private final NoiseHeightMap floorNoise, roofNoise;
private final int floorNoiseOffset, roofNoiseOffset;
// private final PerlinNoise wallNoise;
static final long FLOOR_NOISE_SEED_OFFSET = 177766561L;
static final long ROOF_NOISE_SEED_OFFSET = 184818453L;
private static final long MATERIAL_SEED = 0x688b2af137c77e0cL;
}