/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers.groundcover;
import org.pepsoft.minecraft.Block;
import org.pepsoft.minecraft.Chunk;
import org.pepsoft.minecraft.Material;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.MixedMaterial;
import org.pepsoft.worldpainter.NoiseSettings;
import org.pepsoft.worldpainter.Tile;
import org.pepsoft.worldpainter.exporting.*;
import org.pepsoft.worldpainter.heightMaps.NoiseHeightMap;
import javax.vecmath.Point3i;
import java.awt.*;
import static org.pepsoft.minecraft.Constants.BLK_AIR;
/**
* Algorithm:
*
* The layers are rendered in order from the lowest thickness to the highest
* thickness (which implies that layers digging into the ground are rendered
* first).
*
* 1. If the layer has a negative thickness (it digs down into the ground), the
* blocks is always placed, except if the existing block is air
* 2. If the layer has positive thickness, and:
* a. it is insubstantial (including water), the block is only placed if the
* existing block is insubstantial (excluding water)
* b. it is substantial, the block is always placed
*
* @author pepijn
*/
public class GroundCoverLayerExporter extends AbstractLayerExporter<GroundCoverLayer> implements FirstPassLayerExporter, IncidentalLayerExporter {
public GroundCoverLayerExporter(GroundCoverLayer layer) {
super(layer);
NoiseSettings noiseSettings = layer.getNoiseSettings();
if (noiseSettings != null) {
noiseHeightMap = new NoiseHeightMap(noiseSettings, NOISE_SEED_OFFSET);
noiseOffset = noiseSettings.getRange();
} else {
noiseHeightMap = null;
noiseOffset = 0;
}
}
@Override
public void render(Dimension dimension, Tile tile, Chunk chunk) {
if (noiseHeightMap != null) {
noiseHeightMap.setSeed(dimension.getSeed());
}
final int xOffset = (chunk.getxPos() & 7) << 4;
final int zOffset = (chunk.getzPos() & 7) << 4;
final int minY = dimension.isBottomless() ? 0 : 1;
final int maxY = dimension.getMaxHeight() - 1;
final MixedMaterial mixedMaterial = layer.getMaterial();
final int thickness = layer.getThickness(), edgeThickness = Math.abs(thickness) - 2;
final GroundCoverLayer.EdgeShape edgeShape = layer.getEdgeShape();
final boolean taperedEdge = (edgeShape != GroundCoverLayer.EdgeShape.SHEER) && (Math.abs(thickness) > 1);
final int edgeWidth = layer.getEdgeWidth(), edgeWidthPlusOne = edgeWidth + 1, edgeWidthMinusOne = edgeWidth - 1;
final double edgeFactor = edgeThickness / 2.0, edgeOffset = 1.5 + edgeFactor;
final long seed = dimension.getSeed();
final boolean smooth = layer.isSmooth();
for (int x = 0; x < 16; x++) {
final int localX = xOffset + x;
final int worldX = (chunk.getxPos() << 4) + x;
for (int z = 0; z < 16; z++) {
final int localY = zOffset + z;
if (tile.getBitLayerValue(layer, localX, localY)) {
final int terrainheight = tile.getIntHeight(localX, localY);
final int blockBelow = chunk.getBlockType(x, terrainheight, z);
if ((blockBelow != BLK_AIR)
&& (! Block.BLOCKS[blockBelow].insubstantial)) {
int effectiveThickness = Math.abs(thickness);
final int worldY = (chunk.getzPos() << 4) + z;
if (taperedEdge) {
float distanceToEdge = dimension.getDistanceToEdge(layer, worldX, worldY, edgeWidthPlusOne);
if (distanceToEdge < edgeWidthPlusOne) {
final double normalisedDistance = (distanceToEdge - 1) / edgeWidthMinusOne;
switch (edgeShape) {
case LINEAR:
effectiveThickness = (int) (1.5 + normalisedDistance * edgeThickness);
break;
case SMOOTH:
effectiveThickness = (int) (edgeOffset + -Math.cos(normalisedDistance * Math.PI) * edgeFactor);
break;
case ROUNDED:
double reversedNormalisedDistance = 1 - (distanceToEdge - 0.5) / edgeWidth;
effectiveThickness = (int) (1.5 + Math.sqrt(1 - reversedNormalisedDistance * reversedNormalisedDistance) * edgeThickness);
break;
}
}
}
if (noiseHeightMap != null) {
effectiveThickness += noiseHeightMap.getHeight(worldX, worldY) - noiseOffset;
}
if (thickness > 0) {
for (int dy = 0; dy < effectiveThickness; dy++) {
final int y = terrainheight + dy + 1;
if (y > maxY) {
break;
}
final int existingBlockType = chunk.getBlockType(x, y, z);
final Material material = mixedMaterial.getMaterial(seed, worldX, worldY, y);
if ((material != Material.AIR)
&& ((! material.block.veryInsubstantial)
|| (existingBlockType == BLK_AIR)
|| Block.BLOCKS[existingBlockType].insubstantial)) {
if (smooth && (dy == (effectiveThickness - 1))) {
// Top layer, smooth enabled
int layerHeight = (int) ((dimension.getHeightAt(worldX, worldY) + 0.5f - dimension.getIntHeightAt(worldX, worldY)) / 0.125f);
if (layerHeight > 0) {
layerHeight = Math.max(Math.min(layerHeight, dimension.getBitLayerCount(layer, worldX, worldY, 1) - 2), 0);
}
chunk.setBlockType(x, y, z, material.blockType);
chunk.setDataValue(x, y, z, layerHeight);
} else {
chunk.setMaterial(x, y, z, material);
}
}
}
} else {
for (int dy = 0; dy < effectiveThickness; dy++) {
final int y = terrainheight - dy;
if (y < minY) {
break;
}
int existingBlockType = chunk.getBlockType(x, y, z);
if (existingBlockType != BLK_AIR) {
chunk.setMaterial(x, y, z, mixedMaterial.getMaterial(seed, worldX, worldY, y));
}
}
}
}
}
}
}
}
@Override
public Fixup apply(Dimension dimension, Point3i location, int intensity, Rectangle exportedArea, MinecraftWorld minecraftWorld) {
if (intensity > 0) {
final int blockBelow = minecraftWorld.getBlockTypeAt(location.x, location.y, location.z - 1);
if ((blockBelow != BLK_AIR)
&& (! Block.BLOCKS[blockBelow].insubstantial)) {
final int thickness = layer.getThickness();
final MixedMaterial mixedMaterial = layer.getMaterial();
final long seed = dimension.getSeed();
int effectiveThickness = Math.abs(thickness);
if (noiseHeightMap != null) {
noiseHeightMap.setSeed(seed);
effectiveThickness += noiseHeightMap.getHeight(location.x, location.y) - noiseOffset;
}
if (thickness > 0) {
final int maxZ = dimension.getMaxHeight() - 1;
for (int dz = 0; dz < effectiveThickness; dz++) {
final int z = location.z + dz;
if (z > maxZ) {
break;
}
final int existingBlockType = minecraftWorld.getBlockTypeAt(location.x, location.y, z);
final Material material = mixedMaterial.getMaterial(seed, location.x, location.y, z);
if ((material != Material.AIR)
&& ((! material.block.veryInsubstantial)
|| (existingBlockType == BLK_AIR)
|| Block.BLOCKS[existingBlockType].insubstantial)) {
minecraftWorld.setMaterialAt(location.x, location.y, z, material);
}
}
} else {
final int minZ = dimension.isBottomless() ? 0 : 1;
for (int dz = 0; dz < effectiveThickness; dz++) {
final int z = location.z - 1 - dz;
if (z < minZ) {
break;
}
int existingBlockType = minecraftWorld.getBlockTypeAt(location.x, location.y, z);
if (existingBlockType != BLK_AIR) {
minecraftWorld.setMaterialAt(location.x, location.y, z, mixedMaterial.getMaterial(seed, location.x, location.y, z));
}
}
}
}
}
return null;
}
private final NoiseHeightMap noiseHeightMap;
private final int noiseOffset;
private static final long NOISE_SEED_OFFSET = 135101785L;
}