/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.core.world.generator.facetProviders;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.utilities.Assets;
import org.terasology.assets.ResourceUrn;
import org.terasology.entitySystem.Component;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.BaseVector2i;
import org.terasology.rendering.assets.texture.Texture;
import org.terasology.rendering.nui.properties.OneOf.List;
import org.terasology.rendering.nui.properties.Range;
import org.terasology.world.generation.Border3D;
import org.terasology.world.generation.ConfigurableFacetProvider;
import org.terasology.world.generation.Facet;
import org.terasology.world.generation.GeneratingRegion;
import org.terasology.world.generation.Produces;
import org.terasology.world.generation.Requires;
import org.terasology.world.generation.facets.SeaLevelFacet;
import org.terasology.world.generation.facets.SurfaceHeightFacet;
import org.terasology.rendering.nui.properties.OneOf.Enum;
import com.google.common.math.IntMath;
@Produces(SurfaceHeightFacet.class)
@Requires(@Facet(SeaLevelFacet.class))
public class HeightMapSurfaceHeightProvider implements ConfigurableFacetProvider {
public enum WrapMode {
CLAMP,
REPEAT
}
private static final Logger logger = LoggerFactory.getLogger(HeightMapSurfaceHeightProvider.class);
private float[][] heightmap;
private int mapWidth;
private int mapHeight;
private HeightMapConfiguration configuration = new HeightMapConfiguration();
@Override
public void setSeed(long seed) {
initialize();
}
@Override
public void initialize() {
if (heightmap == null) {
reloadHeightmap();
}
}
private void reloadHeightmap() {
logger.info("Reading height map '{}'", configuration.heightMap);
ResourceUrn urn = new ResourceUrn("core", configuration.heightMap);
Texture texture = Assets.getTexture(urn).get();
ByteBuffer[] bb = texture.getData().getBuffers();
IntBuffer intBuf = bb[0].asIntBuffer();
mapWidth = texture.getWidth();
mapHeight = texture.getHeight();
heightmap = new float[mapWidth][mapHeight];
while (intBuf.position() < intBuf.limit()) {
int pos = intBuf.position();
long val = intBuf.get() & 0xFFFFFFFFL;
heightmap[pos % mapWidth][pos / mapWidth] = val / (256 * 256 * 256 * 256f);
}
}
@Override
public void process(GeneratingRegion region) {
Border3D border = region.getBorderForFacet(SurfaceHeightFacet.class);
SurfaceHeightFacet facet = new SurfaceHeightFacet(region.getRegion(), border);
for (BaseVector2i pos : facet.getWorldRegion().contents()) {
int xzScale = configuration.terrainScale;
int mapX0;
int mapZ0;
int mapX1;
int mapZ1;
switch (configuration.wrapMode) {
case CLAMP:
mapX0 = TeraMath.clamp(pos.getX(), 0, mapWidth * xzScale - 1) / xzScale;
mapZ0 = TeraMath.clamp(pos.getY(), 0, mapHeight * xzScale - 1) / xzScale;
mapX1 = TeraMath.clamp(mapX0 + 1, 0, mapWidth - 1);
mapZ1 = TeraMath.clamp(mapZ0 + 1, 0, mapHeight - 1);
break;
case REPEAT:
mapX0 = IntMath.mod(pos.getX(), mapWidth * xzScale) / xzScale;
mapZ0 = IntMath.mod(pos.getY(), mapHeight * xzScale) / xzScale;
mapX1 = IntMath.mod(mapX0 + 1, mapWidth);
mapZ1 = IntMath.mod(mapZ0 + 1, mapHeight);
break;
default:
throw new UnsupportedOperationException("Not supported: " + configuration.wrapMode);
}
double p00 = heightmap[mapX0][mapZ0];
double p10 = heightmap[mapX1][mapZ0];
double p11 = heightmap[mapX1][mapZ1];
double p01 = heightmap[mapX0][mapZ1];
float relX = IntMath.mod(pos.getX(), xzScale) / (float) xzScale;
float relZ = IntMath.mod(pos.getY(), xzScale) / (float) xzScale;
float interpolatedHeight = (float) lerp(relX, lerp(relZ, p00, p01), lerp(relZ, p10, p11));
float height = configuration.heightOffset + configuration.heightScale * interpolatedHeight;
facet.setWorld(pos, height);
}
region.setRegionFacet(SurfaceHeightFacet.class, facet);
}
private static double lerp(double t, double a, double b) {
return a + fade(t) * (b - a); //not sure if i should fade t, needs a bit longer to generate chunks but is definately nicer
}
private static double fade(double t) {
// This is Perlin
// return t * t * t * (t * (t * 6 - 15) + 10);
// This is Hermite
return t * t * (3 - 2 * t);
}
@Override
public String getConfigurationName() {
return "Height Map";
}
@Override
public Component getConfiguration() {
return configuration;
}
@Override
public void setConfiguration(Component configuration) {
String prevHeightMap = this.configuration.heightMap;
this.configuration = (HeightMapConfiguration) configuration;
if (!Objects.equals(prevHeightMap, this.configuration.heightMap)) {
reloadHeightmap();
}
}
private static class HeightMapConfiguration implements Component {
@Enum(description = "Wrap Mode")
private WrapMode wrapMode = WrapMode.REPEAT;
@List(items = { "platec_heightmap", "opposing_islands" }, description = "Height Map")
private String heightMap = "platec_heightmap";
@Range(min = 0, max = 50f, increment = 1f, precision = 0, description = "Height Offset")
private float heightOffset = 12;
@Range(min = 10, max = 200f, increment = 1f, precision = 0, description = "Height Scale Factor")
private float heightScale = 70;
@Range(min = 1, max = 32, increment = 1, precision = 0, description = "Terrain Scale Factor")
private int terrainScale = 8;
}
}