core + app

This commit is contained in:
dags- 2020-01-16 10:06:28 +00:00
parent ca0579e976
commit baeba9c98a
117 changed files with 9296 additions and 1 deletions

View File

@ -0,0 +1,18 @@
plugins {
id "java"
}
repositories {
mavenCentral()
jcenter()
maven { url "https://jitpack.io" }
}
dependencies {
compile "org.processing:core:3.3.7"
compile project(":TerraForgedCore")
}
jar {
manifest { attributes "Main-Class": "com.terraforged.app.Main" }
}

View File

@ -0,0 +1,136 @@
package com.terraforged.app;
import com.terraforged.app.renderer.MeshRenderer;
import com.terraforged.app.renderer.Renderer;
import com.terraforged.app.renderer.VoxelRenderer;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
import processing.core.PApplet;
import processing.event.KeyEvent;
import processing.event.MouseEvent;
public abstract class Applet extends PApplet {
public static final int ELEVATION = 1;
public static final int BIOME_TYPE = 2;
public static final int TEMPERATURE = 3;
public static final int MOISTURE = 4;
public static final int BIOME = 5;
public static final int STEEPNESS = 6;
public static final int TERRAIN_TYPE = 7;
public static final int EROSION = 8;
public static final int CONTINENT = 9;
protected Renderer mesh = new MeshRenderer(this);
protected Renderer voxel = new VoxelRenderer(this);
public final Controller controller = new Controller();
public abstract Cache getCache();
public abstract float color(Cell<Terrain> cell);
@Override
public void settings() {
size(800, 800, P3D);
}
@Override
public void mousePressed(MouseEvent event) {
controller.mousePress(event);
}
@Override
public void mouseReleased(MouseEvent event) {
controller.mouseRelease(event);
}
@Override
public void mouseDragged(MouseEvent event) {
controller.mouseDrag(event);
}
@Override
public void mouseWheel(MouseEvent event) {
controller.mouseWheel(event);
}
@Override
public void keyPressed(KeyEvent event) {
controller.keyPress(event);
}
@Override
public void keyReleased(KeyEvent event) {
controller.keyRelease(event);
}
public void leftAlignText(int margin, int top, int lineHeight, String... lines) {
noLights();
fill(0, 0, 100);
for (int i = 0; i < lines.length; i++) {
int y = top + (i * lineHeight);
text(lines[i], margin, y);
}
}
public void drawTerrain(float zoom) {
if (controller.getRenderMode() == 0) {
voxel.render(zoom);
} else {
mesh.render(zoom);
}
}
public String colorModeName() {
switch (controller.getColorMode()) {
case STEEPNESS:
return "GRADIENT";
case TEMPERATURE:
return "TEMPERATURE";
case MOISTURE:
return "MOISTURE";
case TERRAIN_TYPE:
return "TERRAIN TYPE";
case ELEVATION:
return "ELEVATION";
case BIOME_TYPE:
return "BIOME TYPE";
case BIOME:
return "BIOME";
case EROSION:
return "EROSION";
case CONTINENT:
return "CONTINENT";
default:
return "-";
}
}
public static float hue(float value, int steps, int max) {
value = Math.round(value * (steps - 1));
value /= (steps - 1);
return value * max;
}
public void drawCompass() {
pushStyle();
pushMatrix();
textSize(200);
fill(100, 0, 100);
char[] chars = {'N', 'E', 'S', 'W'};
for (int r = 0; r < 4; r++) {
char c = chars[r];
float x = -textWidth(c) / 2;
float y = -width * 1.2F;
text(c, x, y);
rotateZ(0.5F * PI);
}
popMatrix();
popStyle();
textSize(16);
}
}

View File

@ -0,0 +1,98 @@
package com.terraforged.app;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.region.Region;
import com.terraforged.core.region.RegionGenerator;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.util.concurrent.ThreadPool;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.WorldGeneratorFactory;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.Terrains;
public class Cache {
private float offsetX = 0;
private float offsetZ = 0;
private float zoom = 0F;
private boolean filter = true;
private Terrains terrain;
private Settings settings;
private GeneratorContext context;
private Region region;
private RegionGenerator renderer;
public Cache(int seed) {
Settings settings = new Settings();
settings.generator.seed = seed;
this.settings = settings;
this.terrain = Terrains.create(settings);
this.context = new GeneratorContext(terrain, settings);
this.renderer = RegionGenerator.builder()
.factory(new WorldGeneratorFactory(context))
.pool(ThreadPool.getCommon())
.size(3, 0)
.build();
}
public Settings getSettings() {
return settings;
}
public Terrains getTerrain() {
return terrain;
}
public Terrain getCenterTerrain() {
Terrain tag = getCenterCell().tag;
return tag == null ? terrain.ocean : tag;
}
public BiomeType getCenterBiomeType() {
return getCenterCell().biomeType;
}
public int getCenterHeight() {
return (int) (context.levels.worldHeight * getCenterCell().value);
}
public Cell<Terrain> getCenterCell() {
int center = region.getBlockSize().size / 2;
return region.getCell(center, center);
}
public Region getRegion() {
return region;
}
public void update(float offsetX, float offsetZ, float zoom, boolean filters) {
if (region == null) {
record(offsetX, offsetZ, zoom, filters);
return;
}
if (this.offsetX != offsetX || this.offsetZ != offsetZ) {
record(offsetX, offsetZ, zoom, filters);
return;
}
if (this.zoom != zoom) {
record(offsetX, offsetZ, zoom, filters);
return;
}
if (this.filter != filters) {
record(offsetX, offsetZ, zoom, filters);
}
}
private void record(float offsetX, float offsetZ, float zoom, boolean filters) {
this.zoom = zoom;
this.filter = filters;
this.offsetX = offsetX;
this.offsetZ = offsetZ;
try {
this.region = renderer.generateRegion(offsetX, offsetZ, zoom, filters);
} catch (Throwable t) {
t.printStackTrace();
}
}
}

View File

@ -0,0 +1,211 @@
package com.terraforged.app;
import processing.core.PApplet;
import processing.event.KeyEvent;
import processing.event.MouseEvent;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
public class Controller {
private static final int BUTTON_NONE = -1;
private static final int BUTTON_1 = 37;
private static final int BUTTON_2 = 39;
private static final int BUTTON_3 = 3;
private static final float cameraSpeed = 100F;
private static final float zoomSpeed = 0.01F;
private static final float rotateSpeed = 0.002F;
private static final float translateSpeed = 2F;
private static final float moveSpeed = 10F;
private int mouseButton = BUTTON_NONE;
private int lastX = 0;
private int lastY = 0;
private float yaw = -0.2F;
private float pitch = 0.85F;
private float translateX = 0F;
private float translateY = 0F;
private float translateZ = -800;
private float velocityX = 0F;
private float velocityY = 0F;
private int colorMode = 1;
private int renderMode = 0;
private int newSeed = 0;
private int left = 0;
private int right = 0;
private int up = 0;
private int down = 0;
private float zoom = 16;
private boolean filters = true;
public void apply(PApplet applet) {
applet.translate(translateX, translateY, translateZ);
applet.translate(applet.width / 2, applet.height / 2, 0);
applet.rotateX(pitch);
applet.rotateZ(yaw);
update();
}
public void update() {
float forward = up + down;
float strafe = left + right;
velocityX = forward * (float) Math.sin(yaw);
velocityY = forward * (float) Math.cos(yaw);
velocityX += strafe * (float) Math.sin(yaw + Math.toRadians(90));
velocityY += strafe * (float) Math.cos(yaw + Math.toRadians(90));
if (velocityX != 0 || velocityY != 0) {
float magnitude = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
velocityX /= magnitude;
velocityY /= magnitude;
}
}
public int getColorMode() {
return colorMode;
}
public int getRenderMode() {
return renderMode;
}
public float velocityX() {
return velocityX * moveSpeed / zoom;
}
public float velocityY() {
return velocityY * moveSpeed / zoom;
}
public float zoomLevel() {
return zoom;
}
public boolean filters() {
return filters;
}
public int getNewSeed() {
if (newSeed == 1) {
newSeed = 0;
return 1;
}
if (newSeed != 0) {
int val = newSeed;
newSeed = 0;
return val;
}
return 0;
}
public void keyPress(KeyEvent event) {
switch (event.getKey()) {
case 'w':
up = -1;
break;
case 'a':
left = -1;
break;
case 's':
down = 1;
break;
case 'd':
right = 1;
break;
}
}
public void keyRelease(KeyEvent event) {
switch (event.getKey()) {
case 'w':
up = 0;
return;
case 'a':
left = 0;
return;
case 's':
down = 0;
return;
case 'd':
right = 0;
return;
case 'r':
renderMode = renderMode == 0 ? 1 : 0;
return;
case 'n':
newSeed = 1;
return;
case 'm':
newSeed = Main.seed;
return;
case 'f':
filters = !filters;
return;
case 'c':
StringSelection selection = new StringSelection("" + Main.seed);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);
return;
case 'v':
try {
Object data = Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
newSeed = Integer.parseInt(data.toString());
} catch (Throwable t) {
t.printStackTrace();
}
}
if (event.getKey() >= '1' && event.getKey() <= '9') {
colorMode = event.getKey() - '0';
return;
}
}
public void mousePress(MouseEvent event) {
if (mouseButton == BUTTON_NONE) {
lastX = event.getX();
lastY = event.getY();
mouseButton = event.getButton();
}
}
public void mouseRelease(MouseEvent event) {
mouseButton = BUTTON_NONE;
}
public void mouseWheel(MouseEvent event) {
translateZ -= event.getCount() * cameraSpeed;
}
public void mouseDrag(MouseEvent event) {
int dx = event.getX() - lastX;
int dy = event.getY() - lastY;
boolean ctrl = (event.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
lastX = event.getX();
lastY = event.getY();
if (mouseButton == BUTTON_1) {
yaw -= dx * rotateSpeed;
pitch -= dy * rotateSpeed;
}
if (mouseButton == BUTTON_2) {
translateX += dx * translateSpeed;
translateY += dy * translateSpeed;
}
if (mouseButton == BUTTON_3) {
if (ctrl) {
zoom += (dy - dx) * zoom * zoomSpeed;
zoom = Math.max(1F, zoom);
} else {
translateZ -= (dy - dx) * cameraSpeed * 0.1F;
}
}
}
}

View File

@ -0,0 +1,239 @@
package com.terraforged.app;
import com.terraforged.app.biome.BiomeProvider;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.biome.BiomeData;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.terrain.Terrain;
import java.awt.*;
import java.util.Random;
public class Main extends Applet {
public static void main(String[] args) {
Main.start(-1);
}
public static void start(long seed) {
Main.seed = (int) seed;
Main.random = seed == -1;
main(Main.class.getName());
}
private static boolean random = false;
public static int seed = -1;
private Cache cache;
private float offsetX = 0;
private float offsetZ = 0;
private final String bits = System.getProperty("sun.arch.data.model");
private final BiomeProvider biomeProvider = new BiomeProvider();
@Override
public Cache getCache() {
return cache;
}
@Override
public void setup() {
super.setup();
if (random) {
setSeed(new Random(System.currentTimeMillis()).nextInt());
offsetX = 0;
offsetZ = 0;
}
setSeed(seed);
}
public void setSeed(int seed) {
Main.seed = seed;
cache = new Cache(seed);
System.out.println(seed);
}
@Override
public float color(Cell<Terrain> cell) {
switch (controller.getColorMode()) {
case STEEPNESS:
return hue(1 - cell.steepness, 64, 70);
case TEMPERATURE:
return hue(1 - cell.temperature, 64, 70);
case MOISTURE:
return hue(cell.moisture, 64, 70);
case TERRAIN_TYPE:
if (cell.tag == getCache().getTerrain().volcano) {
return 0F;
}
return 20 + (cell.tag.getId() / (float) Terrain.MAX_ID.get()) * 80;
case ELEVATION:
float value = (cell.value - 0.245F) / 0.65F;
return (1 - value) * 30;
case BIOME:
BiomeData biome = biomeProvider.getBiome(cell);
if (biome == null) {
return 0F;
}
return cell.biome * 70;
case CONTINENT:
return cell.continent * 70;
default:
return 50;
}
}
@Override
public void draw() {
int nextSeed = controller.getNewSeed();
if (nextSeed == 1) {
setup();
} else if (nextSeed != 0) {
setSeed(nextSeed);
}
offsetX += controller.velocityX() * controller.zoomLevel() * controller.zoomLevel();
offsetZ += controller.velocityY() * controller.zoomLevel() * controller.zoomLevel();
cache.update(offsetX, offsetZ, controller.zoomLevel(), controller.filters());
// color stuff
noStroke();
background(0);
colorMode(HSB, 100);
// lighting
ambientLight(0, 0, 75, width / 2, -height, height / 2);
pointLight(0, 0, 50, width / 2, -height * 100, height / 2);
// render
pushMatrix();
controller.apply(this);
// translate(-width / 2F, -height / 2F);
drawTerrain(controller.zoomLevel());
drawCompass();
// translate(0, 0, 255 * (width / (float) controller.resolution()) / controller.zoomLevel());
// mesh.renderWind(controller.resolution(), controller.zoomLevel());
popMatrix();
pushMatrix();
translate(0, 0, -1);
// drawGradient(0, height - 150, 100F, width, 150);
popMatrix();
drawStats();
drawBiomeKey();
drawControls();
}
private void drawGradient(int x, int y, float d, float w, float h) {
noFill();
for (int dy = 0; dy <= h; dy++) {
float dist = Math.min(1, dy / d);
stroke(0, 0, 0, dist * 100F);
line(x, y + dy, x + w, y + dy);
}
noStroke();
}
private void drawStats() {
int resolution = cache.getRegion().getBlockSize().size;
int blocks = NoiseUtil.round(resolution * controller.zoomLevel());
String[][] info = {
{"Java:", String.format("x%s", bits)},
{"Fps: ", String.format("%.3f", frameRate)},
{"Seed:", String.format("%s", seed)},
{"Zoom: ", String.format("%.2f", controller.zoomLevel())},
{"Area: ", String.format("%sx%s [%sx%s]", blocks, blocks, resolution, resolution)},
{"Center: ", String.format("x=%.0f, y=%s, z=%.0f", offsetX, cache.getCenterHeight(), offsetZ)},
{"Terrain: ", String.format("%s:%s", cache.getCenterTerrain().getName(),
cache.getCenterBiomeType().name())},
{"Biome: ", String.format("%s", biomeProvider.getBiome(cache.getCenterCell()).name)},
{"Overlay: ", colorModeName()},
};
int widest = 0;
for (String[] s : info) {
widest = Math.max(widest, (int) textWidth(s[0]));
}
int top = 20;
int lineHeight = 15;
for (String[] s : info) {
leftAlignText(10, top, 0, s[0]);
top += lineHeight;
}
top = 20;
for (String[] s : info) {
if (s.length == 2) {
leftAlignText(12 + widest, top, 0, s[1]);
top += lineHeight;
}
}
}
private void drawBiomeKey() {
int top = 20;
int lineHeight = 15;
int widest = 0;
for (BiomeType type : BiomeType.values()) {
widest = Math.max(widest, (int) textWidth(type.name()));
}
int left = width - widest - lineHeight - 15;
for (BiomeType type : BiomeType.values()) {
leftAlignText(left, top, 0, type.name());
float[] hsb = Color.RGBtoHSB(type.getColor().getRed(), type.getColor().getGreen(),
type.getColor().getBlue(), null);
fill(0, 0, 100);
rect(width - lineHeight - 11, top - lineHeight + 1, lineHeight + 2, lineHeight + 2);
fill(hsb[0] * 100, hsb[1] * 100, hsb[2] * 100);
rect(width - lineHeight - 10, top - lineHeight + 2, lineHeight, lineHeight);
top += lineHeight;
}
}
private void drawControls() {
String[][][] columns = {
{
{"Mouse-Left + Move", " - Rotate terrain"},
{"Mouse-Right + Move", " - Pan terrain"},
{"Mouse-Scroll + Move", " - Zoom camera"},
{"Mouse-Scroll + LCTRL + Move", " - Zoom terrain"},
{"WASD", "- Move terrain"}
}, {
{"Key 1-8", "- Select overlay"},
{"Key R", "- Toggle mesh renderer"},
{"Key F", "- Toggle filters"},
{"Key N", "- Generate new world"},
{"Key C", "- Copy seed to clipboard"},
{"Key V", "- Paste seed from clipboard"},
}};
int lineHeight = 15;
int rows = 0;
for (String[][] column : columns) {
rows = Math.max(rows, column.length);
}
int left = 10;
int widest = 0;
for (String[][] column : columns) {
int top = (height - 10) - ((rows - 1) * lineHeight);
for (String[] row : column) {
int width = 0;
for (String cell : row) {
leftAlignText(left + width, top, 0, cell);
width += (int) textWidth(cell);
}
top += lineHeight;
widest = Math.max(widest, width);
}
left += widest + 10;
}
}
}

View File

@ -0,0 +1,34 @@
package com.terraforged.app.biome;
import me.dags.noise.util.NoiseUtil;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
public class BiomeColor {
private static final BufferedImage image = load();
private static final int width = image.getWidth() - 1;
private static final int height = image.getHeight() - 1;
public static int getRGB(float temp, float moist) {
float humidity = temp * moist;
temp = 1 - temp;
humidity = 1 - humidity;
int x = NoiseUtil.round(temp * width);
int y = NoiseUtil.round(humidity * height);
return image.getRGB(x, y);
}
private static BufferedImage load() {
try (InputStream inputStream = BiomeColor.class.getResourceAsStream("/grass.png")) {
return ImageIO.read(inputStream);
} catch (IOException e) {
e.printStackTrace();
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
}
}
}

View File

@ -0,0 +1,109 @@
package com.terraforged.app.biome;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.grid.FixedGrid;
import com.terraforged.core.world.biome.BiomeData;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.terrain.Terrain;
import processing.data.JSONArray;
import processing.data.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
public class BiomeProvider {
private final List<FixedGrid<BiomeData>> biomes;
public BiomeProvider() {
biomes = getBiomes(5);
}
public BiomeData getBiome(Cell<Terrain> cell) {
FixedGrid<BiomeData> grid = biomes.get(cell.biomeType.ordinal());
if (grid == null) {
return null;
}
return grid.get(cell.moisture, cell.temperature, cell.biome);
}
private static List<FixedGrid<BiomeData>> getBiomes(int gridSize) {
List<FixedGrid<BiomeData>> data = new ArrayList<>();
for (BiomeType type : BiomeType.values()) {
data.add(type.ordinal(), null);
}
Map<String, BiomeData> biomes = loadBiomes();
Map<BiomeType, List<String>> types = loadBiomeTypes();
for (Map.Entry<BiomeType, List<String>> e : types.entrySet()) {
List<BiomeData> list = new LinkedList<>();
for (String id : e.getValue()) {
BiomeData biome = biomes.get(id);
if (biome != null) {
list.add(biome);
}
}
FixedGrid<BiomeData> grid = FixedGrid.generate(gridSize, list, b -> b.rainfall, b -> b.temperature);
data.set(e.getKey().ordinal(), grid);
}
return data;
}
private static Map<String, BiomeData> loadBiomes() {
try (InputStream inputStream = BiomeProvider.class.getResourceAsStream("/biome_data.json")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
reader.lines().forEach(sb::append);
JSONArray array = JSONArray.parse(sb.toString());
Map<String,BiomeData> biomes = new HashMap<>();
for (int i = 0; i < array.size(); i++) {
JSONObject object = array.getJSONObject(i);
String name = object.getString("id");
float moisture = object.getFloat("moisture");
float temperature = object.getFloat("temperature");
int color = BiomeColor.getRGB(temperature, moisture);
BiomeData biome = new BiomeData(name, null, color, moisture, temperature);
biomes.put(name, biome);
}
return biomes;
} catch (IOException e) {
e.printStackTrace();
return Collections.emptyMap();
}
}
private static Map<BiomeType, List<String>> loadBiomeTypes() {
try (InputStream inputStream = BiomeProvider.class.getResourceAsStream("/biome_groups.json")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
reader.lines().forEach(sb::append);
JSONObject object = JSONObject.parse(sb.toString());
Iterator iterator = object.keyIterator();
Map<BiomeType, List<String>> biomes = new HashMap<>();
while (iterator.hasNext()) {
String key = "" + iterator.next();
if (key.contains("rivers")) {
continue;
}
if (key.contains("oceans")) {
continue;
}
BiomeType type = BiomeType.valueOf(key);
List<String> group = new LinkedList<>();
JSONArray array = object.getJSONArray(key);
for (int i = 0; i < array.size(); i++) {
group.add(array.getString(i));
}
biomes.put(type, group);
}
return biomes;
} catch (IOException e) {
e.printStackTrace();
return Collections.emptyMap();
}
}
}

View File

@ -0,0 +1,116 @@
package com.terraforged.app.mesh;
public class Mesh {
private final Mesh inner;
private final float x0;
private final float y0;
private final float x1;
private final float y1;
private final float quality;
public Mesh(Mesh inner, float x0, float y0, float x1, float y1, float quality) {
this.inner = inner;
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.quality = quality;
}
public Mesh expand(float scale) {
return expand(scale, scale);
}
public Mesh expand(float scale, float quality) {
float width0 = getWidth();
float height0 = getHeight();
float width1 = width0 * scale;
float height1 = height0 * scale;
float deltaX = (width1 - width0) / 2F;
float deltaY = (height1 - height0) / 2F;
float newQuality = this.quality * quality;
return new Mesh(this, x0 - deltaX, y0 - deltaY, x1 + deltaX, y1 + deltaY, newQuality);
}
public float getWidth() {
return x1 - x0;
}
public float getHeight() {
return y1 - y0;
}
public void start(float width, float height) {
if (inner != null) {
inner.start(width, height);
}
}
public void render() {
if (inner == null) {
renderNormal();
} else {
renderCutout();
inner.render();
}
}
public void beginStrip() {
if (inner != null) {
inner.beginStrip();
}
}
public void endStrip() {
if (inner != null) {
inner.endStrip();
}
}
public void visit(float x, float y) {
if (inner != null) {
inner.visit(x, y);
}
}
private void renderNormal() {
beginStrip();
iterate(x0, y0, x1, y1);
endStrip();
}
private void renderCutout() {
beginStrip();
iterate(x0, y0, inner.x1, inner.y0);
endStrip();
beginStrip();
iterate(inner.x1, y0, x1, inner.y1);
endStrip();
beginStrip();
iterate(inner.x0, inner.y1, x1, y1);
endStrip();
beginStrip();
iterate(x0, inner.y0, inner.x0, y1);
endStrip();
}
private void iterate(float minX, float minY, float maxX, float maxY) {
float x = minX - quality;
float y = minY - quality;
while (y < maxY) {
y = Math.min(y + quality, maxY);
beginStrip();
while (x < maxX) {
x = Math.min(x + quality, maxX);
visit(x, y);
visit(x, y + quality);
}
x = minX - quality;
endStrip();
}
}
}

View File

@ -0,0 +1,42 @@
package com.terraforged.app.mesh;
import com.terraforged.app.renderer.Renderer;
import com.terraforged.app.Applet;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
import processing.core.PApplet;
public class NoiseMesh extends Mesh {
private final Applet applet;
private final Renderer renderer;
private final Cell<Terrain> cell = new Cell<>();
public NoiseMesh(Applet applet, Renderer renderer, float x0, float y0, float x1, float y1) {
super(null, x0, y0, x1, y1, 1F);
this.applet = applet;
this.renderer = renderer;
}
@Override
public void start(float width, float height) {
applet.noStroke();
}
@Override
public void beginStrip() {
applet.beginShape(PApplet.TRIANGLE_STRIP);
}
@Override
public void endStrip() {
applet.endShape();
}
@Override
public void visit(float x, float y) {
float height = cell.value * 255;
float surface = renderer.getSurface(cell, height, 63, 10F);
applet.vertex(x * 10F, y * 10F, (int) surface);
}
}

View File

@ -0,0 +1,33 @@
package com.terraforged.app.mesh;
import com.terraforged.app.renderer.Renderer;
import com.terraforged.app.Applet;
public class TestRenderer extends Renderer {
private static boolean printed = false;
public TestRenderer(Applet visualizer) {
super(visualizer);
}
@Override
public void render(float zoom) {
Mesh mesh = new NoiseMesh(applet, this, -64, -64, 64, 64);
for (int i = 0; i < 1; i++) {
// mesh = mesh.expand(4);
}
float width = mesh.getWidth();
float height = mesh.getHeight();
if (!printed) {
printed = true;
System.out.println(width + "x" + height);
}
applet.pushMatrix();
mesh.start(mesh.getWidth(), mesh.getHeight());
mesh.render();
applet.popMatrix();
}
}

View File

@ -0,0 +1,50 @@
package com.terraforged.app.renderer;
import com.terraforged.app.Applet;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
import processing.core.PApplet;
public class MeshRenderer extends Renderer {
public MeshRenderer(Applet visualizer) {
super(visualizer);
}
@Override
public void render(float zoom) {
float seaLevel = new Levels(applet.getCache().getSettings().generator).water;
int worldHeight = applet.getCache().getSettings().generator.world.worldHeight;
int waterLevel = (int) (seaLevel * worldHeight);
int seabedLevel = (int) ((seaLevel - 0.04) * worldHeight);
int resolution = applet.getCache().getRegion().getBlockSize().size;
float w = applet.width / (float) (resolution - 1);
float h = applet.width / (float) (resolution - 1);
applet.noStroke();
applet.pushMatrix();
applet.translate(-applet.width / 2F, -applet.width / 2F);
for (int dy = 0; dy < resolution - 1; dy++) {
applet.beginShape(PApplet.TRIANGLE_STRIP);
for (int dx = 0; dx < resolution; dx++) {
draw(dx, dy, w, h, zoom, worldHeight, waterLevel, seabedLevel);
draw(dx, dy + 1, w, h, zoom, worldHeight, waterLevel, resolution / 2);
}
applet.endShape();
}
applet.popMatrix();
}
private void draw(int dx, int dz, float w, float h, float zoom, int worldHeight, int waterLevel, int center) {
Cell<Terrain> cell = applet.getCache().getRegion().getCell(dx, dz);
float height = (cell.value * worldHeight);
float x = dx * w;
float z = dz * h;
float y = (int) getSurface(cell, height, waterLevel, 1);
applet.vertex(x, z, y / (zoom * 0.2F));
}
}

View File

@ -0,0 +1,105 @@
package com.terraforged.app.renderer;
import com.terraforged.app.Applet;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
import java.awt.*;
public abstract class Renderer {
protected final Applet applet;
protected Renderer(Applet visualizer) {
this.applet = visualizer;
}
public float getSurface(Cell<Terrain> cell, float height, int waterLevel, float el) {
if (cell.tag == applet.getCache().getTerrain().volcanoPipe) {
applet.fill(2, 80, 64);
return height * 0.95F * el;
}
if (height < waterLevel) {
float temp = cell.temperature;
float tempDelta = temp > 0.5 ? temp - 0.5F : -(0.5F - temp);
float tempAlpha = (tempDelta / 0.5F);
float hueMod = 4 * tempAlpha;
float depth = (waterLevel - height) / (float) (90);
float darkness = (1 - depth);
float darknessMod = 0.5F + (darkness * 0.5F);
applet.fill(60 - hueMod, 65, 90 * darknessMod);
return height * el;
} else if (applet.controller.getColorMode() == Applet.ELEVATION) {
float hei = Math.min(1, Math.max(0, height - waterLevel) / (255F - waterLevel));
float temp = cell.temperature;
float moist = Math.min(temp, cell.moisture);
float hue = 35 - (temp * (1 - moist)) * 25;
float sat = 75 * (1 - hei);
float bri = 50 + 40 * hei;
applet.fill(hue, sat, bri);
return height * el;
} else if (applet.controller.getColorMode() == Applet.BIOME_TYPE) {
Color c = cell.biomeType.getColor();
float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
float bri = 90 + cell.biomeTypeMask * 10;
applet.fill(hsb[0] * 100, hsb[1] * 100, hsb[2] * bri);
return height * el;
} else if (applet.controller.getColorMode() == Applet.BIOME) {
float hue = applet.color(cell);
float sat = 70;
float bright = 50 + 50 * cell.riverMask;
applet.fill(hue, sat, bright);
return height * el;
} else if(applet.controller.getColorMode() == Applet.TERRAIN_TYPE) {
float hue = applet.color(cell);
if (cell.tag == applet.getCache().getTerrain().coast) {
hue = 15;
}
float modifier = cell.mask;
float modAlpha = 0.1F;
float mod = (1 - modAlpha) + (modifier * modAlpha);
float sat = 70;
float bri = 70;
applet.fill(hue, 65, 70);
return height * el;
} else if(applet.controller.getColorMode() == Applet.EROSION) {
float change = cell.sediment + cell.erosion;
float value = Math.abs(cell.sediment * 250);
value = Math.max(0, Math.min(1, value));
float hue = value * 70;
float sat = 70;
float bri = 70;
applet.fill(hue, sat, bri);
return height * el;
} else {
float hue = applet.color(cell);
float sat = 70;
float bri = 70;
applet.fill(hue, sat, bri);
return height * el;
}
}
public abstract void render(float zoom);
private void renderGradLine(int steps, float x1, float y1, float x2, float y2, float hue1, float hue2, float sat, float bright) {
float dx = x2 - x1;
float dy = y2 - y1;
float fx = dx / steps;
float fy = dy / steps;
float dhue = hue2 - hue1;
float fhue = dhue / steps;
for (int i = 0; i < steps; i++) {
float px1 = x1 + (i * fx);
float py1 = y1 + (i * fy);
float px2 = x2 + ((i + 1) * fx);
float py2 = y2 + ((i + 1) * fy);
float hue = (i + 1) * fhue;
applet.stroke(hue1 + hue, sat, bright);
applet.line(px1, py1, px2, py2);
}
}
}

View File

@ -0,0 +1,130 @@
package com.terraforged.app.renderer;
import com.terraforged.app.Applet;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
import processing.core.PApplet;
public class VoxelRenderer extends Renderer {
public VoxelRenderer(Applet visualizer) {
super(visualizer);
}
@Override
public void render(float zoom) {
Levels levels = new Levels(applet.getCache().getSettings().generator);
int worldHeight = applet.getCache().getSettings().generator.world.worldHeight;
int waterLevel = levels.waterY;
int resolution = applet.getCache().getRegion().getBlockSize().size;
int center = resolution / 2;
float w = applet.width / (float) resolution;
float h = applet.width / (float) resolution;
float el = w / zoom;
applet.pushMatrix();
applet.translate(-applet.width / 2F, -applet.width / 2F);
int difX = Math.max(0, applet.getCache().getRegion().getBlockSize().size - resolution);
int difY = Math.max(0, applet.getCache().getRegion().getBlockSize().size - resolution);
int offsetX = difX / 2;
int offsetZ = difY / 2;
for (int dy = 0; dy < resolution; dy++) {
for (int dx = 0; dx < resolution; dx++) {
Cell<Terrain> cell = applet.getCache().getRegion().getCell(dx + offsetX, dy + offsetZ);
float cellHeight = cell.value * worldHeight;
int height = Math.min(worldHeight, Math.max(0, (int) cellHeight));
if (height < 0) {
continue;
}
float x0 = dx * w;
float x1 = (dx + 1) * w;
float z0 = dy * h;
float z1 = (dy + 1) * h;
float y = getSurface(cell, height, waterLevel, el);
if ((dx == center && (dy == center || dy - 1 == center || dy + 1 == center))
|| (dy == center && (dx - 1 == center || dx + 1 == center))) {
applet.fill(100F, 100F, 100F);
}
drawColumn(x0, z0, 0, x1, z1, y);
}
}
drawRulers(16, worldHeight, resolution, h, el);
applet.popMatrix();
}
private void drawRulers(int step, int max, int resolution, float unit, float height) {
float width = (resolution + 1) * unit;
int doubleStep = step * 2;
for (int dz = 0; dz <= 1; dz++) {
for (int dx = 0; dx <= 1; dx++) {
float x0 = dx * width;
float x1 = x0 - unit;
float z0 = dz * width;
float z1 = z0 - unit;
for (int dy = 0; dy < max; dy += step) {
float y0 = dy * height;
float y1 = y0 + (step * height);
float h = 100, s = 100, b = 100;
if ((dy % doubleStep) != step) {
s = 0;
}
applet.fill(h, s, b);
drawColumn(x0, z0, y0, x1, z1, y1);
}
}
}
}
private void drawColumn(float x0, float y0, float z0, float x1, float y1, float z1) {
applet.beginShape(PApplet.QUADS);
// +Z "front" face
applet.vertex(x0, y0, z1);
applet.vertex(x1, y0, z1);
applet.vertex(x1, y1, z1);
applet.vertex(x0, y1, z1);
// -Z "back" face
applet.vertex(x1, y0, z0);
applet.vertex(x0, y0, z0);
applet.vertex(x0, y1, z0);
applet.vertex(x1, y1, z0);
// +Y "bottom" face
applet.vertex(x0, y1, z1);
applet.vertex(x1, y1, z1);
applet.vertex(x1, y1, z0);
applet.vertex(x0, y1, z0);
// -Y "top" face
applet.vertex(x0, y0, z0);
applet.vertex(x1, y0, z0);
applet.vertex(x1, y0, z1);
applet.vertex(x0, y0, z1);
// +X "right" face
applet.vertex(x1, y0, z1);
applet.vertex(x1, y0, z0);
applet.vertex(x1, y1, z0);
applet.vertex(x1, y1, z1);
// -X "left" face
applet.vertex(x0, y0, z0);
applet.vertex(x0, y0, z1);
applet.vertex(x0, y1, z1);
applet.vertex(x0, y1, z0);
applet.endShape();
}
}

View File

@ -0,0 +1,764 @@
[
{
"id": "biomesoplenty:alps",
"moisture": 0.2,
"temperature": 0.1,
"color": -2302756
},
{
"id": "biomesoplenty:alps_foothills",
"moisture": 0.2,
"temperature": 0.1,
"color": -10461088
},
{
"id": "biomesoplenty:bayou",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "biomesoplenty:bog",
"moisture": 0.59999996,
"temperature": 0.42,
"color": -13592211
},
{
"id": "biomesoplenty:boreal_forest",
"moisture": 0.4,
"temperature": 0.32,
"color": -13592211
},
{
"id": "biomesoplenty:brushland",
"moisture": 0.06666667,
"temperature": 0.8,
"color": -13592211
},
{
"id": "biomesoplenty:chaparral",
"moisture": 0.29999998,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:cherry_blossom_grove",
"moisture": 0.59999996,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:cold_desert",
"moisture": 0.0,
"temperature": 0.3,
"color": -6034953
},
{
"id": "biomesoplenty:coniferous_forest",
"moisture": 0.33333334,
"temperature": 0.38,
"color": -13592211
},
{
"id": "biomesoplenty:dead_forest",
"moisture": 0.2,
"temperature": 0.32,
"color": -13592211
},
{
"id": "biomesoplenty:fir_clearing",
"moisture": 0.33333334,
"temperature": 0.38,
"color": -13592211
},
{
"id": "biomesoplenty:floodplain",
"moisture": 0.8,
"temperature": 0.56,
"color": -13592211
},
{
"id": "biomesoplenty:flower_meadow",
"moisture": 0.46666667,
"temperature": 0.35999998,
"color": -13592211
},
{
"id": "biomesoplenty:grassland",
"moisture": 0.46666667,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:gravel_beach",
"moisture": 0.33333334,
"temperature": 0.44,
"color": -6034953
},
{
"id": "biomesoplenty:grove",
"moisture": 0.18333334,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:highland",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:highland_moor",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:lavender_field",
"moisture": 0.46666667,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:lush_grassland",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "biomesoplenty:lush_swamp",
"moisture": 0.6666667,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "biomesoplenty:mangrove",
"moisture": 0.46666667,
"temperature": 0.524,
"color": -13592211
},
{
"id": "biomesoplenty:maple_woods",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "biomesoplenty:marsh",
"moisture": 0.46666667,
"temperature": 0.45999998,
"color": -13592211
},
{
"id": "biomesoplenty:meadow",
"moisture": 0.46666667,
"temperature": 0.35999998,
"color": -13592211
},
{
"id": "biomesoplenty:mire",
"moisture": 0.59999996,
"temperature": 0.42,
"color": -13592211
},
{
"id": "biomesoplenty:mystic_grove",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "biomesoplenty:oasis",
"moisture": 0.33333334,
"temperature": 1.0,
"color": -6034953
},
{
"id": "biomesoplenty:ominous_woods",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:orchard",
"moisture": 0.26666668,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:outback",
"moisture": 0.033333335,
"temperature": 1.0,
"color": -6034953
},
{
"id": "biomesoplenty:overgrown_cliffs",
"moisture": 0.53333336,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "biomesoplenty:pasture",
"moisture": 0.2,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:prairie",
"moisture": 0.2,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:pumpkin_patch",
"moisture": 0.53333336,
"temperature": 0.35999998,
"color": -13592211
},
{
"id": "biomesoplenty:rainforest",
"moisture": 1.0,
"temperature": 0.54,
"color": -13592211
},
{
"id": "biomesoplenty:redwood_forest",
"moisture": 0.4,
"temperature": 0.52,
"color": -12427646
},
{
"id": "biomesoplenty:redwood_forest_edge",
"moisture": 0.4,
"temperature": 0.52,
"color": -12427646
},
{
"id": "biomesoplenty:scrubland",
"moisture": 0.06666667,
"temperature": 0.64,
"color": -13592211
},
{
"id": "biomesoplenty:seasonal_forest",
"moisture": 0.53333336,
"temperature": 0.35999998,
"color": -13592211
},
{
"id": "biomesoplenty:shield",
"moisture": 0.53333336,
"temperature": 0.35999998,
"color": -13592211
},
{
"id": "biomesoplenty:shrubland",
"moisture": 0.033333335,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:silkglade",
"moisture": 0.13333334,
"temperature": 0.5,
"color": -13592211
},
{
"id": "biomesoplenty:snowy_coniferous_forest",
"moisture": 0.33333334,
"temperature": 0.1,
"color": -13592211
},
{
"id": "biomesoplenty:snowy_fir_clearing",
"moisture": 0.33333334,
"temperature": 0.1,
"color": -13592211
},
{
"id": "biomesoplenty:snowy_forest",
"moisture": 0.33333334,
"temperature": 0.1,
"color": -13592211
},
{
"id": "biomesoplenty:steppe",
"moisture": 0.033333335,
"temperature": 0.51,
"color": -13592211
},
{
"id": "biomesoplenty:temperate_rainforest",
"moisture": 0.8,
"temperature": 0.45999998,
"color": -13592211
},
{
"id": "biomesoplenty:temperate_rainforest_hills",
"moisture": 0.8,
"temperature": 0.45999998,
"color": -13592211
},
{
"id": "biomesoplenty:tropical_rainforest",
"moisture": 0.6666667,
"temperature": 0.6,
"color": -13592211
},
{
"id": "biomesoplenty:tundra",
"moisture": 0.33333334,
"temperature": 0.28,
"color": -13592211
},
{
"id": "biomesoplenty:wasteland",
"moisture": 0.0,
"temperature": 1.0,
"color": -12427646
},
{
"id": "biomesoplenty:wetland",
"moisture": 0.46666667,
"temperature": 0.44,
"color": -13592211
},
{
"id": "biomesoplenty:white_beach",
"moisture": 0.6666667,
"temperature": 0.58000004,
"color": -6034953
},
{
"id": "biomesoplenty:woodland",
"moisture": 0.33333334,
"temperature": 0.52,
"color": -13592211
},
{
"id": "biomesoplenty:xeric_shrubland",
"moisture": 0.06666667,
"temperature": 0.9,
"color": -6034953
},
{
"id": "minecraft:badlands",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:badlands_plateau",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:bamboo_jungle",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "minecraft:bamboo_jungle_hills",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "minecraft:beach",
"moisture": 0.26666668,
"temperature": 0.52,
"color": -6034953
},
{
"id": "minecraft:birch_forest",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "minecraft:birch_forest_hills",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "minecraft:cold_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:dark_forest",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "minecraft:dark_forest_hills",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "minecraft:deep_cold_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:deep_frozen_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:deep_lukewarm_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:deep_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:deep_warm_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -6034953
},
{
"id": "minecraft:desert",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:desert_hills",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:desert_lakes",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:eroded_badlands",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:flower_forest",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "minecraft:forest",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "minecraft:frozen_ocean",
"moisture": 0.33333334,
"temperature": 0.2,
"color": -13592211
},
{
"id": "minecraft:frozen_river",
"moisture": 0.33333334,
"temperature": 0.2,
"color": -13592211
},
{
"id": "minecraft:giant_spruce_taiga",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "minecraft:giant_spruce_taiga_hills",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "minecraft:giant_tree_taiga",
"moisture": 0.53333336,
"temperature": 0.32,
"color": -13592211
},
{
"id": "minecraft:giant_tree_taiga_hills",
"moisture": 0.53333336,
"temperature": 0.32,
"color": -13592211
},
{
"id": "minecraft:gravelly_mountains",
"moisture": 0.2,
"temperature": 0.28,
"color": -13592211
},
{
"id": "minecraft:ice_spikes",
"moisture": 0.33333334,
"temperature": 0.2,
"color": -2302756
},
{
"id": "minecraft:jungle",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "minecraft:jungle_hills",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "minecraft:lukewarm_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:modified_badlands_plateau",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:modified_gravelly_mountains",
"moisture": 0.2,
"temperature": 0.28,
"color": -13592211
},
{
"id": "minecraft:modified_jungle",
"moisture": 0.59999996,
"temperature": 0.58000004,
"color": -13592211
},
{
"id": "minecraft:modified_wooded_badlands_plateau",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:mountains",
"moisture": 0.2,
"temperature": 0.28,
"color": -13592211
},
{
"id": "minecraft:mushroom_field_shore",
"moisture": 0.6666667,
"temperature": 0.56,
"color": -13592211
},
{
"id": "minecraft:mushroom_fields",
"moisture": 0.6666667,
"temperature": 0.56,
"color": -13592211
},
{
"id": "minecraft:ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:plains",
"moisture": 0.26666668,
"temperature": 0.52,
"color": -13592211
},
{
"id": "minecraft:river",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -13592211
},
{
"id": "minecraft:savanna",
"moisture": 0.0,
"temperature": 0.68,
"color": -13592211
},
{
"id": "minecraft:savanna_plateau",
"moisture": 0.0,
"temperature": 0.6,
"color": -13592211
},
{
"id": "minecraft:shattered_savanna",
"moisture": 0.0,
"temperature": 0.64,
"color": -13592211
},
{
"id": "minecraft:shattered_savanna_plateau",
"moisture": 0.0,
"temperature": 0.6,
"color": -13592211
},
{
"id": "minecraft:snowy_beach",
"moisture": 0.2,
"temperature": 0.22,
"color": -6034953
},
{
"id": "minecraft:snowy_mountains",
"moisture": 0.33333334,
"temperature": 0.2,
"color": -13592211
},
{
"id": "minecraft:snowy_taiga",
"moisture": 0.26666668,
"temperature": 0.0,
"color": -13592211
},
{
"id": "minecraft:snowy_taiga_hills",
"moisture": 0.26666668,
"temperature": 0.0,
"color": -13592211
},
{
"id": "minecraft:snowy_taiga_mountains",
"moisture": 0.26666668,
"temperature": 0.0,
"color": -13592211
},
{
"id": "minecraft:snowy_tundra",
"moisture": 0.33333334,
"temperature": 0.2,
"color": -13592211
},
{
"id": "minecraft:sunflower_plains",
"moisture": 0.26666668,
"temperature": 0.52,
"color": -13592211
},
{
"id": "minecraft:swamp",
"moisture": 0.59999996,
"temperature": 0.52,
"color": -13592211
},
{
"id": "minecraft:swamp_hills",
"moisture": 0.59999996,
"temperature": 0.52,
"color": -13592211
},
{
"id": "minecraft:taiga",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "minecraft:taiga_hills",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "minecraft:taiga_mountains",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
},
{
"id": "minecraft:tall_birch_forest",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "minecraft:tall_birch_hills",
"moisture": 0.4,
"temperature": 0.44,
"color": -13592211
},
{
"id": "minecraft:warm_ocean",
"moisture": 0.33333334,
"temperature": 0.4,
"color": -6034953
},
{
"id": "minecraft:wooded_badlands_plateau",
"moisture": 0.0,
"temperature": 1.0,
"color": -6034953
},
{
"id": "minecraft:wooded_hills",
"moisture": 0.53333336,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "minecraft:wooded_mountains",
"moisture": 0.2,
"temperature": 0.28,
"color": -13592211
},
{
"id": "terraforged:cold_steppe",
"moisture": 0.06666667,
"temperature": 0.28,
"color": -13592211
},
{
"id": "terraforged:savanna_scrub",
"moisture": 0.0,
"temperature": 0.68,
"color": -13592211
},
{
"id": "terraforged:shattered_savanna_scrub",
"moisture": 0.0,
"temperature": 0.64,
"color": -13592211
},
{
"id": "terraforged:snowy_taiga_scrub",
"moisture": 0.26666668,
"temperature": 0.0,
"color": -13592211
},
{
"id": "terraforged:steppe",
"moisture": 0.06666667,
"temperature": 0.48000002,
"color": -13592211
},
{
"id": "terraforged:taiga_scrub",
"moisture": 0.53333336,
"temperature": 0.3,
"color": -13592211
}
]

View File

@ -0,0 +1,168 @@
{
"TROPICAL_RAINFOREST": [
"biomesoplenty:overgrown_cliffs",
"biomesoplenty:tropical_rainforest",
"minecraft:bamboo_jungle",
"minecraft:bamboo_jungle_hills",
"minecraft:jungle",
"minecraft:jungle_hills",
"minecraft:modified_jungle"
],
"SAVANNA": [
"biomesoplenty:brushland",
"biomesoplenty:scrubland",
"biomesoplenty:xeric_shrubland",
"minecraft:savanna",
"minecraft:savanna_plateau",
"minecraft:shattered_savanna",
"minecraft:shattered_savanna_plateau",
"terraforged:savanna_scrub",
"terraforged:shattered_savanna_scrub"
],
"DESERT": [
"biomesoplenty:oasis",
"biomesoplenty:outback",
"biomesoplenty:wasteland",
"minecraft:badlands",
"minecraft:badlands_plateau",
"minecraft:desert",
"minecraft:desert_hills",
"minecraft:desert_lakes",
"minecraft:eroded_badlands",
"minecraft:modified_badlands_plateau",
"minecraft:modified_wooded_badlands_plateau",
"minecraft:wooded_badlands_plateau"
],
"TEMPERATE_RAINFOREST": [
"biomesoplenty:rainforest",
"biomesoplenty:temperate_rainforest",
"biomesoplenty:temperate_rainforest_hills",
"minecraft:plains"
],
"TEMPERATE_FOREST": [
"biomesoplenty:cherry_blossom_grove",
"biomesoplenty:grove",
"biomesoplenty:mystic_grove",
"biomesoplenty:orchard",
"biomesoplenty:pumpkin_patch",
"biomesoplenty:redwood_forest",
"biomesoplenty:redwood_forest_edge",
"biomesoplenty:seasonal_forest",
"biomesoplenty:silkglade",
"biomesoplenty:temperate_rainforest",
"biomesoplenty:temperate_rainforest_hills",
"biomesoplenty:woodland",
"minecraft:birch_forest",
"minecraft:birch_forest_hills",
"minecraft:dark_forest",
"minecraft:flower_forest",
"minecraft:forest",
"minecraft:plains",
"minecraft:tall_birch_forest",
"minecraft:wooded_hills"
],
"GRASSLAND": [
"biomesoplenty:bayou",
"biomesoplenty:bog",
"biomesoplenty:chaparral",
"biomesoplenty:floodplain",
"biomesoplenty:grassland",
"biomesoplenty:highland_moor",
"biomesoplenty:lavender_field",
"biomesoplenty:lush_grassland",
"biomesoplenty:lush_swamp",
"biomesoplenty:mangrove",
"biomesoplenty:marsh",
"biomesoplenty:mire",
"biomesoplenty:pasture",
"biomesoplenty:prairie",
"biomesoplenty:shrubland",
"biomesoplenty:steppe",
"biomesoplenty:wetland",
"minecraft:plains",
"minecraft:sunflower_plains",
"minecraft:swamp"
],
"COLD_STEPPE": [
"terraforged:cold_steppe"
],
"STEPPE": [
"biomesoplenty:steppe",
"terraforged:steppe"
],
"TAIGA": [
"biomesoplenty:boreal_forest",
"biomesoplenty:coniferous_forest",
"biomesoplenty:dead_forest",
"biomesoplenty:fir_clearing",
"biomesoplenty:flower_meadow",
"biomesoplenty:maple_woods",
"biomesoplenty:meadow",
"biomesoplenty:ominous_woods",
"biomesoplenty:shield",
"biomesoplenty:tundra",
"minecraft:giant_spruce_taiga",
"minecraft:giant_tree_taiga",
"minecraft:giant_tree_taiga_hills",
"minecraft:taiga",
"minecraft:taiga_hills",
"terraforged:taiga_scrub"
],
"TUNDRA": [
"biomesoplenty:alps",
"biomesoplenty:alps_foothills",
"biomesoplenty:snowy_coniferous_forest",
"biomesoplenty:snowy_fir_clearing",
"biomesoplenty:snowy_forest",
"minecraft:ice_spikes",
"minecraft:snowy_taiga",
"minecraft:snowy_taiga_hills",
"minecraft:snowy_tundra",
"terraforged:snowy_taiga_scrub"
],
"ALPINE": [
"biomesoplenty:highland",
"minecraft:gravelly_mountains",
"minecraft:modified_gravelly_mountains",
"minecraft:mountains",
"minecraft:snowy_mountains",
"minecraft:snowy_taiga_mountains",
"minecraft:taiga_mountains",
"minecraft:wooded_mountains"
],
"rivers": {
"COLD": [
"minecraft:frozen_river"
],
"MEDIUM": [
"minecraft:river"
],
"WARM": []
},
"oceans": {
"COLD": [
"minecraft:cold_ocean",
"minecraft:frozen_ocean"
],
"MEDIUM": [
"minecraft:ocean"
],
"WARM": [
"minecraft:lukewarm_ocean",
"minecraft:warm_ocean"
]
},
"deep_oceans": {
"COLD": [
"minecraft:deep_cold_ocean",
"minecraft:deep_frozen_ocean"
],
"MEDIUM": [
"minecraft:deep_ocean"
],
"WARM": [
"minecraft:deep_lukewarm_ocean",
"minecraft:deep_warm_ocean"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,5 @@
apply plugin: "java"
dependencies {
compile project(":Noise2D")
}

View File

@ -0,0 +1,98 @@
package com.terraforged.core.cell;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.terrain.Terrain;
public class Cell<T extends Tag> {
private static final Cell EMPTY = new Cell() {
@Override
public boolean isAbsent() {
return true;
}
};
private static final ObjectPool<Cell<Terrain>> POOL = new ObjectPool<>(100, Cell::new);
public float continent;
public float continentEdge;
public float value;
public float biome;
public float biomeMoisture;
public float biomeTemperature;
public float moisture;
public float temperature;
public float steepness;
public float erosion;
public float sediment;
public float mask = 1F;
public float biomeMask = 1F;
public float biomeTypeMask = 1F;
public float regionMask = 1F;
public float riverMask = 1F;
public BiomeType biomeType = BiomeType.GRASSLAND;
public T tag = null;
public void copy(Cell<T> other) {
continent = other.continent;
continentEdge = other.continentEdge;
value = other.value;
biome = other.biome;
biomeMask = other.biomeMask;
riverMask = other.riverMask;
biomeMoisture = other.biomeMoisture;
biomeTemperature = other.biomeTemperature;
mask = other.mask;
regionMask = other.regionMask;
moisture = other.moisture;
temperature = other.temperature;
steepness = other.steepness;
erosion = other.erosion;
sediment = other.sediment;
biomeType = other.biomeType;
biomeTypeMask = other.biomeTypeMask;
tag = other.tag;
}
public float combinedMask(float clamp) {
return NoiseUtil.map(biomeMask * regionMask, 0, clamp, clamp);
}
public float biomeMask(float clamp) {
return NoiseUtil.map(biomeMask, 0, clamp, clamp);
}
public float regionMask(float clamp) {
return NoiseUtil.map(regionMask, 0, clamp, clamp);
}
public boolean isAbsent() {
return false;
}
@SuppressWarnings("unchecked")
public static <T extends Tag> Cell<T> empty() {
return EMPTY;
}
public static ObjectPool.Item<Cell<Terrain>> pooled() {
return POOL.get();
}
public interface Visitor<T extends Tag> {
void visit(Cell<T> cell, int dx, int dz);
}
public interface ZoomVisitor<T extends Tag> {
void visit(Cell<T> cell, float x, float z);
}
}

View File

@ -0,0 +1,10 @@
package com.terraforged.core.cell;
import com.terraforged.core.world.terrain.Terrain;
public interface Extent {
Cell<Terrain> getCell(int x, int z);
void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor);
}

View File

@ -0,0 +1,23 @@
package com.terraforged.core.cell;
import me.dags.noise.Module;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.world.terrain.Terrain;
public interface Populator extends Module {
void apply(Cell<Terrain> cell, float x, float y);
void tag(Cell<Terrain> cell, float x, float y);
default float getValue(float x, float z) {
try (ObjectPool.Item<Cell<Terrain>> cell = Cell.pooled()) {
return getValue(cell.getValue(), x, z);
}
}
default float getValue(Cell<Terrain> cell, float x, float z) {
apply(cell, x, z);
return cell.value;
}
}

View File

@ -0,0 +1,6 @@
package com.terraforged.core.cell;
public interface Tag {
String getName();
}

View File

@ -0,0 +1,9 @@
package com.terraforged.core.decorator;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
public interface Decorator {
void apply(Cell<Terrain> cell, float x, float y);
}

View File

@ -0,0 +1,55 @@
package com.terraforged.core.decorator;
import me.dags.noise.Module;
import me.dags.noise.Source;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
public class DesertStacks implements Decorator {
private final float minY;
private final float maxY;
private final Module module;
public DesertStacks(Seed seed, Levels levels) {
Module mask = Source.perlin(seed.next(), 500, 1).clamp(0.7, 1).map(0, 1);
Module shape = Source.perlin(seed.next(), 25, 1).clamp(0.6, 1).map(0, 1)
.mult(Source.perlin(seed.next(), 8, 1).alpha(0.1));
Module top = Source.perlin(seed.next(), 4, 1).alpha(0.25);
Module scale = Source.perlin(seed.next(), 400, 1)
.clamp(20F / 255F, 25F / 255F);
Module stack = (x, y) -> {
float value = shape.getValue(x, y);
if (value > 0.3) {
return top.getValue(x, y);
}
return value * 0.95F;
};
this.minY = levels.water(0);
this.maxY = levels.water(50);
this.module = stack.scale(scale).mult(mask);
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
if (BiomeType.DESERT != cell.biomeType) {
return;
}
if (cell.value <= minY || cell.value > maxY) {
return;
}
float value = module.getValue(x, y);
value *= cell.biomeMask;
cell.value += value;
}
}

View File

@ -0,0 +1,59 @@
package com.terraforged.core.decorator;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.Terrains;
public class SwampPools implements Decorator {
private final Module module;
private final Levels levels;
private final Terrains terrains;
private final float minY;
private final float maxY;
private final float blendY;
private final float blendRange;
public SwampPools(Seed seed, Terrains terrains, Levels levels) {
this.levels = levels;
this.terrains = terrains;
this.minY = levels.water(-3);
this.maxY = levels.water(1);
this.blendY = levels.water(4);
this.blendRange = blendY - maxY;
this.module = Source.perlin(seed.next(), 14, 1).clamp(0.45, 0.8).map(0, 1);
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
if (cell.tag == terrains.ocean) {
return;
}
if (cell.moisture < 0.7 || cell.temperature < 0.3) {
return;
}
if (cell.value <= levels.water) {
return;
}
if (cell.value > blendY) {
return;
}
float alpha = module.getValue(x, y);
if (cell.value > maxY) {
float delta = blendY - cell.value;
float alpha2 = delta / blendRange;
alpha *= alpha2;
}
cell.value = NoiseUtil.lerp(cell.value, minY, alpha);
}
}

View File

@ -0,0 +1,234 @@
package com.terraforged.core.filter;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.world.heightmap.Levels;
import java.util.Random;
public class Erosion implements Filter {
private int erosionRadius = 3;
private float inertia = 0.05f;
private float sedimentCapacityFactor = 4;
private float minSedimentCapacity = 0.01f;
private float erodeSpeed = 0.3f;
private float depositSpeed = 0.3f;
private float evaporateSpeed = 0.01f;
private float gravity = 8;
private int maxDropletLifetime = 30;
private float initialWaterVolume = 1;
private float initialSpeed = 1;
private final Random random = new Random();
private final TerrainPos gradient = new TerrainPos();
private int[][] erosionBrushIndices = new int[0][];
private float[][] erosionBrushWeights = new float[0][];
private final Modifier modifier;
public Erosion(Settings settings, Levels levels) {
erodeSpeed = settings.filters.erosion.erosionRate;
depositSpeed = settings.filters.erosion.depositeRate;
modifier = Modifier.range(levels.ground, levels.ground(15));
}
@Override
public void setSeed(long seed) {
random.setSeed(seed);
}
@Override
public void apply(Filterable<?> cellMap) {
int size = cellMap.getRawWidth();
Cell<?>[] cells = cellMap.getBacking();
// Create water droplet at random point on map
float posX = random.nextInt(size - 1);
float posY = random.nextInt(size - 1);
float dirX = 0;
float dirY = 0;
float speed = initialSpeed;
float water = initialWaterVolume;
float sediment = 0;
for (int lifetime = 0; lifetime < maxDropletLifetime; lifetime++) {
int nodeX = (int) posX;
int nodeY = (int) posY;
int dropletIndex = nodeY * size + nodeX;
// Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node
float cellOffsetX = posX - nodeX;
float cellOffsetY = posY - nodeY;
// Calculate droplet's height and direction of flow with bilinear interpolation of surrounding heights
gradient.update(cells, size, posX, posY);
// Update the droplet's direction and position (move position 1 unit regardless of speed)
dirX = (dirX * inertia - gradient.gradientX * (1 - inertia));
dirY = (dirY * inertia - gradient.gradientY * (1 - inertia));
// Normalize direction
float len = (float) Math.sqrt(dirX * dirX + dirY * dirY);
if (Float.isNaN(len)) {
len = 0;
}
if (len != 0) {
dirX /= len;
dirY /= len;
}
posX += dirX;
posY += dirY;
// Stop simulating droplet if it's not moving or has flowed over edge of map
if ((dirX == 0 && dirY == 0) || posX < 0 || posX >= size - 1 || posY < 0 || posY >= size - 1) {
break;
}
// Find the droplet's new height and calculate the deltaHeight
float oldHeight = gradient.height;
float newHeight = gradient.update(cells, size, posX, posY).height;
float deltaHeight = newHeight - oldHeight;
// Calculate the droplet's sediment capacity (higher when moving fast down a slope and contains lots of water)
float sedimentCapacity = Math.max(-deltaHeight * speed * water * sedimentCapacityFactor, minSedimentCapacity);
// If carrying more sediment than capacity, or if flowing uphill:
if (sediment > sedimentCapacity || deltaHeight > 0) {
// If moving uphill (deltaHeight > 0) try fill up to the current height, otherwise deposit a fraction of the excess sediment
float amountToDeposit = (deltaHeight > 0) ? Math.min(deltaHeight, sediment) : (sediment - sedimentCapacity) * depositSpeed;
sediment -= amountToDeposit;
// Add the sediment to the four nodes of the current cell using bilinear interpolation
// Deposition is not distributed over a radius (like erosion) so that it can fill small pits
deposit(cells[dropletIndex], amountToDeposit * (1 - cellOffsetX) * (1 - cellOffsetY));
deposit(cells[dropletIndex + 1], amountToDeposit * cellOffsetX * (1 - cellOffsetY));
deposit(cells[dropletIndex + size], amountToDeposit * (1 - cellOffsetX) * cellOffsetY);
deposit(cells[dropletIndex + size + 1], amountToDeposit * cellOffsetX * cellOffsetY);
} else {
// Erode a fraction of the droplet's current carry capacity.
// Clamp the erosion to the change in height so that it doesn't dig a hole in the terrain behind the droplet
float amountToErode = Math.min((sedimentCapacity - sediment) * erodeSpeed, -deltaHeight);
// Use erosion brush to erode from all nodes inside the droplet's erosion radius
for (int brushPointIndex = 0; brushPointIndex < erosionBrushIndices[dropletIndex].length; brushPointIndex++) {
int nodeIndex = erosionBrushIndices[dropletIndex][brushPointIndex];
Cell<?> cell = cells[nodeIndex];
float brushWeight = erosionBrushWeights[dropletIndex][brushPointIndex];
float weighedErodeAmount = amountToErode * brushWeight;
float deltaSediment = Math.min(cell.value, weighedErodeAmount);//cell.value < weighedErodeAmount) ? cell.value : weighedErodeAmount;
erode(cell, deltaSediment);
sediment += deltaSediment;
}
}
// Update droplet's speed and water content
speed = (float) Math.sqrt(speed * speed + deltaHeight * gravity);
water *= (1 - evaporateSpeed);
if (Float.isNaN(speed)) {
speed = 0;
}
}
}
@Override
public void apply(Filterable<?> map, int iterations) {
if (erosionBrushIndices.length != map.getRawWidth()) {
init(map.getRawWidth(), erosionRadius);
}
while (iterations-- > 0) {
apply(map);
}
}
private void init(int size, int radius) {
erosionBrushIndices = new int[size * size][];
erosionBrushWeights = new float[size * size][];
int[] xOffsets = new int[radius * radius * 4];
int[] yOffsets = new int[radius * radius * 4];
float[] weights = new float[radius * radius * 4];
float weightSum = 0;
int addIndex = 0;
for (int i = 0; i < erosionBrushIndices.length; i++) {
int centreX = i % size;
int centreY = i / size;
if (centreY <= radius || centreY >= size - radius || centreX <= radius + 1 || centreX >= size - radius) {
weightSum = 0;
addIndex = 0;
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
float sqrDst = x * x + y * y;
if (sqrDst < radius * radius) {
int coordX = centreX + x;
int coordY = centreY + y;
if (coordX >= 0 && coordX < size && coordY >= 0 && coordY < size) {
float weight = 1 - (float) Math.sqrt(sqrDst) / radius;
weightSum += weight;
weights[addIndex] = weight;
xOffsets[addIndex] = x;
yOffsets[addIndex] = y;
addIndex++;
}
}
}
}
}
int numEntries = addIndex;
erosionBrushIndices[i] = new int[numEntries];
erosionBrushWeights[i] = new float[numEntries];
for (int j = 0; j < numEntries; j++) {
erosionBrushIndices[i][j] = (yOffsets[j] + centreY) * size + xOffsets[j] + centreX;
erosionBrushWeights[i][j] = weights[j] / weightSum;
}
}
}
private void deposit(Cell cell, float amount) {
float change = modifier.modify(cell, amount);
cell.value += change;
cell.sediment += change;
}
private void erode(Cell cell, float amount) {
float change = modifier.modify(cell, amount);
cell.value -= change;
cell.erosion -= change;
}
private static class TerrainPos {
private float height;
private float gradientX;
private float gradientY;
private TerrainPos update(Cell[] nodes, int mapSize, float posX, float posY) {
int coordX = (int) posX;
int coordY = (int) posY;
// Calculate droplet's offset inside the cell (0,0) = at NW node, (1,1) = at SE node
float x = posX - coordX;
float y = posY - coordY;
// Calculate heights of the four nodes of the droplet's cell
int nodeIndexNW = coordY * mapSize + coordX;
float heightNW = nodes[nodeIndexNW].value;
float heightNE = nodes[nodeIndexNW + 1].value;
float heightSW = nodes[nodeIndexNW + mapSize].value;
float heightSE = nodes[nodeIndexNW + mapSize + 1].value;
// Calculate droplet's direction of flow with bilinear interpolation of height difference along the edges
this.gradientX = (heightNE - heightNW) * (1 - y) + (heightSE - heightSW) * y;
this.gradientY = (heightSW - heightNW) * (1 - x) + (heightSE - heightNE) * x;
// Calculate height with bilinear interpolation of the heights of the nodes of the cell
this.height = heightNW * (1 - x) * (1 - y) + heightNE * x * (1 - y) + heightSW * (1 - x) * y + heightSE * x * y;
return this;
}
}
}

View File

@ -0,0 +1,32 @@
package com.terraforged.core.filter;
import com.terraforged.core.cell.Cell;
public interface Filter {
void apply(Filterable<?> cellMap);
default void setSeed(long seed) {
}
default void apply(Filterable<?> cellMap, int iterations) {
while (iterations-- > 0) {
apply(cellMap);
}
}
default void iterate(Filterable<?> cellMap, Visitor visitor) {
for (int dz = 0; dz < cellMap.getRawHeight(); dz++) {
for (int dx = 0; dx < cellMap.getRawWidth(); dx++) {
Cell<?> cell = cellMap.getCellRaw(dx, dz);
visitor.visit(cellMap, cell, dx, dz);
}
}
}
interface Visitor {
void visit(Filterable<?> cellMap, Cell cell, int dx, int dz);
}
}

View File

@ -0,0 +1,15 @@
package com.terraforged.core.filter;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Tag;
public interface Filterable<T extends Tag> {
int getRawWidth();
int getRawHeight();
Cell<T> getCellRaw(int x, int z);
Cell<T>[] getBacking();
}

View File

@ -0,0 +1,36 @@
package com.terraforged.core.filter;
import com.terraforged.core.cell.Cell;
public interface Modifier {
float getModifier(float value);
default float modify(Cell cell, float value) {
return value * getModifier(cell.value);
}
default Modifier invert() {
return v -> 1 - getModifier(v);
}
static Modifier range(float minValue, float maxValue) {
return new Modifier() {
private final float min = minValue;
private final float max = maxValue;
private final float range = maxValue - minValue;
@Override
public float getModifier(float value) {
if (value > max) {
return 1F;
}
if (value < min) {
return 0F;
}
return (value - min) / range;
}
};
}
}

View File

@ -0,0 +1,60 @@
package com.terraforged.core.filter;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.world.heightmap.Levels;
public class Smoothing implements Filter {
private final int radius;
private final float rad2;
private final float strength;
private final Modifier modifier;
public Smoothing(Settings settings, Levels levels) {
this.radius = NoiseUtil.round(settings.filters.smoothing.smoothingRadius + 0.5F);
this.rad2 = settings.filters.smoothing.smoothingRadius * settings.filters.smoothing.smoothingRadius;
this.strength = settings.filters.smoothing.smoothingRate;
this.modifier = Modifier.range(levels.ground(10), levels.ground(150)).invert();
}
@Override
public void apply(Filterable<?> cellMap) {
int maxZ = cellMap.getRawHeight() - radius;
int maxX = cellMap.getRawWidth() - radius;
for (int z = radius; z < maxZ; z++) {
for (int x = radius; x < maxX; x++) {
Cell cell = cellMap.getCellRaw(x, z);
float total = 0;
float weights = 0;
for (int dz = -radius; dz <= radius; dz++) {
for (int dx = -radius; dx <= radius; dx++) {
float dist2 = dx * dx + dz * dz;
if (dist2 > rad2) {
continue;
}
int px = x + dx;
int pz = z + dz;
Cell<?> neighbour = cellMap.getCellRaw(px, pz);
if (neighbour.isAbsent()) {
continue;
}
float value = neighbour.value;
float weight = 1F - (dist2 / rad2);
total += (value * weight);
weights += weight;
}
}
if (weights > 0) {
float dif = cell.value - (total / weights);
// cell.value -= dif * strength;
cell.value -= modifier.modify(cell, dif * strength);
}
}
}
}
}

View File

@ -0,0 +1,53 @@
package com.terraforged.core.filter;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrains;
public class Steepness implements Filter, Filter.Visitor {
private final int radius;
private final float scaler;
private final Terrains terrains;
public Steepness(Terrains terrains) {
this(1, 16F, terrains);
}
public Steepness(int radius, float scaler, Terrains terrains) {
this.radius = radius;
this.scaler = scaler;
this.terrains = terrains;
}
@Override
public void apply(Filterable<?> cellMap) {
iterate(cellMap, this);
}
@Override
public void visit(Filterable<?> cellMap, Cell cell, int cx, int cz) {
float totalHeightDif = 0F;
for (int dz = -1; dz <= 2; dz++) {
for (int dx = -1; dx <= 2; dx++) {
if (dx == 0 && dz == 0) {
continue;
}
int x = cx + dx * radius;
int z = cz + dz * radius;
Cell<?> neighbour = cellMap.getCellRaw(x, z);
if (neighbour.isAbsent()) {
continue;
}
float height = Math.max(neighbour.value, 62 / 256F);
totalHeightDif += (Math.abs(cell.value - height) / radius);
}
}
cell.steepness = Math.min(1, totalHeightDif * scaler);
if (cell.tag == terrains.coast && cell.steepness < 0.2F) {
cell.tag = terrains.beach;
}
}
}

View File

@ -0,0 +1,102 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
import me.dags.noise.func.Interpolation;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.world.terrain.Terrain;
public class Blender extends Select implements Populator {
private final Populator lower;
private final Populator upper;
private final float blendLower;
private final float blendUpper;
private final float blendRange;
private final float midpoint;
private final float tagThreshold;
private boolean mask = false;
public Blender(Module control, Populator lower, Populator upper, float min, float max, float split) {
super(control);
this.lower = lower;
this.upper = upper;
this.blendLower = min;
this.blendUpper = max;
this.blendRange = blendUpper - blendLower;
this.midpoint = blendLower + (blendRange * split);
this.tagThreshold = midpoint;
}
public Blender(Populator control, Populator lower, Populator upper, float min, float max, float split, float tagThreshold) {
super(control);
this.lower = lower;
this.upper = upper;
this.blendLower = min;
this.blendUpper = max;
this.blendRange = blendUpper - blendLower;
this.midpoint = blendLower + (blendRange * split);
this.tagThreshold = tagThreshold;
}
public Blender mask() {
mask = true;
return this;
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
float select = getSelect(cell, x, y);
if (select < blendLower) {
lower.apply(cell, x, y);
return;
}
if (select > blendUpper) {
upper.apply(cell, x, y);
return;
}
float alpha = Interpolation.LINEAR.apply((select - blendLower) / blendRange);
lower.apply(cell, x, y);
float lowerVal = cell.value;
Terrain lowerType = cell.tag;
upper.apply(cell, x, y);
float upperVal = cell.value;
cell.value = NoiseUtil.lerp(lowerVal, upperVal, alpha);
if (select < midpoint) {
cell.tag = lowerType;
}
if (mask) {
cell.mask *= alpha;
}
}
@Override
public void tag(Cell<Terrain> cell, float x, float y) {
float select = getSelect(cell, x, y);
if (select < blendLower) {
lower.tag(cell, x, y);
return;
}
if (select > blendUpper) {
upper.tag(cell, x, y);
return;
}
if (select < tagThreshold) {
lower.tag(cell, x, y);
} else {
upper.tag(cell, x, y);
}
}
}

View File

@ -0,0 +1,21 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
public class CellLookup implements Module {
private final int scale;
private final Module module;
public CellLookup(Module module, int scale) {
this.module = module;
this.scale = scale;
}
@Override
public float getValue(float x, float y) {
float px = x * scale;
float pz = y * scale;
return module.getValue(px, pz);
}
}

View File

@ -0,0 +1,30 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
import me.dags.noise.util.NoiseUtil;
public class CellLookupOffset implements Module {
private final int scale;
private final Module lookup;
private final Module direction;
private final Module strength;
public CellLookupOffset(Module lookup, Module direction, Module strength, int scale) {
this.scale = scale;
this.lookup = lookup;
this.direction = direction;
this.strength = strength;
}
@Override
public float getValue(float x, float y) {
float px = x * scale;
float pz = y * scale;
float str = strength.getValue(x, y);
float dir = direction.getValue(x, y) * NoiseUtil.PI2;
float dx = NoiseUtil.sin(dir) * str;
float dz = NoiseUtil.cos(dir) * str;
return lookup.getValue(px + dx, pz + dz);
}
}

View File

@ -0,0 +1,54 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.world.terrain.Terrain;
public class Lerp implements Populator {
private final Module control;
private final Populator lower;
private final Populator upper;
public Lerp(Module control, Populator lower, Populator upper) {
this.control = control;
this.lower = lower;
this.upper = upper;
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
float alpha = control.getValue(x, y);
cell.regionMask = alpha;
if (alpha == 0) {
lower.apply(cell, x, y);
return;
}
if (alpha == 1) {
upper.apply(cell, x, y);
return;
}
lower.apply(cell, x, y);
float lowerValue = cell.value;
upper.apply(cell, x, y);
float upperValue = cell.value;
cell.value = NoiseUtil.lerp(lowerValue, upperValue, alpha);
}
@Override
public void tag(Cell<Terrain> cell, float x, float y) {
float alpha = control.getValue(x, y);
if (alpha == 0) {
lower.tag(cell, x, y);
return;
}
upper.tag(cell, x, y);
}
}

View File

@ -0,0 +1,108 @@
package com.terraforged.core.module;
import me.dags.noise.func.Interpolation;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.terrain.Terrain;
public class MultiBlender extends Select implements Populator {
private final Climate climate;
private final Populator lower;
private final Populator middle;
private final Populator upper;
private final float midpoint;
private final float blendLower;
private final float blendUpper;
private final float lowerRange;
private final float upperRange;
private boolean mask = false;
public MultiBlender(Climate climate, Populator control, Populator lower, Populator middle, Populator upper, float min, float mid, float max) {
super(control);
this.climate = climate;
this.lower = lower;
this.upper = upper;
this.middle = middle;
this.midpoint = mid;
this.blendLower = min;
this.blendUpper = max;
this.lowerRange = midpoint - blendLower;
this.upperRange = blendUpper - midpoint;
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
float select = getSelect(cell, x, y);
if (select < blendLower) {
lower.apply(cell, x, y);
return;
}
if (select > blendUpper) {
upper.apply(cell, x, y);
return;
}
if (select < midpoint) {
float alpha = Interpolation.CURVE3.apply((select - blendLower) / lowerRange);
lower.apply(cell, x, y);
float lowerVal = cell.value;
Terrain lowerType = cell.tag;
middle.apply(cell, x, y);
float upperVal = cell.value;
cell.value = NoiseUtil.lerp(lowerVal, upperVal, alpha);
// cell.tag = lowerType;
if (mask) {
cell.mask *= alpha;
}
} else {
float alpha = Interpolation.CURVE3.apply((select - midpoint) / upperRange);
middle.apply(cell, x, y);
float lowerVal = cell.value;
upper.apply(cell, x, y);
cell.value = NoiseUtil.lerp(lowerVal, cell.value, alpha);
if (mask) {
cell.mask *= alpha;
}
}
}
@Override
public void tag(Cell<Terrain> cell, float x, float y) {
float select = getSelect(cell, x, y);
if (select < blendLower) {
lower.tag(cell, x, y);
return;
}
if (select > blendUpper) {
upper.tag(cell, x, y);
return;
}
if (select < midpoint) {
lower.tag(cell, x, y);
// upper.tag(cell, x, y);
if (cell.value > cell.tag.getMax(climate.getRand().getValue(x, y))) {
upper.tag(cell, x, y);
}
} else {
upper.tag(cell, x, y);
}
}
}

View File

@ -0,0 +1,18 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
public class Select {
private final Module control;
public Select(Module control) {
this.control = control;
}
public float getSelect(Cell<Terrain> cell, float x, float y) {
return control.getValue(x, y);
}
}

View File

@ -0,0 +1,66 @@
package com.terraforged.core.module;
import me.dags.noise.Module;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import java.util.LinkedList;
import java.util.List;
public class Selector implements Populator {
private final int maxIndex;
private final Module control;
private final Populator[] nodes;
public Selector(Module control, List<Populator> populators) {
this.control = control;
this.nodes = getWeightedArray(populators);
this.maxIndex = nodes.length - 1;
}
@Override
public void apply(Cell<Terrain> cell, float x, float y) {
get(x, y).apply(cell, x, y);
}
@Override
public void tag(Cell<Terrain> cell, float x, float y) {
get(x, y).tag(cell, x, y);
}
public Populator get(float x, float y) {
float selector = control.getValue(x, y);
int index = NoiseUtil.round(selector * maxIndex);
return nodes[index];
}
private static Populator[] getWeightedArray(List<Populator> modules) {
float smallest = Float.MAX_VALUE;
for (Populator p : modules) {
if (p instanceof TerrainPopulator) {
smallest = Math.min(smallest, ((TerrainPopulator) p).getType().getWeight());
} else {
smallest = Math.min(smallest, 1);
}
}
List<Populator> result = new LinkedList<>();
for (Populator p : modules) {
int count;
if (p instanceof TerrainPopulator) {
count = Math.round(((TerrainPopulator) p).getType().getWeight() / smallest);
} else {
count = Math.round(1 / smallest);
}
while (count-- > 0) {
result.add(p);
}
}
return result.toArray(new Populator[0]);
}
}

View File

@ -0,0 +1,311 @@
package com.terraforged.core.region;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Extent;
import com.terraforged.core.decorator.Decorator;
import com.terraforged.core.filter.Filterable;
import com.terraforged.core.region.chunk.ChunkGenTask;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.region.chunk.ChunkWriter;
import com.terraforged.core.region.chunk.ChunkZoomTask;
import com.terraforged.core.util.concurrent.ThreadPool;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.terrain.Terrain;
import java.util.Collection;
import java.util.function.Consumer;
public class Region implements Extent {
private final int regionX;
private final int regionZ;
private final int chunkX;
private final int chunkZ;
private final int blockX;
private final int blockZ;
private final Size blockSize;
private final Size chunkSize;
private final GenCell[] blocks;
private final GenChunk[] chunks;
public Region(int regionX, int regionZ, int size, int borderChunks) {
this.regionX = regionX;
this.regionZ = regionZ;
this.chunkX = regionX << size;
this.chunkZ = regionZ << size;
this.blockX = Size.chunkToBlock(chunkX);
this.blockZ = Size.chunkToBlock(chunkZ);
this.chunkSize = Size.chunks(size, borderChunks);
this.blockSize = Size.blocks(size, borderChunks);
this.blocks = new GenCell[blockSize.total * blockSize.total];
this.chunks = new GenChunk[chunkSize.total * chunkSize.total];
}
public int getRegionX() {
return regionX;
}
public int getRegionZ() {
return regionZ;
}
public int getBlockX() {
return blockX;
}
public int getBlockZ() {
return blockZ;
}
public int getChunkCount() {
return chunks.length;
}
public int getBlockCount() {
return blocks.length;
}
public Size getChunkSize() {
return chunkSize;
}
public Size getBlockSize() {
return blockSize;
}
public Filterable<Terrain> filterable() {
return new FilterRegion();
}
@Override
public Cell<Terrain> getCell(int blockX, int blockZ) {
int relBlockX = blockSize.border + blockSize.mask(blockX);
int relBlockZ = blockSize.border + blockSize.mask(blockZ);
int index = blockSize.indexOf(relBlockX, relBlockZ);
return blocks[index];
}
public Cell<Terrain> getRawCell(int blockX, int blockZ) {
int index = blockSize.indexOf(blockX, blockZ);
return blocks[index];
}
public ChunkReader getChunk(int chunkX, int chunkZ) {
int relChunkX = chunkSize.border + chunkSize.mask(chunkX);
int relChunkZ = chunkSize.border + chunkSize.mask(chunkZ);
int index = chunkSize.indexOf(relChunkX, relChunkZ);
return chunks[index];
}
public void generate(Consumer<ChunkWriter> consumer) {
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {
int index = chunkSize.indexOf(cx, cz);
GenChunk chunk = computeChunk(index, cx, cz);
consumer.accept(chunk);
}
}
}
public void generate(Heightmap heightmap, ThreadPool.Batcher batcher) {
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {
int index = chunkSize.indexOf(cx, cz);
GenChunk chunk = computeChunk(index, cx, cz);
Runnable task = new ChunkGenTask(chunk, heightmap);
batcher.submit(task);
}
}
}
public void generateZoom(Heightmap heightmap, float offsetX, float offsetZ, float zoom, ThreadPool.Batcher batcher) {
float translateX = offsetX - ((blockSize.total * zoom) / 2F);
float translateZ = offsetZ - ((blockSize.total * zoom) / 2F);
for (int cz = 0; cz < chunkSize.total; cz++) {
for (int cx = 0; cx < chunkSize.total; cx++) {
int index = chunkSize.indexOf(cx, cz);
GenChunk chunk = computeChunk(index, cx, cz);
Runnable task = new ChunkZoomTask(chunk, heightmap, translateX, translateZ, zoom);
batcher.submit(task);
}
}
}
public void decorate(Collection<Decorator> decorators) {
for (int dz = 0; dz < blockSize.total; dz++) {
for (int dx = 0; dx < blockSize.total; dx++) {
int index = blockSize.indexOf(dx, dz);
GenCell cell = blocks[index];
for (Decorator decorator : decorators) {
decorator.apply(cell, getBlockX() + dx, getBlockZ() + dz);
}
}
}
}
public void decorateZoom(Collection<Decorator> decorators, float offsetX, float offsetZ, float zoom) {
float translateX = offsetX - ((blockSize.total * zoom) / 2F);
float translateZ = offsetZ - ((blockSize.total * zoom) / 2F);
for (int dz = 0; dz < blockSize.total; dz++) {
for (int dx = 0; dx < blockSize.total; dx++) {
int index = blockSize.indexOf(dx, dz);
GenCell cell = blocks[index];
for (Decorator decorator : decorators) {
decorator.apply(cell, getBlockX() + translateX + dx, getBlockZ() + translateZ + dz);
}
}
}
}
public void iterate(Consumer<ChunkReader> consumer) {
for (int cz = 0; cz < chunkSize.size; cz++) {
int chunkZ = chunkSize.border + cz;
for (int cx = 0; cx < chunkSize.size; cx++) {
int chunkX = chunkSize.border + cx;
int index = chunkSize.indexOf(chunkX, chunkZ);
GenChunk chunk = chunks[index];
consumer.accept(chunk);
}
}
}
public void iterate(Cell.Visitor<Terrain> visitor) {
for (int dz = 0; dz < blockSize.size; dz++) {
int z = blockSize.border + dz;
for (int dx = 0; dx < blockSize.size; dx++) {
int x = blockSize.border + dx;
int index = blockSize.indexOf(x, z);
GenCell cell = blocks[index];
visitor.visit(cell, dx, dz);
}
}
}
@Override
public void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
int regionMinX = getBlockX();
int regionMinZ = getBlockZ();
if (maxX < regionMinX || maxZ < regionMinZ) {
return;
}
int regionMaxX = getBlockX() + getBlockSize().size - 1;
int regionMaxZ = getBlockZ() + getBlockSize().size - 1;
if (minX > regionMaxX || maxZ > regionMaxZ) {
return;
}
minX = Math.max(minX, regionMinX);
minZ = Math.max(minZ, regionMinZ);
maxX = Math.min(maxX, regionMaxX);
maxZ = Math.min(maxZ, regionMaxZ);
for (int z = minZ; z <= maxX; z++) {
for (int x = minX; x <= maxZ; x++) {
visitor.visit(getCell(x, z), x, z);
}
}
}
private GenChunk computeChunk(int index, int chunkX, int chunkZ) {
GenChunk chunk = chunks[index];
if (chunk == null) {
chunk = new GenChunk(chunkX, chunkZ);
chunks[index] = chunk;
}
return chunk;
}
private GenCell computeCell(int index) {
GenCell cell = blocks[index];
if (cell == null) {
cell = new GenCell();
blocks[index] = cell;
}
return cell;
}
private static class GenCell extends Cell<Terrain> {}
private class GenChunk implements ChunkReader, ChunkWriter {
private final int chunkX;
private final int chunkZ;
private final int blockX;
private final int blockZ;
private final int relBlockX;
private final int relBlockZ;
private GenChunk(int relChunkX, int relChunkZ) {
this.relBlockX = relChunkX << 4;
this.relBlockZ = relChunkZ << 4;
this.chunkX = Region.this.chunkX + relChunkX;
this.chunkZ = Region.this.chunkZ + relChunkZ;
this.blockX = chunkX << 4;
this.blockZ = chunkZ << 4;
}
@Override
public int getChunkX() {
return chunkX;
}
@Override
public int getChunkZ() {
return chunkZ;
}
@Override
public int getBlockX() {
return blockX;
}
@Override
public int getBlockZ() {
return blockZ;
}
@Override
public Cell<Terrain> getCell(int blockX, int blockZ) {
int relX = relBlockX + (blockX & 15);
int relZ = relBlockZ + (blockZ & 15);
int index = blockSize.indexOf(relX, relZ);
return blocks[index];
}
@Override
public Cell<Terrain> genCell(int blockX, int blockZ) {
int relX = relBlockX + (blockX & 15);
int relZ = relBlockZ + (blockZ & 15);
int index = blockSize.indexOf(relX, relZ);
return computeCell(index);
}
}
private class FilterRegion implements Filterable<Terrain> {
@Override
public int getRawWidth() {
return blockSize.total;
}
@Override
public int getRawHeight() {
return blockSize.total;
}
@Override
public Cell<Terrain>[] getBacking() {
return blocks;
}
@Override
public Cell<Terrain> getCellRaw(int x, int z) {
int index = blockSize.indexOf(x, z);
if (index < 0 || index >= blocks.length) {
return Cell.empty();
}
return blocks[index];
}
}
}

View File

@ -0,0 +1,88 @@
package com.terraforged.core.region;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.util.Cache;
import com.terraforged.core.util.FutureValue;
import com.terraforged.core.world.heightmap.RegionExtent;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class RegionCache implements RegionExtent {
private final boolean queuing;
private final RegionGenerator renderer;
private final Cache<Long, Future<Region>> cache;
private Region cachedRegion = null;
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
this.renderer = renderer;
this.queuing = queueNeighbours;
this.cache = new com.terraforged.core.util.Cache<>(30, 30, TimeUnit.SECONDS);
}
@Override
public int chunkToRegion(int coord) {
return renderer.chunkToRegion(coord);
}
@Override
public Future<Region> getRegionAsync(int regionX, int regionZ) {
long id = NoiseUtil.seed(regionX, regionZ);
Future<Region> future = cache.get(id);
if (future == null) {
future = renderer.getRegionAsync(regionX, regionZ);
cache.put(id, future);
}
return future;
}
@Override
public ChunkReader getChunk(int chunkX, int chunkZ) {
int regionX = renderer.chunkToRegion(chunkX);
int regionZ = renderer.chunkToRegion(chunkZ);
Region region = getRegion(regionX, regionZ);
return region.getChunk(chunkX, chunkZ);
}
@Override
public Region getRegion(int regionX, int regionZ) {
if (cachedRegion != null && regionX == cachedRegion.getRegionX() && regionZ == cachedRegion.getRegionZ()) {
return cachedRegion;
}
long id = NoiseUtil.seed(regionX, regionZ);
Future<Region> futureRegion = cache.get(id);
if (futureRegion == null) {
cachedRegion = renderer.generateRegion(regionX, regionZ);
cache.put(id, new FutureValue<>(cachedRegion));
} else {
try {
cachedRegion = futureRegion.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
if (queuing) {
queueNeighbours(regionX, regionZ);
}
return cachedRegion;
}
private void queueNeighbours(int regionX, int regionZ) {
for (int z = -1; z <= 1; z++) {
for (int x = -1; x <= 1; x++){
if (x == 0 && z == 0) {
continue;
}
getRegionAsync(regionX + x, regionZ + z);
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.terraforged.core.region;
import com.terraforged.core.world.WorldGeneratorFactory;
public interface RegionCacheFactory {
RegionCache create(WorldGeneratorFactory factory);
}

View File

@ -0,0 +1,145 @@
package com.terraforged.core.region;
import com.terraforged.core.filter.Filterable;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.util.concurrent.ThreadPool;
import com.terraforged.core.world.WorldGenerator;
import com.terraforged.core.world.WorldGeneratorFactory;
import com.terraforged.core.world.heightmap.RegionExtent;
import com.terraforged.core.world.terrain.Terrain;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
public class RegionGenerator implements RegionExtent {
private final int factor;
private final int border;
private final ThreadPool threadPool;
private final ObjectPool<WorldGenerator> genPool;
private RegionGenerator(Builder builder) {
this.factor = builder.factor;
this.border = builder.border;
this.threadPool = builder.threadPool;
this.genPool = new ObjectPool<>(50, builder.factory);
}
public RegionCache toCache() {
return toCache(true);
}
public RegionCache toCache(boolean queueNeighbours) {
return new RegionCache(queueNeighbours, this);
}
@Override
public int chunkToRegion(int i) {
return i >> factor;
}
@Override
public Region getRegion(int regionX, int regionZ) {
return generateRegion(regionX, regionZ);
}
@Override
public Future<Region> getRegionAsync(int regionX, int regionZ) {
return generate(regionX, regionZ);
}
public Future<Region> generate(int regionX, int regionZ) {
return ForkJoinPool.commonPool().submit(() -> generateRegion(regionX, regionZ));
}
public Future<Region> generate(float centerX, float centerZ, float zoom, boolean filter) {
return ForkJoinPool.commonPool().submit(() -> generateRegion(centerX, centerZ, zoom, filter));
}
public Region generateRegion(int regionX, int regionZ) {
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
WorldGenerator generator = item.getValue();
Region region = new Region(regionX, regionZ, factor, border);
try (ThreadPool.Batcher batcher = threadPool.batcher(region.getChunkCount())) {
region.generate(generator.getHeightmap(), batcher);
}
postProcess(region, generator);
return region;
}
}
private void postProcess(Region region, WorldGenerator generator) {
Filterable<Terrain> filterable = region.filterable();
generator.getFilters().setRegion(region.getRegionX(), region.getRegionZ());
generator.getFilters().getErosion().apply(filterable, generator.getFilters().getSettings().erosion.iterations);
generator.getFilters().getSmoothing().apply(filterable, generator.getFilters().getSettings().smoothing.iterations);
generator.getFilters().getSteepness().apply(filterable);
region.decorate(generator.getDecorators().getDecorators());
}
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
try (ObjectPool.Item<WorldGenerator> item = genPool.get()) {
WorldGenerator generator = item.getValue();
Region region = new Region(0, 0, factor, border);
try (ThreadPool.Batcher batcher = threadPool.batcher(region.getChunkCount())) {
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom, batcher);
}
postProcess(region, generator, centerX, centerZ, zoom, filter);
return region;
}
}
private void postProcess(Region region, WorldGenerator generator, float centerX, float centerZ, float zoom,
boolean filter) {
Filterable<Terrain> filterable = region.filterable();
if (filter) {
generator.getFilters().setRegion(region.getRegionX(), region.getRegionZ());
generator.getFilters().getErosion().apply(filterable, generator.getFilters().getSettings().erosion.iterations);
generator.getFilters().getSmoothing().apply(filterable, generator.getFilters().getSettings().smoothing.iterations);
}
generator.getFilters().getSteepness().apply(filterable);
// region.decorateZoom(generator.getDecorators().getDecorators(), centerX, centerZ, zoom);
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private int factor = 0;
private int border = 0;
private ThreadPool threadPool;
private WorldGeneratorFactory factory;
public Builder size(int factor, int border) {
return factor(factor).border(border);
}
public Builder factor(int factor) {
this.factor = factor;
return this;
}
public Builder border(int border) {
this.border = border;
return this;
}
public Builder pool(ThreadPool threadPool) {
this.threadPool = threadPool;
return this;
}
public Builder factory(WorldGeneratorFactory factory) {
this.factory = factory;
return this;
}
public RegionGenerator build() {
return new RegionGenerator(this);
}
}
}

View File

@ -0,0 +1,50 @@
package com.terraforged.core.region;
public class Size {
public final int size;
public final int total;
public final int border;
private final int mask;
public Size(int size, int border) {
this.size = size;
this.mask = size - 1;
this.border = border;
this.total = size + (2 * border);
}
public int mask(int i) {
return i & mask;
}
public int indexOf(int x, int z) {
return z * total + x;
}
public static int chunkToBlock(int i) {
return i << 4;
}
public static int blockToChunk(int i) {
return i >> 4;
}
public static int count(int minX, int minZ, int maxX, int maxZ) {
int dx = maxX - minX;
int dz = maxZ - minZ;
return dx * dz;
}
public static Size chunks(int factor, int borderChunks) {
int chunks = 1 << factor;
return new Size(chunks, borderChunks);
}
public static Size blocks(int factor, int borderChunks) {
int chunks = 1 << factor;
int blocks = chunks << 4;
int borderBlocks = borderChunks << 4;
return new Size(blocks, borderBlocks);
}
}

View File

@ -0,0 +1,23 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.world.heightmap.Heightmap;
public class ChunkGenTask implements Runnable {
protected final ChunkWriter chunk;
protected final Heightmap heightmap;
public ChunkGenTask(ChunkWriter chunk, Heightmap heightmap) {
this.chunk = chunk;
this.heightmap = heightmap;
}
@Override
public void run() {
chunk.generate((cell, dx, dz) -> {
float x = chunk.getBlockX() + dx;
float z = chunk.getBlockZ() + dz;
heightmap.apply(cell, x, z);
});
}
}

View File

@ -0,0 +1,14 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.cell.Extent;
public interface ChunkHolder extends Extent {
int getChunkX();
int getChunkZ();
int getBlockX();
int getBlockZ();
}

View File

@ -0,0 +1,44 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
public interface ChunkReader extends ChunkHolder {
@Override
Cell<Terrain> getCell(int dx, int dz);
@Override
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
int regionMinX = getBlockX();
int regionMinZ = getBlockZ();
if (maxX < regionMinX || maxZ < regionMinZ) {
return;
}
int regionMaxX = getBlockX() + 15;
int regionMaxZ = getBlockZ() + 15;
if (minX > regionMaxX || maxZ > regionMaxZ) {
return;
}
minX = Math.max(minX, regionMinX);
minZ = Math.max(minZ, regionMinZ);
maxX = Math.min(maxX, regionMaxX);
maxZ = Math.min(maxZ, regionMaxZ);
for (int z = minZ; z <= maxX; z++) {
for (int x = minX; x <= maxZ; x++) {
visitor.visit(getCell(x, z), x, z);
}
}
}
default void iterate(Cell.Visitor<Terrain> visitor) {
for (int dz = 0; dz < 16; dz++) {
for (int dx = 0; dx < 16; dx++) {
visitor.visit(getCell(dx, dz), dx, dz);
}
}
}
}

View File

@ -0,0 +1,17 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
public interface ChunkWriter extends ChunkHolder {
Cell<Terrain> genCell(int dx, int dz);
default void generate(Cell.Visitor<Terrain> visitor) {
for (int dz = 0; dz < 16; dz++) {
for (int dx = 0; dx < 16; dx++) {
visitor.visit(genCell(dx, dz), dx, dz);
}
}
}
}

View File

@ -0,0 +1,26 @@
package com.terraforged.core.region.chunk;
import com.terraforged.core.world.heightmap.Heightmap;
public class ChunkZoomTask extends ChunkGenTask {
private final float translateX;
private final float translateZ;
private final float zoom;
public ChunkZoomTask(ChunkWriter chunk, Heightmap heightmap, float translateX, float translateZ, float zoom) {
super(chunk, heightmap);
this.translateX = translateX;
this.translateZ = translateZ;
this.zoom = zoom;
}
@Override
public void run() {
chunk.generate((cell, dx, dz) -> {
float x = ((chunk.getBlockX() + dx) * zoom) + translateX;
float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ;
heightmap.apply(cell, x, z);
});
}
}

View File

@ -0,0 +1,79 @@
package com.terraforged.core.settings;
import com.terraforged.core.util.serialization.annotation.Comment;
import com.terraforged.core.util.serialization.annotation.Range;
import com.terraforged.core.util.serialization.annotation.Serializable;
import com.terraforged.core.world.biome.BiomeType;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class BiomeSettings {
public BiomeGroup desert = new BiomeGroup(BiomeType.DESERT);
public BiomeGroup steppe = new BiomeGroup(BiomeType.STEPPE);
public BiomeGroup coldSteppe = new BiomeGroup(BiomeType.COLD_STEPPE);
public BiomeGroup grassland = new BiomeGroup(BiomeType.GRASSLAND);
public BiomeGroup savanna = new BiomeGroup(BiomeType.SAVANNA);
public BiomeGroup taiga = new BiomeGroup(BiomeType.TAIGA);
public BiomeGroup temperateForest = new BiomeGroup(BiomeType.TEMPERATE_FOREST);
public BiomeGroup temperateRainForest = new BiomeGroup(BiomeType.TEMPERATE_RAINFOREST);
public BiomeGroup tropicalRainForest = new BiomeGroup(BiomeType.TROPICAL_RAINFOREST);
public BiomeGroup tundra = new BiomeGroup(BiomeType.TUNDRA);
public List<BiomeGroup> asList() {
return Arrays.asList(
desert,
steppe,
coldSteppe,
grassland,
savanna,
taiga,
temperateForest,
temperateRainForest,
tropicalRainForest,
tundra
);
}
public Map<BiomeType, BiomeGroup> asMap() {
return asList().stream().collect(Collectors.toMap(g -> g.type, g -> g));
}
@Serializable
public static class BiomeGroup {
public BiomeType type;
public BiomeWeight[] biomes = new BiomeWeight[0];
public BiomeGroup() {
}
public BiomeGroup(BiomeType biomeType) {
this.type = biomeType;
}
}
@Serializable
public static class BiomeWeight {
public String id;
@Range(min = 0, max = 50)
@Comment("Controls how common this biome type is")
public int weight;
public BiomeWeight() {
}
public BiomeWeight(String id, int weight) {
this.id = id;
this.weight = weight;
}
}
}

View File

@ -0,0 +1,45 @@
package com.terraforged.core.settings;
import com.terraforged.core.util.serialization.annotation.Comment;
import com.terraforged.core.util.serialization.annotation.Range;
import com.terraforged.core.util.serialization.annotation.Serializable;
@Serializable
public class FilterSettings {
public Erosion erosion = new Erosion();
public Smoothing smoothing = new Smoothing();
@Serializable
public static class Erosion {
@Range(min = 0, max = 30000)
@Comment("Controls the number of erosion iterations")
public int iterations = 15000;
@Range(min = 0F, max = 1F)
@Comment("Controls how quickly material dissolves (during erosion)")
public float erosionRate = 0.35F;
@Range(min = 0F, max = 1F)
@Comment("Controls how quickly material is deposited (during erosion)")
public float depositeRate = 0.5F;
}
@Serializable
public static class Smoothing {
@Range(min = 0, max = 5)
@Comment("Controls the number of smoothing iterations")
public int iterations = 1;
@Range(min = 0, max = 5)
@Comment("Controls the smoothing radius")
public float smoothingRadius = 1.75F;
@Range(min = 0, max = 1)
@Comment("Controls how strongly smoothing is applied")
public float smoothingRate = 0.85F;
}
}

View File

@ -0,0 +1,200 @@
package com.terraforged.core.settings;
import me.dags.noise.Module;
import me.dags.noise.Source;
import com.terraforged.core.util.serialization.annotation.Comment;
import com.terraforged.core.util.serialization.annotation.Range;
import com.terraforged.core.util.serialization.annotation.Serializable;
@Serializable
public class GeneratorSettings {
public transient long seed = 0L;
/**
* WORLD PROPERTIES
*/
public World world = new World();
/**
* TERRAIN PROPERTIES
*/
public Land land = new Land();
/**
* BIOME PROPERTIES
*/
public Biome biome = new Biome();
public BiomeNoise biomeEdgeNoise = new BiomeNoise();
/**
* RIVER PROPERTIES
*/
public River primaryRivers = new River(5, 2, 8, 25, 8, 0.75F);
public River secondaryRiver = new River(4, 1, 6, 15, 5, 0.75F);
public River tertiaryRivers = new River(3, 0, 4, 10, 4, 0.75F);
public Lake lake = new Lake();
@Serializable
public static class World {
@Range(min = 0, max = 256)
@Comment("Controls the world height")
public int worldHeight = 256;
@Range(min = 0, max = 255)
@Comment("Controls the sea level")
public int seaLevel = 63;
@Range(min = 0F, max = 1F)
@Comment("Controls the amount of ocean between continents")
public float oceanSize = 0.25F;
}
@Serializable
public static class Land {
@Range(min = 500, max = 10000)
@Comment("Controls the size of continents")
public int continentScale = 4000;
@Range(min = 250, max = 5000)
@Comment("Controls the size of mountain ranges")
public int mountainScale = 950;
@Range(min = 125, max = 2500)
@Comment("Controls the size of terrain regions")
public int regionSize = 1000;
}
@Serializable
public static class Biome {
@Range(min = 50, max = 500)
@Comment("Controls the size of individual biomes")
public int biomeSize = 200;
@Range(min = 1, max = 200)
@Comment("Controls the scale of shape distortion for biomes")
public int biomeWarpScale = 30;
@Range(min = 1, max = 200)
@Comment("Controls the strength of shape distortion for biomes")
public int biomeWarpStrength = 30;
}
@Serializable
public static class BiomeNoise {
@Comment("The noise type")
public Source type = Source.PERLIN;
@Range(min = 1, max = 100)
@Comment("Controls the scale of the noise")
public int scale = 4;
@Range(min = 1, max = 5)
@Comment("Controls the number of noise octaves")
public int octaves = 1;
@Range(min = 0F, max = 5.5F)
@Comment("Controls the gain subsequent noise octaves")
public float gain = 0.5F;
@Range(min = 0F, max = 10.5F)
@Comment("Controls the lacunarity of subsequent noise octaves")
public float lacunarity = 2F;
@Range(min = 1, max = 100)
@Comment("Controls the strength of the noise")
public int strength = 12;
public Module build(int seed) {
return Source.build(seed, scale, octaves).gain(gain).lacunarity(lacunarity).build(type).bias(-0.5);
}
}
@Serializable
public static class River {
@Range(min = 1, max = 10)
@Comment("Controls the depth of the river")
public int bedDepth;
@Range(min = 1, max = 10)
@Comment("Controls the height of river banks")
public int minBankHeight;
@Range(min = 1, max = 10)
@Comment("Controls the height of river banks")
public int maxBankHeight;
@Range(min = 1, max = 20)
@Comment("Controls the river-bed width")
public int bedWidth;
@Range(min = 1, max = 50)
@Comment("Controls the river-banks width")
public int bankWidth;
@Range(min = 0.0F, max = 1.0F)
@Comment("Controls how much rivers taper")
public float fade;
public River() {
}
public River(int depth, int minBank, int maxBank, int outer, int inner, float fade) {
this.minBankHeight = minBank;
this.maxBankHeight = maxBank;
this.bankWidth = outer;
this.bedWidth = inner;
this.bedDepth = depth;
this.fade = fade;
}
}
public static class Lake {
@Range(min = 0.0F, max = 1.0F)
@Comment("Controls the chance of a lake spawning")
public float chance = 0.2F;
@Range(min = 0F, max = 1F)
@Comment("The minimum distance along a river that a lake will spawn")
public float minStartDistance = 0.03F;
@Range(min = 0F, max = 1F)
@Comment("The maximum distance along a river that a lake will spawn")
public float maxStartDistance = 0.07F;
@Range(min = 1, max = 20)
@Comment("The max depth of the lake")
public int depth = 10;
@Range(min = 10, max = 50)
@Comment("The minimum size of the lake")
public int sizeMin = 50;
@Range(min = 50, max = 150)
@Comment("The maximum size of the lake")
public int sizeMax = 100;
@Range(min = 1, max = 10)
@Comment("The minimum bank height")
public int minBankHeight = 2;
@Range(min = 1, max = 10)
@Comment("The maximum bank height")
public int maxBankHeight = 10;
public Lake() {
}
}
}

View File

@ -0,0 +1,15 @@
package com.terraforged.core.settings;
import com.terraforged.core.util.serialization.annotation.Serializable;
@Serializable
public class Settings {
public GeneratorSettings generator = new GeneratorSettings();
public FilterSettings filters = new FilterSettings();
public TerrainSettings terrain = new TerrainSettings();
public BiomeSettings biomes = new BiomeSettings();
}

View File

@ -0,0 +1,50 @@
package com.terraforged.core.settings;
import com.terraforged.core.util.serialization.annotation.Comment;
import com.terraforged.core.util.serialization.annotation.Range;
import com.terraforged.core.util.serialization.annotation.Serializable;
@Serializable
public class TerrainSettings {
public transient float deepOcean = 1F;
public transient float ocean = 1F;
public transient float coast = 1F;
public transient float river = 1F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float steppe = 5F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float plains = 5F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float hills = 2F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float dales = 2F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float plateau = 2F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float badlands = 2F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float torridonian = 0.5F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float mountains = 0.5F;
@Range(min = 0, max = 10)
@Comment("Controls how common this terrain type is")
public float volcano = 1F;
}

View File

@ -0,0 +1,72 @@
package com.terraforged.core.util;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class Cache<K, V> {
private final long lifespan;
private final long interval;
private final Map<K, CachedValue<V>> cache;
private long lastUpdate = 0L;
public Cache(long lifespan, long interval, TimeUnit unit) {
this(lifespan, interval, unit, () -> new HashMap<>(100));
}
public Cache(long lifespan, long interval, TimeUnit unit, Supplier<Map<K, CachedValue<V>>> backing) {
this.lifespan = unit.toMillis(lifespan);
this.interval = unit.toMillis(interval);
this.cache = backing.get();
}
public V get(K id) {
update();
CachedValue<V> value = cache.get(id);
if (value != null) {
return value.getValue();
}
return null;
}
public void put(K id, V region) {
update();
cache.put(id, new CachedValue<>(region));
}
public void drop(K id) {
update();
}
public boolean contains(K id) {
return cache.containsKey(id);
}
private void update() {
long time = System.currentTimeMillis();
if (time - lastUpdate < interval) {
return;
}
cache.values().removeIf(value -> time - value.time >= lifespan);
lastUpdate = time;
}
public static class CachedValue<T> {
private final T value;
private long time;
private CachedValue(T value) {
this.value = value;
this.time = System.currentTimeMillis();
}
private T getValue() {
time = System.currentTimeMillis();
return value;
}
}
}

View File

@ -0,0 +1,38 @@
package com.terraforged.core.util;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class FutureValue<T> implements Future<T> {
private final T value;
public FutureValue(T value) {
this.value = value;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return true;
}
@Override
public T get() {
return value;
}
@Override
public T get(long timeout, TimeUnit unit) {
return value;
}
}

View File

@ -0,0 +1,131 @@
package com.terraforged.core.util;
public class PosIterator {
private final int minX;
private final int minZ;
private final int maxX;
private final int maxY;
private final int maxZ;
private final int size;
private int x;
private int y;
private int z;
private int index = -1;
public PosIterator(int x, int y, int z, int width, int height, int length) {
this.x = x - 1;
this.y = y;
this.z = z;
this.minX = x;
this.minZ = z;
this.maxX = x + width;
this.maxY = y + height;
this.maxZ = z + length;
this.size = (width * height * length) - 1;
}
/**
* Steps the iterator forward one position if there there is a new position within it's x/y/z bounds.
*
* @return true if the an increment was made, false if the maximum x,y,z coord has been reached
*/
public boolean next() {
if (x + 1 < maxX) {
x += 1;
index++;
return true;
}
if (z + 1 < maxZ) {
x = minX;
z += 1;
index++;
return true;
}
if (y + 1 < maxY) {
x = minX - 1;
z = minZ;
y += 1;
return true;
}
return false;
}
public int size() {
return size;
}
public int index() {
return index;
}
public int x() {
return x;
}
public int y() {
return y;
}
public int z() {
return z;
}
/**
* Iterates over a 2D area in the x-z planes, centered on x:z, with the given radius
*
* Iteration Order:
* 1. Increments the x-axis from x - radius to x + radius (inclusive), then:
* 2. Increments the z-axis once, resets the x-axis to x - radius, then:
* 3. Repeats steps 1 & 2 until z reaches z + radius
*/
public static PosIterator radius2D(int x, int z, int radius) {
int startX = x - radius;
int startZ = z - radius;
int size = radius * 2 + 1;
return new PosIterator(startX, 0, startZ, size, 0, size);
}
/**
* Iterates over a 3D volume, centered on x:y:z, with the given radius
*
* Iteration Order:
* 1. Increments the x-axis (starting from x - radius) up to x + radius (inclusive), then:
* 2. Increments the z-axis once (starting from z - radius) and resets the x-axis to x - radius, then:
* 3. Increments the y-axis once (starting from y - radius), resets the x & z axes to x - radius & z - radius
* respectively, then:
* 4. Repeats steps 1-3 until y reaches y + radius
*/
public static PosIterator radius3D(int x, int y, int z, int radius) {
int startX = x - radius;
int startY = y - radius;
int startZ = z - radius;
int size = radius * 2 + 1;
return new PosIterator(startX, startY, startZ, size, size, size);
}
public static PosIterator area(int x, int z, int width, int length) {
return new PosIterator(x, 0, z, width, 0, length);
}
public static PosIterator volume3D(int x, int y, int z, int width, int height, int length) {
return new PosIterator(x, y, z, width, height, length);
}
public static PosIterator range2D(int minX, int minZ, int maxX, int maxZ) {
int width = maxX - minX;
int length = maxZ - minZ;
return new PosIterator(minX, 0, minZ, width, 0, length);
}
public static PosIterator range2D(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
int width = 1 + maxX - minX;
int height = 1 + maxY - minY;
int length = 1 + maxZ - minZ;
return new PosIterator(minX, minY, minZ, width, height, length);
}
}

View File

@ -0,0 +1,42 @@
package com.terraforged.core.util;
public class Seed {
private final Seed root;
private final int value;
private int seed;
public Seed(long seed) {
this((int) seed);
}
public Seed(int seed) {
this.value = seed;
this.seed = seed;
this.root = this;
}
private Seed(Seed root) {
this.root = root;
this.seed = root.next();
this.value = seed;
}
public int next() {
return ++root.seed;
}
public int get() {
return value;
}
public Seed nextSeed() {
return new Seed(root);
}
public Seed reset() {
this.seed = value;
return this;
}
}

View File

@ -0,0 +1,31 @@
package com.terraforged.core.util;
import me.dags.noise.Module;
import me.dags.noise.Source;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrain;
import java.util.function.BiPredicate;
public class VariablePredicate {
private final Module module;
private final BiPredicate<Cell<Terrain>, Float> predicate;
public VariablePredicate(Module module, BiPredicate<Cell<Terrain>, Float> predicate) {
this.module = module;
this.predicate = predicate;
}
public boolean test(Cell<Terrain> cell, float x, float z) {
return predicate.test(cell, module.getValue(x, z));
}
public static VariablePredicate height(Seed seed, Levels levels, int min, int max, int size, int octaves) {
float bias = levels.scale(min);
float scale = levels.scale(max - min);
Module source = Source.perlin(seed.next(), size, 1).scale(scale).bias(bias);
return new VariablePredicate(source, (cell, height) -> cell.value < height);
}
}

View File

@ -0,0 +1,78 @@
package com.terraforged.core.util.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class ObjectPool<T> {
private final int capacity;
private final List<Item<T>> pool;
private final Supplier<? extends T> supplier;
public ObjectPool(int size, Supplier<? extends T> supplier) {
this.capacity = size;
this.pool = new ArrayList<>(size);
this.supplier = supplier;
}
public Item<T> get() {
synchronized (pool) {
if (pool.size() > 0) {
return pool.remove(pool.size() - 1).retain();
}
}
return new Item<>(supplier.get(), this);
}
public int size() {
synchronized (pool) {
return pool.size();
}
}
private boolean restore(Item<T> item) {
synchronized (pool) {
int size = pool.size();
if (size < capacity) {
pool.add(item);
return true;
}
}
return false;
}
public static class Item<T> implements AutoCloseable {
private final T value;
private final ObjectPool<T> pool;
private boolean released = false;
private Item(T value, ObjectPool<T> pool) {
this.value = value;
this.pool = pool;
}
public T getValue() {
return value;
}
public void release() {
if (!released) {
released = true;
released = pool.restore(this);
}
}
private Item<T> retain() {
released = false;
return this;
}
@Override
public void close() {
release();
}
}
}

View File

@ -0,0 +1,110 @@
package com.terraforged.core.util.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPool {
public static final int DEFAULT_POOL_SIZE = Math.max(2, Runtime.getRuntime().availableProcessors() / 2);
private static final Object lock = new Object();
private static ThreadPool instance = new ThreadPool();
private final int size;
private final ExecutorService service;
private ThreadPool() {
this.service = ForkJoinPool.commonPool();
this.size = -1;
}
public ThreadPool(int size) {
this.service = Executors.newFixedThreadPool(size);
this.size = size;
}
public void shutdown() {
if (size > 0) {
service.shutdown();
}
}
public <T> Future<?> submit(Runnable runnable) {
return service.submit(runnable);
}
public <T> Future<T> submit(Callable<T> callable) {
return service.submit(callable);
}
public Batcher batcher(int size) {
return new Batcher(size);
}
public class Batcher implements AutoCloseable {
private final List<Future<?>> tasks;
public Batcher(int size) {
tasks = new ArrayList<>(size);
}
public void submit(Runnable task) {
tasks.add(ThreadPool.this.submit(task));
}
public void submit(Callable<?> task) {
tasks.add(ThreadPool.this.submit(task));
}
public void await() {
boolean hasMore = true;
while (hasMore) {
hasMore = false;
for (Future<?> future : tasks) {
if (!future.isDone()) {
hasMore = true;
break;
}
}
}
tasks.clear();
}
@Override
public void close() {
await();
}
}
public static ThreadPool getFixed(int size) {
synchronized (lock) {
if (instance.size != size) {
instance.shutdown();
instance = new ThreadPool(size);
}
return instance;
}
}
public static ThreadPool getFixed() {
synchronized (lock) {
if (instance.size == -1) {
instance = new ThreadPool(ThreadPool.DEFAULT_POOL_SIZE);
}
return instance;
}
}
public static ThreadPool getCommon() {
synchronized (lock) {
if (instance.size != -1) {
instance.shutdown();
instance = new ThreadPool();
}
return instance;
}
}
}

View File

@ -0,0 +1,134 @@
package com.terraforged.core.util.grid;
import me.dags.noise.util.NoiseUtil;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class FixedGrid<T> implements Iterable<MappedList<FixedList<T>>> {
private final MappedList<MappedList<FixedList<T>>> grid;
private FixedGrid(MappedList<MappedList<FixedList<T>>> grid) {
this.grid = grid;
}
public int size() {
return grid.size();
}
public FixedList<T> get(float x, float y) {
return grid.get(y).get(x);
}
public T get(float x, float y, float z) {
MappedList<FixedList<T>> row = grid.get(y);
FixedList<T> cell = row.get(x);
return cell.get(z);
}
@Override
public Iterator<MappedList<FixedList<T>>> iterator() {
return grid.iterator();
}
public static <T> FixedGrid<T> create(List<List<List<T>>> grid, float minX, float minY, float rangeX, float rangeY) {
List<MappedList<FixedList<T>>> list = new ArrayList<>();
for (List<List<T>> src : grid) {
List<FixedList<T>> row = new ArrayList<>(src.size());
for (List<T> cell : src) {
row.add(FixedList.of(cell));
}
list.add(MappedList.of(row, minX, rangeX));
}
return new FixedGrid<>(MappedList.of(list, minY, rangeY));
}
public static <T> FixedGrid<T> generate(int size, List<T> values, Function<T, Float> xFunc, Function<T, Float> yFunc) {
List<List<List<T>>> src = createList(size, () -> createList(size, ArrayList::new));
List<List<List<T>>> dest = createList(size, () -> createList(size, ArrayList::new));
float minX = 1F;
float maxX = 0F;
float minY = 1F;
float maxY = 0F;
for (T value : values) {
float x = xFunc.apply(value);
float y = yFunc.apply(value);
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
int maxIndex = size - 1;
float rangeX = maxX - minX;
float rangeY = maxY - minY;
for (T value : values) {
float colVal = (xFunc.apply(value) - minX) / rangeX;
float rowVal = (yFunc.apply(value) - minY) / rangeY;
int colIndex = NoiseUtil.round(maxIndex * colVal);
int rowIndex = NoiseUtil.round(maxIndex * rowVal);
List<List<T>> row = src.get(rowIndex);
List<T> group = row.get(colIndex);
group.add(value);
}
for (int y = 0; y < size; y++) {
List<List<T>> srcRow = src.get(y);
List<List<T>> destRow = dest.get(y);
for (int x = 0; x < size; x++) {
List<T> srcGroup = srcRow.get(x);
List<T> destGroup = destRow.get(x);
if (srcGroup.isEmpty()) {
float fx = minX + (x / (float) maxIndex) * rangeX;
float fy = minY + (x / (float) maxIndex) * rangeY;
addClosest(values, destGroup, fx, fy, xFunc, yFunc);
} else {
destGroup.addAll(srcGroup);
}
}
}
return create(dest, minX, minY, rangeX, rangeY);
}
private static <T> void addClosest(List<T> source, List<T> dest, float fx, float fy, Function<T, Float> xFunc, Function<T, Float> yFunc) {
float dist2 = Float.MAX_VALUE;
Map<T, Float> distances = new HashMap<>();
for (T t : source) {
if (!distances.containsKey(t)) {
float dx = fx - xFunc.apply(t);
float dy = fy - yFunc.apply(t);
float d2 = dx * dx + dy * dy;
distances.put(t, d2);
if (d2 < dist2) {
dist2 = d2;
}
}
}
if (dist2 <= 0) {
dist2 = 1F;
}
List<T> sorted = new ArrayList<>(distances.keySet());
sorted.sort((o1, o2) -> Float.compare(distances.getOrDefault(o1, Float.MAX_VALUE), distances.getOrDefault(o2, Float.MAX_VALUE)));
for (T t : sorted) {
float d2 = distances.get(t);
if (d2 / dist2 < 1.025F) {
dest.add(t);
}
}
}
private static <T> List<T> createList(int size, Supplier<T> supplier) {
List<T> list = new ArrayList<>();
while (list.size() < size) {
list.add(supplier.get());
}
return list;
}
}

View File

@ -0,0 +1,67 @@
package com.terraforged.core.util.grid;
import me.dags.noise.util.NoiseUtil;
import java.util.*;
public class FixedList<T> implements Iterable<T> {
private final int maxIndex;
private final T[] elements;
FixedList(T[] elements) {
this.maxIndex = elements.length - 1;
this.elements = elements;
}
public T get(int index) {
if (index < 0) {
return elements[0];
}
if (index > maxIndex) {
return elements[maxIndex];
}
return elements[index];
}
public T get(float value) {
return get(indexOf(value));
}
public int size() {
return elements.length;
}
public int indexOf(float value) {
return NoiseUtil.round(value * maxIndex);
}
public Set<T> uniqueValues() {
Set<T> set = new HashSet<>();
Collections.addAll(set, elements);
return set;
}
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < elements.length;
}
@Override
public T next() {
return elements[index++];
}
};
}
@SuppressWarnings("unchecked")
public static <T> FixedList<T> of(List<T> list) {
return new FixedList<>((T[]) list.toArray());
}
}

View File

@ -0,0 +1,25 @@
package com.terraforged.core.util.grid;
import java.util.List;
public class MappedList<T> extends FixedList<T> {
private final float min;
private final float range;
public MappedList(T[] elements, float min, float range) {
super(elements);
this.min = min;
this.range = range;
}
@Override
public int indexOf(float value) {
return super.indexOf((value - min) / range);
}
@SuppressWarnings("unchecked")
public static <T> MappedList<T> of(List<T> list, float min, float range) {
return new MappedList<>((T[]) list.toArray(), min, range);
}
}

View File

@ -0,0 +1,13 @@
package com.terraforged.core.util.serialization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Comment {
String[] value();
}

View File

@ -0,0 +1,12 @@
package com.terraforged.core.util.serialization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Option {
}

View File

@ -0,0 +1,15 @@
package com.terraforged.core.util.serialization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
float min();
float max();
}

View File

@ -0,0 +1,11 @@
package com.terraforged.core.util.serialization.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Serializable {
}

View File

@ -0,0 +1,85 @@
package com.terraforged.core.util.serialization.serializer;
import com.terraforged.core.util.serialization.annotation.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
public class Deserializer {
public void deserialize(Reader reader, Object object) throws Throwable {
Class<?> type = object.getClass();
for (String name : reader.getKeys()) {
if (name.charAt(0) == '_') {
continue;
}
Reader child = reader.getChild(name);
Field field = type.getField(getName(name));
if (Serializer.isSerializable(field)) {
field.setAccessible(true);
fromValue(child, object, field);
}
}
}
private void fromValue(Reader reader, Object object, Field field) throws Throwable {
if (field.getType() == int.class) {
field.set(object, reader.getInt("value"));
return;
}
if (field.getType() == float.class) {
field.set(object, reader.getFloat("value"));
return;
}
if (field.getType() == boolean.class) {
field.set(object, reader.getBool("value"));
return;
}
if (field.getType() == String.class) {
field.set(object, reader.getString("value"));
return;
}
if (field.getType().isEnum()) {
String name = reader.getString("value");
for (Enum e : field.getType().asSubclass(Enum.class).getEnumConstants()) {
if (e.name().equals(name)) {
field.set(object, e);
return;
}
}
}
if (field.getType().isAnnotationPresent(Serializable.class)) {
Reader child = reader.getChild("value");
Object value = field.getType().newInstance();
deserialize(child, value);
field.set(object, value);
return;
}
if (field.getType().isArray()) {
Class<?> type = field.getType().getComponentType();
if (type.isAnnotationPresent(Serializable.class)) {
Reader child = reader.getChild("value");
Object array = Array.newInstance(type, child.getSize());
for (int i = 0; i < child.getSize(); i++) {
Object value = type.newInstance();
deserialize(child.getChild(i), value);
Array.set(array, i, value);
}
field.set(object, array);
}
}
}
private static String getName(String name) {
StringBuilder sb = new StringBuilder(name.length());
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (c == '_' && i + 1 < name.length()) {
sb.append(Character.toUpperCase(name.charAt(++i)));
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@ -0,0 +1,58 @@
package com.terraforged.core.util.serialization.serializer;
import java.util.Collection;
public interface Reader {
int getSize();
Reader getChild(String key);
Reader getChild(int index);
Collection<String> getKeys();
String getString();
boolean getBool();
float getFloat();
int getInt();
default String getString(String key) {
return getChild(key).getString();
}
default boolean getBool(String key) {
return getChild(key).getBool();
}
default float getFloat(String key) {
return getChild(key).getFloat();
}
default int getInt(String key) {
return getChild(key).getInt();
}
default String getString(int index) {
return getChild(index).getString();
}
default boolean getBool(int index) {
return getChild(index).getBool();
}
default float getFloat(int index) {
return getChild(index).getFloat();
}
default int getInt(int index) {
return getChild(index).getInt();
}
default void writeTo(Object object) throws Throwable {
new Deserializer().deserialize(this, object);
}
}

View File

@ -0,0 +1,146 @@
package com.terraforged.core.util.serialization.serializer;
import com.terraforged.core.util.serialization.annotation.Comment;
import com.terraforged.core.util.serialization.annotation.Range;
import com.terraforged.core.util.serialization.annotation.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Serializer {
public void serialize(Object object, Writer writer) throws IllegalAccessException {
if (object.getClass().isArray()) {
writer.beginArray();
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
serialize(element, writer);
}
writer.endArray();
} else if (!object.getClass().isPrimitive()) {
int order = 0;
writer.beginObject();
for (Field field : object.getClass().getFields()) {
if (Serializer.isSerializable(field)) {
field.setAccessible(true);
write(object, field, order, writer);
order++;
}
}
writer.endObject();
}
}
private void write(Object object, Field field, int order, Writer writer) throws IllegalAccessException {
if (field.getType() == int.class) {
writer.name(getName(field));
writer.beginObject();
writer.name("value").value((int) field.get(object));
writeMeta(field, order, writer);
writer.endObject();
return;
}
if (field.getType() == float.class) {
writer.name(getName(field));
writer.beginObject();
writer.name("value").value((float) field.get(object));
writeMeta(field, order, writer);
writer.endObject();
return;
}
if (field.getType() == String.class) {
writer.name(getName(field));
writer.beginObject();
writer.name("value").value((String) field.get(object));
writeMeta(field, order, writer);
writer.endObject();
return;
}
if (field.getType().isEnum()) {
writer.name(getName(field));
writer.beginObject();
writer.name("value").value(((Enum) field.get(object)).name());
writeMeta(field, order, writer);
writer.endObject();
return;
}
if (field.getType().isArray()) {
if (field.getType().getComponentType().isAnnotationPresent(Serializable.class)) {
writer.name(getName(field));
writer.beginObject();
writer.name("value");
serialize(field.get(object), writer);
writeMeta(field, order, writer);
writer.endObject();
}
return;
}
if (field.getType().isAnnotationPresent(Serializable.class)) {
writer.name(getName(field));
writer.beginObject();
writer.name("value");
serialize(field.get(object), writer);
writeMeta(field, order, writer);
writer.endObject();
}
}
private void writeMeta(Field field, int order, Writer writer) {
writer.name("_name").value(getName(field));
writer.name("_order").value(order);
Range range = field.getAnnotation(Range.class);
if (range != null) {
if (field.getType() == int.class) {
writer.name("_min").value((int) range.min());
writer.name("_max").value((int) range.max());
} else {
writer.name("_min").value(range.min());
writer.name("_max").value(range.max());
}
}
Comment comment = field.getAnnotation(Comment.class);
if (comment != null) {
writer.name("_comment");
writer.beginArray();
for (String line : comment.value()) {
writer.value(line);
}
writer.endArray();
}
if (field.getType().isEnum()) {
writer.name("_options");
writer.beginArray();
for (Enum o : field.getType().asSubclass(Enum.class).getEnumConstants()) {
writer.value(o.name());
}
writer.endArray();
}
}
private static String getName(Field field) {
String name = field.getName();
StringBuilder sb = new StringBuilder(name.length() * 2);
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (Character.isUpperCase(c)) {
sb.append('_').append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
protected static boolean isSerializable(Field field) {
int modifiers = field.getModifiers();
return Modifier.isPublic(modifiers)
&& !Modifier.isFinal(modifiers)
&& !Modifier.isStatic(modifiers)
&& !Modifier.isTransient(modifiers);
}
}

View File

@ -0,0 +1,24 @@
package com.terraforged.core.util.serialization.serializer;
public interface Writer {
Writer name(String name);
Writer beginObject();
Writer endObject();
Writer beginArray();
Writer endArray();
Writer value(String value);
Writer value(float value);
Writer value(int value);
default void readFrom(Object value) throws IllegalAccessException {
new Serializer().serialize(value, this);
}
}

View File

@ -0,0 +1,41 @@
package com.terraforged.core.world;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.heightmap.Levels;
import com.terraforged.core.world.terrain.Terrains;
import com.terraforged.core.world.terrain.provider.StandardTerrainProvider;
import com.terraforged.core.world.terrain.provider.TerrainProviderFactory;
public class GeneratorContext {
public final Seed seed;
public final Levels levels;
public final Terrains terrain;
public final Settings settings;
public final TerrainProviderFactory terrainFactory;
public GeneratorContext(Terrains terrain, Settings settings) {
this(terrain, settings, StandardTerrainProvider::new);
}
public GeneratorContext(Terrains terrain, Settings settings, TerrainProviderFactory terrainFactory) {
this.terrain = terrain;
this.settings = settings;
this.seed = new Seed(settings.generator.seed);
this.levels = new Levels(settings.generator);
this.terrainFactory = terrainFactory;
}
private GeneratorContext(GeneratorContext src) {
seed = new Seed(src.seed.get());
levels = src.levels;
terrain = src.terrain;
settings = src.settings;
terrainFactory = src.terrainFactory;
}
public GeneratorContext copy() {
return new GeneratorContext(this);
}
}

View File

@ -0,0 +1,26 @@
package com.terraforged.core.world;
import com.terraforged.core.decorator.Decorator;
import com.terraforged.core.decorator.DesertStacks;
import com.terraforged.core.decorator.SwampPools;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class WorldDecorators {
private final List<Decorator> decorators;
public WorldDecorators(GeneratorContext context) {
context = context.copy();
List<Decorator> list = new ArrayList<>();
list.add(new DesertStacks(context.seed, context.levels));
list.add(new SwampPools(context.seed, context.terrain, context.levels));
decorators = Collections.unmodifiableList(list);
}
public List<Decorator> getDecorators() {
return decorators;
}
}

View File

@ -0,0 +1,46 @@
package com.terraforged.core.world;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.filter.Erosion;
import com.terraforged.core.filter.Smoothing;
import com.terraforged.core.filter.Steepness;
import com.terraforged.core.settings.FilterSettings;
public class WorldFilters {
private final Erosion erosion;
private final Smoothing smoothing;
private final Steepness steepness;
private final FilterSettings settings;
public WorldFilters(GeneratorContext context) {
context = context.copy();
this.settings = context.settings.filters;
this.erosion = new Erosion(context.settings, context.levels);
this.smoothing = new Smoothing(context.settings, context.levels);
this.steepness = new Steepness(1, 10F, context.terrain);
}
public void setRegion(int regionX, int regionZ) {
long seed = NoiseUtil.seed(regionX, regionZ);
getErosion().setSeed(seed);
getSmoothing().setSeed(seed);
getSteepness().setSeed(seed);
}
public FilterSettings getSettings() {
return settings;
}
public Erosion getErosion() {
return erosion;
}
public Smoothing getSmoothing() {
return smoothing;
}
public Steepness getSteepness() {
return steepness;
}
}

View File

@ -0,0 +1,28 @@
package com.terraforged.core.world;
import com.terraforged.core.world.heightmap.Heightmap;
public class WorldGenerator {
private final Heightmap heightmap;
private final WorldFilters filters;
private final WorldDecorators decorators;
public WorldGenerator(Heightmap heightmap, WorldDecorators decorators, WorldFilters filters) {
this.filters = filters;
this.heightmap = heightmap;
this.decorators = decorators;
}
public Heightmap getHeightmap() {
return heightmap;
}
public WorldFilters getFilters() {
return filters;
}
public WorldDecorators getDecorators() {
return decorators;
}
}

View File

@ -0,0 +1,44 @@
package com.terraforged.core.world;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.heightmap.WorldHeightmap;
import java.util.function.Supplier;
public class WorldGeneratorFactory implements Supplier<WorldGenerator> {
private final GeneratorContext context;
private final Heightmap heightmap;
private final WorldDecorators decorators;
public WorldGeneratorFactory(GeneratorContext context) {
this.context = context;
this.heightmap = new WorldHeightmap(context);
this.decorators = new WorldDecorators(context);
}
public WorldGeneratorFactory(GeneratorContext context, Heightmap heightmap) {
this.context = context;
this.heightmap = heightmap;
this.decorators = new WorldDecorators(context);
}
public Heightmap getHeightmap() {
return heightmap;
}
public Climate getClimate() {
return getHeightmap().getClimate();
}
public WorldDecorators getDecorators() {
return decorators;
}
@Override
public WorldGenerator get() {
return new WorldGenerator(heightmap, decorators, new WorldFilters(context));
}
}

View File

@ -0,0 +1,134 @@
package com.terraforged.core.world.biome;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BiomeData implements Comparable<BiomeData> {
private static float[] bounds = {0, 1 / 3F, 2 / 3F, 1F};
public static final List<BiomeData> BIOMES = new ArrayList<>();
public static BiomeData DEFAULT = new BiomeData("none", "", 1, 0.5F, 0.5F);
public final String name;
public final Object reference;
public final float color;
public final float rainfall;
public final float temperature;
public BiomeData(String name, Object reference, float color, float rainfall, float temperature) {
this.reference = reference;
this.name = name;
this.rainfall = rainfall;
this.temperature = temperature;
this.color = color;
}
public BiomeData(String name, Object reference, int color, float rainfall, float temperature) {
Color c = new Color(color);
this.reference = reference;
this.name = name;
this.rainfall = rainfall;
this.temperature = temperature;
this.color = getHue(c.getRed(), c.getGreen(), c.getBlue());
}
@Override
public int compareTo(BiomeData o) {
return name.compareTo(o.name);
}
public static Collection<BiomeData> getBiomes(float temperature, float rainfall) {
int temp = Math.min(3, (int) (bounds.length * temperature));
int rain = Math.min(3, (int) (bounds.length * rainfall));
return getBiomes(temp, rain);
}
public static Collection<BiomeData> getBiomes(int tempLower, int rainLower) {
int temp0 = tempLower;
int temp1 = temp0 + 1;
int rain0 = rainLower;
int rain1 = rain0 + 1;
float tempMin = bounds[temp0];
float tempMax = bounds[temp1];
float rainMin = bounds[rain0];
float rainMax = bounds[rain1];
List<BiomeData> biomes = new ArrayList<>();
for (BiomeData biome : BIOMES) {
if (biome.temperature >= tempMin && biome.temperature <= tempMax
&& biome.rainfall >= rainMin && biome.rainfall <= rainMax) {
biomes.add(biome);
}
}
if (biomes.isEmpty()) {
biomes.add(DEFAULT);
}
return biomes;
}
public static Collection<BiomeData> getTempBiomes(float temperature) {
int lower = Math.min(3, (int) (bounds.length * temperature));
int upper = lower + 1;
float min = bounds[lower];
float max = bounds[upper];
List<BiomeData> biomes = new ArrayList<>();
for (BiomeData data : BIOMES) {
if (data.temperature >= min && data.temperature <= max) {
biomes.add(data);
}
}
return biomes;
}
public static Collection<BiomeData> getRainBiomes(float rainfall) {
int lower = Math.min(3, (int) (bounds.length * rainfall));
int upper = lower + 1;
float min = bounds[lower];
float max = bounds[upper];
List<BiomeData> biomes = new ArrayList<>();
for (BiomeData data : BIOMES) {
if (data.rainfall >= min && data.rainfall <= max) {
biomes.add(data);
}
}
return biomes;
}
private static float getHue(int red, int green, int blue) {
float min = Math.min(Math.min(red, green), blue);
float max = Math.max(Math.max(red, green), blue);
if (min == max) {
return 0;
}
float hue;
if (max == red) {
hue = (green - blue) / (max - min);
} else if (max == green) {
hue = 2f + (blue - red) / (max - min);
} else {
hue = 4f + (red - green) / (max - min);
}
hue = hue * 60;
if (hue < 0) hue = hue + 360;
return (Math.round(hue) / 360F) * 100F;
}
}

View File

@ -0,0 +1,10 @@
package com.terraforged.core.world.biome;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BiomeManager {
private final Map<BiomeType, List<BiomeData>> biomes = new HashMap<>();
}

View File

@ -0,0 +1,111 @@
package com.terraforged.core.world.biome;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import java.awt.*;
public enum BiomeType {
TROPICAL_RAINFOREST(7, 83, 48, new Color(7, 83, 48)),
SAVANNA(151, 165, 39, new Color(151, 165, 39)),
DESERT(200, 113, 55, new Color(200, 113, 55)),
TEMPERATE_RAINFOREST(10, 84, 109, new Color(10, 160, 65)),
TEMPERATE_FOREST(44, 137, 160, new Color(50, 200, 80)),
GRASSLAND(179, 124, 6, new Color(100, 220, 60)),
COLD_STEPPE(131, 112, 71, new Color(175, 180, 150)),
STEPPE(199, 155, 60, new Color(200, 200, 120)),
TAIGA(91, 143, 82, new Color(91, 143, 82)),
TUNDRA(147, 167, 172, new Color(147, 167, 172)),
ALPINE(0, 0, 0, new Color(160, 120, 170));
public static final int RESOLUTION = 256;
public static final int MAX = RESOLUTION - 1;
private final Color lookup;
private final Color color;
BiomeType(int r, int g, int b, Color color) {
this(new Color(r, g, b), color);
}
BiomeType(Color lookup, Color color) {
this.lookup = lookup;
this.color = BiomeTypeColors.getInstance().getColor(name(), color);
}
Color getLookup() {
return lookup;
}
public Color getColor() {
return color;
}
public static BiomeType get(float temperature, float moisture) {
return getCurve(temperature, moisture);
}
public static BiomeType getLinear(float temperature, float moisture) {
int x = NoiseUtil.round(MAX * temperature);
int y = getYLinear(x, temperature, moisture);
return getType(x, y);
}
public static BiomeType getCurve(float temperature, float moisture) {
int x = NoiseUtil.round(MAX * temperature);
int y = getYCurve(x, temperature, moisture);
return getType(x, y);
}
public static float getEdge(float temperature, float moisture) {
return getEdgeCurve(temperature, moisture);
}
public static float getEdgeLinear(float temperature, float moisture) {
int x = NoiseUtil.round(MAX * temperature);
int y = getYLinear(x, temperature, moisture);
return getEdge(x, y);
}
public static float getEdgeCurve(float temperature, float moisture) {
int x = NoiseUtil.round(MAX * temperature);
int y = getYCurve(x, temperature, moisture);
return getEdge(x, y);
}
public static void apply(Cell<?> cell) {
applyCurve(cell);
}
public static void applyLinear(Cell<?> cell) {
cell.biomeType = get(cell.biomeTemperature, cell.biomeMoisture);
cell.biomeTypeMask = getEdge(cell.temperature, cell.moisture);
}
public static void applyCurve(Cell<?> cell) {
cell.biomeType = get(cell.biomeTemperature, cell.biomeMoisture);
cell.biomeTypeMask = getEdge(cell.temperature, cell.moisture);
}
private static BiomeType getType(int x, int y) {
return BiomeTypeLoader.getInstance().getTypeMap()[y][x];
}
private static float getEdge(int x, int y) {
return BiomeTypeLoader.getInstance().getEdgeMap()[y][x];
}
private static int getYLinear(int x, float temperature, float moisture) {
if (moisture > temperature) {
return x;
}
return NoiseUtil.round(MAX * moisture);
}
private static int getYCurve(int x, float temperature, float moisture) {
int max = x + ((MAX - x) / 2);
int y = NoiseUtil.round(max * moisture);
return Math.min(x, y);
}
}

View File

@ -0,0 +1,50 @@
package com.terraforged.core.world.biome;
import java.awt.*;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BiomeTypeColors {
private static BiomeTypeColors instance = new BiomeTypeColors();
private final Map<String, Color> colors = new HashMap<>();
private BiomeTypeColors() {
try (InputStream inputStream = BiomeType.class.getResourceAsStream("/biomes.txt")) {
Properties properties = new Properties();
properties.load(inputStream);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
Color color = Color.decode("#" + entry.getValue().toString());
colors.put(entry.getKey().toString(), color);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public Color getColor(String name, Color defaultColor) {
return colors.getOrDefault(name, defaultColor);
}
public static BiomeTypeColors getInstance() {
return instance;
}
public static void main(String[] args) throws Throwable {
try (FileWriter writer = new FileWriter("biome_colors.properties")) {
Properties properties = new Properties();
for (BiomeType type : BiomeType.values()) {
int r = type.getColor().getRed();
int g = type.getColor().getGreen();
int b = type.getColor().getBlue();
properties.setProperty(type.name(), String.format("%02x%02x%02x", r, g, b));
}
properties.store(writer, "TerraForged BiomeType Hex Colors (do not include hash/pound character)");
}
}
}

View File

@ -0,0 +1,179 @@
package com.terraforged.core.world.biome;
import me.dags.noise.util.NoiseUtil;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class BiomeTypeLoader {
private static BiomeTypeLoader instance;
private final float[][] edges = new float[BiomeType.RESOLUTION][BiomeType.RESOLUTION];
private final BiomeType[][] map = new BiomeType[BiomeType.RESOLUTION][BiomeType.RESOLUTION];
public BiomeTypeLoader() {
generateTypeMap();
generateEdgeMap();
}
public BiomeType[][] getTypeMap() {
return map;
}
public float[][] getEdgeMap() {
return edges;
}
private BiomeType getType(int x, int y) {
return map[y][x];
}
private void generateTypeMap() {
try {
BufferedImage image = ImageIO.read(BiomeType.class.getResourceAsStream("/biomes.png"));
float xf = image.getWidth() / (float) BiomeType.RESOLUTION;
float yf = image.getHeight() / (float) BiomeType.RESOLUTION;
for (int y = 0; y < BiomeType.RESOLUTION; y++) {
for (int x = 0; x < BiomeType.RESOLUTION; x++) {
if (BiomeType.MAX - y > x) {
map[BiomeType.MAX - y][x] = BiomeType.ALPINE;
continue;
}
int ix = NoiseUtil.round(x * xf);
int iy = NoiseUtil.round(y * yf);
int argb = image.getRGB(ix, iy);
Color color = fromARGB(argb);
map[BiomeType.MAX - y][x] = forColor(color);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void generateEdgeMap() {
int[] distances = new int[BiomeType.values().length];
for (int y = 0; y < BiomeType.RESOLUTION; y++) {
for (int x = 0; x < BiomeType.RESOLUTION; x++) {
if (y > x) continue;
BiomeType type = getType(x, y);
if (type == BiomeType.ALPINE) {
continue;
}
int distance2 = getEdge(x, y, type);
edges[y][x] = distance2;
distances[type.ordinal()] = Math.max(distances[type.ordinal()], distance2);
}
}
for (int y = 0; y < BiomeType.RESOLUTION; y++) {
for (int x = 0; x < BiomeType.RESOLUTION; x++) {
BiomeType type = getType(x, y);
int max = distances[type.ordinal()];
float distance = edges[y][x];
float value = NoiseUtil.pow(distance / max, 0.33F);
edges[y][x] = NoiseUtil.clamp(value, 0, 1);
}
}
}
private int getEdge(int cx, int cy, BiomeType type) {
int radius = BiomeType.RESOLUTION / 4;
int distance2 = Integer.MAX_VALUE;
int x0 = Math.max(0, cx - radius);
int x1 = Math.min(BiomeType.MAX, cx + radius);
int y0 = Math.max(0, cy - radius);
int y1 = Math.min(BiomeType.MAX, cy + radius);
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
BiomeType neighbour = getType(x, y);
if (neighbour == BiomeType.ALPINE) {
continue;
}
if (neighbour != type) {
int dist2 = dist2(cx, cy, x, y);
if (dist2 < distance2) {
distance2 = dist2;
}
}
}
}
return distance2;
}
private static BiomeType forColor(Color color) {
BiomeType type = null;
int closest = Integer.MAX_VALUE;
for (BiomeType t : BiomeType.values()) {
int distance2 = getDistance2(color, t.getLookup());
if (distance2 < closest) {
closest = distance2;
type = t;
}
}
if (type == null) {
return BiomeType.GRASSLAND;
}
return type;
}
private static int getDistance2(Color a, Color b) {
int dr = a.getRed() - b.getRed();
int dg = a.getGreen() - b.getGreen();
int db = a.getBlue() - b.getBlue();
return dr * dr + dg * dg + db * db;
}
private static Color fromARGB(int argb) {
int b = (argb) & 0xFF;
int g = (argb >> 8) & 0xFF;
int r = (argb >> 16) & 0xFF;
return new Color(r, g, b);
}
private static int dist2(int x1, int y1, int x2, int y2) {
int dx = x1 - x2;
int dy = y1 - y2;
return dx * dx + dy * dy;
}
private static BufferedImage generateEdgeMapImage() {
BufferedImage image = new BufferedImage(BiomeType.RESOLUTION, BiomeType.RESOLUTION, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < BiomeType.RESOLUTION; y++) {
for (int x = 0; x < BiomeType.RESOLUTION; x++) {
float temperature = x / (float) BiomeType.RESOLUTION;
float moisture = y / (float) BiomeType.RESOLUTION;
float value = BiomeType.getEdge(temperature, moisture);
int color = Color.HSBtoRGB(0, 0, value);
image.setRGB(x, image.getHeight() - 1 - y, color);
}
}
return image;
}
public static BiomeTypeLoader getInstance() {
if (instance == null) {
instance = new BiomeTypeLoader();
}
return instance;
}
public static void main(String[] args) throws Throwable {
BufferedImage img = generateEdgeMapImage();
ImageIO.write(img, "png", new File("biomes_dist.png"));
JLabel label = new JLabel(new ImageIcon(img));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

View File

@ -0,0 +1,141 @@
package com.terraforged.core.world.climate;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.func.CellFunc;
import me.dags.noise.func.DistanceFunc;
import me.dags.noise.source.Rand;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.module.CellLookup;
import com.terraforged.core.module.CellLookupOffset;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.heightmap.WorldHeightmap;
import com.terraforged.core.world.terrain.Terrain;
public class Climate {
private final float seaLevel;
private final float lowerHeight;
private final float midHeight = 0.425F;
private final float upperHeight = 0.75F;
private final float moistureModifier = 0.05F;
private final float temperatureModifier = 0.175F;
private final Rand rand;
private final Module treeLine;
private final Module heightMap;
private final Module offsetHeightMap;
private final Module offsetX;
private final Module offsetY;
private final ClimateModule biomeNoise;
public Climate(GeneratorContext context, WorldHeightmap heightmap) {
final int cellSeed = context.seed.next();
final int cellSize = context.settings.generator.biome.biomeSize;
final int warpScale = context.settings.generator.biome.biomeWarpScale;
final int warpStrength = context.settings.generator.biome.biomeWarpStrength;
this.biomeNoise = new ClimateModule(context.seed, context.settings.generator);
Module warpX = Source.perlin(context.seed.next(), warpScale, 2);
Module warpZ = Source.perlin(context.seed.next(), warpScale, 2);
Module windDirection = Source.cubic(context.seed.next(), cellSize, 1);
Module windStrength = Source.perlin(context.seed.next(), cellSize, 1)
.scale(cellSize * 1.5)
.bias(cellSize * 0.5);
this.treeLine = Source.perlin(context.seed.next(), context.settings.generator.biome.biomeSize * 2, 1)
.scale(0.1).bias(0.4);
this.heightMap = Source.build(cellSeed, cellSize, 1)
.distFunc(DistanceFunc.NATURAL)
.cellFunc(CellFunc.NOISE_LOOKUP)
.source(new CellLookup(heightmap::getValue, cellSize))
.cell()
.warp(warpX, warpZ, warpStrength);
this.offsetHeightMap = Source.build(cellSeed, cellSize, 1)
.distFunc(DistanceFunc.NATURAL)
.cellFunc(CellFunc.NOISE_LOOKUP)
.source(new CellLookupOffset(heightmap::getValue, windDirection, windStrength, cellSize))
.cell()
.warp(warpX, warpZ, warpStrength);
this.rand = new Rand(Source.builder().seed(context.seed.next()));
this.offsetX = context.settings.generator.biomeEdgeNoise.build(context.seed.next());
this.offsetY = context.settings.generator.biomeEdgeNoise.build(context.seed.next());
this.seaLevel = context.levels.water;
this.lowerHeight = context.levels.ground;
}
public Rand getRand() {
return rand;
}
public float getCellHeight(float x, float z) {
return heightMap.getValue(x, z);
}
public float getOffsetCellHeight(float x, float z) {
return offsetHeightMap.getValue(x, z);
}
public float getOffsetX(float x, float z, int distance) {
return offsetX.getValue(x, z) * distance;
}
public float getOffsetZ(float x, float z, int distance) {
return offsetY.getValue(x, z) * distance;
}
public float getTreeLine(float x, float z) {
return treeLine.getValue(x, z);
}
public void apply(Cell<Terrain> cell, float x, float z, boolean mask) {
biomeNoise.apply(cell, x, z, mask);
modify(cell, x, z);
}
private void modify(Cell<Terrain> cell, float x, float z) {
float height = getCellHeight(x, z);
float height1 = getOffsetCellHeight(x, z);
float moistDelta = 0F;
if (height > seaLevel) {
if (height1 < seaLevel) {
moistDelta = 0.7F;
} else if (height - height1 > 0.2) {
moistDelta = height - height1;
} else if (height1 - height > 0.1) {
moistDelta = Math.max(-0.5F, height - height1) * 2F;
}
}
float moistChange = moistureModifier * moistDelta;
cell.moisture = NoiseUtil.clamp(cell.moisture + moistChange, 0, 1);
if (height > upperHeight) {
cell.temperature = Math.max(0, cell.temperature - temperatureModifier);
return;
}
// temperature decreases away from 'midHeight' towards 'upperHeight'
if (height > midHeight) {
float delta = (height - midHeight) / (upperHeight - midHeight);
cell.temperature = Math.max(0, cell.temperature - (delta * temperatureModifier));
return;
}
height = Math.max(lowerHeight, height);
// temperature increases away from 'midHeight' towards 'lowerHeight'
if (height >= lowerHeight) {
float delta = 1 - ((height - lowerHeight) / (midHeight - lowerHeight));
cell.temperature = Math.min(1, cell.temperature + (delta * temperatureModifier));
}
}
}

View File

@ -0,0 +1,134 @@
package com.terraforged.core.world.climate;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.func.DistanceFunc;
import me.dags.noise.func.EdgeFunc;
import me.dags.noise.util.NoiseUtil;
import me.dags.noise.util.Vec2f;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.settings.GeneratorSettings;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.biome.BiomeType;
import com.terraforged.core.world.terrain.Terrain;
public class ClimateModule {
private final int seed;
private final float edgeClamp;
private final float edgeScale;
private final float biomeFreq;
private final float warpStrength;
private final Module warpX;
private final Module warpZ;
private final Module moisture;
private final Module temperature;
public ClimateModule(Seed seed, GeneratorSettings settings) {
int biomeSize = settings.biome.biomeSize;
float biomeFreq = 1F / biomeSize;
int moistureSize = 40 * biomeSize;
int temperatureSize = 10 * biomeSize;
int moistScale = NoiseUtil.round(moistureSize * biomeFreq);
int tempScale = NoiseUtil.round(temperatureSize * biomeFreq);
int warpScale = settings.biome.biomeWarpScale;
this.seed = seed.next();
this.edgeClamp = 0.85F;
this.edgeScale = 1 / edgeClamp;
this.biomeFreq = 1F / biomeSize;
this.warpStrength = settings.biome.biomeWarpStrength;
this.warpX = Source.perlin(seed.next(), warpScale, 2).bias(-0.5);
this.warpZ = Source.perlin(seed.next(), warpScale, 2).bias(-0.5);
this.moisture = Source.simplex(seed.next(), moistScale, 2)
.clamp(0.15, 0.85).map(0, 1)
.warp(seed.next(), moistScale / 2, 1, moistScale / 4D)
.warp(seed.next(), moistScale / 6, 2, moistScale / 12D);
Module temperature = Source.sin(tempScale, Source.constant(0.9)).clamp(0.05, 0.95).map(0, 1);
this.temperature = new Compressor(temperature, 0.1F, 0.2F)
.warp(seed.next(), tempScale * 4, 2, tempScale * 4)
.warp(seed.next(), tempScale, 1, tempScale)
.warp(seed.next(), tempScale / 8, 1, tempScale / 8D);
}
public void apply(Cell<Terrain> cell, float x, float y, boolean mask) {
float ox = warpX.getValue(x, y) * warpStrength;
float oz = warpZ.getValue(x, y) * warpStrength;
x += ox;
y += oz;
x *= biomeFreq;
y *= biomeFreq;
int cellX = 0;
int cellY = 0;
Vec2f vec2f = null;
int xr = NoiseUtil.round(x);
int yr = NoiseUtil.round(y);
float edgeDistance = 999999.0F;
float edgeDistance2 = 999999.0F;
float valueDistance = 3.4028235E38F;
DistanceFunc dist = DistanceFunc.NATURAL;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
int xi = xr + dx;
int yi = yr + dy;
Vec2f vec = NoiseUtil.CELL_2D[NoiseUtil.hash2D(seed, xi, yi) & 255];
float vecX = xi - x + vec.x;
float vecY = yi - y + vec.y;
float distance = dist.apply(vecX, vecY);
if (distance < valueDistance) {
valueDistance = distance;
vec2f = vec;
cellX = xi;
cellY = yi;
}
if (distance < edgeDistance2) {
edgeDistance2 = Math.max(edgeDistance, distance);
} else {
edgeDistance2 = Math.max(edgeDistance, edgeDistance2);
}
edgeDistance = Math.min(edgeDistance, distance);
}
}
if (mask) {
cell.biomeMask = edgeValue(edgeDistance, edgeDistance2);
} else {
cell.biome = cellValue(seed, cellX, cellY);
cell.biomeMask = edgeValue(edgeDistance, edgeDistance2);
cell.biomeMoisture = moisture.getValue(cellX + vec2f.x, cellY + vec2f.y);
cell.biomeTemperature = temperature.getValue(cellX + vec2f.x, cellY + vec2f.y);
cell.moisture = moisture.getValue(x, y);
cell.temperature = temperature.getValue(x, y);
BiomeType.apply(cell);
}
}
private float cellValue(int seed, int cellX, int cellY) {
float value = NoiseUtil.valCoord2D(seed, cellX, cellY);
return NoiseUtil.map(value, -1, 1, 2);
}
private float edgeValue(float distance, float distance2) {
EdgeFunc edge = EdgeFunc.DISTANCE_2_DIV;
float value = edge.apply(distance, distance2);
value = 1 - NoiseUtil.map(value, edge.min(), edge.max(), edge.range());
if (value > edgeClamp) {
return 1F;
}
return value * edgeScale;
}
}

View File

@ -0,0 +1,56 @@
package com.terraforged.core.world.climate;
import me.dags.noise.Module;
public class Compressor implements Module {
private final float lowerStart;
private final float lowerEnd;
private final float lowerRange;
private final float lowerExpandRange;
private final float upperStart;
private final float upperEnd;
private final float upperRange;
private final float upperExpandedRange;
private final float compression;
private final float compressionRange;
private final Module module;
public Compressor(Module module, float inset, float amount) {
this(module, inset, inset + amount, 1 - inset - amount, 1 - inset);
}
public Compressor(Module module, float lowerStart, float lowerEnd, float upperStart, float upperEnd) {
this.module = module;
this.lowerStart = lowerStart;
this.lowerEnd = lowerEnd;
this.lowerRange = lowerStart;
this.lowerExpandRange = lowerEnd;
this.upperStart = upperStart;
this.upperEnd = upperEnd;
this.upperRange = 1 - upperEnd;
this.upperExpandedRange = 1 - upperStart;
this.compression = upperStart - lowerEnd;
this.compressionRange = upperEnd - lowerStart;
}
@Override
public float getValue(float x, float y) {
float value = module.getValue(x, y);
if (value <= lowerStart) {
float alpha = value / lowerRange;
return alpha * lowerExpandRange;
} else if (value >= upperEnd) {
float delta = value - upperEnd;
float alpha = delta / upperRange;
return upperStart + alpha * upperExpandedRange;
} else {
float delta = value - lowerStart;
float alpha = delta / compressionRange;
return lowerEnd + alpha * compression;
}
}
}

View File

@ -0,0 +1,21 @@
package com.terraforged.core.world.continent;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.module.Blender;
import com.terraforged.core.world.terrain.Terrain;
public class ContinentBlender extends Blender {
private final Populator control;
public ContinentBlender(Populator continent, Populator lower, Populator upper, float min, float max, float split, float tagThreshold) {
super(continent, lower, upper, min, max, split, tagThreshold);
this.control = continent;
}
@Override
public float getValue(Cell<Terrain> cell, float x, float z) {
return cell.continentEdge;
}
}

View File

@ -0,0 +1,7 @@
package com.terraforged.core.world.continent;
import com.terraforged.core.cell.Populator;
public interface ContinentModule extends Populator {
}

View File

@ -0,0 +1,22 @@
package com.terraforged.core.world.continent;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.module.MultiBlender;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.terrain.Terrain;
public class ContinentMultiBlender extends MultiBlender {
private final Populator control;
public ContinentMultiBlender(Climate climate, Populator continent, Populator lower, Populator middle, Populator upper, float min, float mid, float max) {
super(climate, continent, lower, middle, upper, min, mid, max);
this.control = continent;
}
@Override
public float getValue(Cell<Terrain> cell, float x, float z) {
return cell.continentEdge;
}
}

View File

@ -0,0 +1,138 @@
package com.terraforged.core.world.continent;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.domain.Domain;
import me.dags.noise.func.DistanceFunc;
import me.dags.noise.func.EdgeFunc;
import me.dags.noise.util.NoiseUtil;
import me.dags.noise.util.Vec2f;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.settings.GeneratorSettings;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.terrain.Terrain;
public class VoronoiContinentModule implements Populator {
private static final float edgeClampMin = 0.05F;
private static final float edgeClampMax = 0.50F;
private static final float edgeClampRange = edgeClampMax - edgeClampMin;
private final int seed;
private final float frequency;
private final float edgeMin;
private final float edgeMax;
private final float edgeRange;
private final Domain warp;
private final Module shape;
public VoronoiContinentModule(Seed seed, GeneratorSettings settings) {
int tectonicScale = settings.land.continentScale * 4;
int continentScale = settings.land.continentScale / 2;
double oceans = Math.min(Math.max(settings.world.oceanSize, 0.01), 0.99);
double shapeMin = 0.15 + (oceans * 0.35);
this.seed = seed.next();
this.frequency = 1F / tectonicScale;
this.edgeMin = edgeClampMin;
this.edgeMax = (float) oceans;
this.edgeRange = edgeMax - edgeMin;
this.warp = Domain.warp(Source.SIMPLEX, seed.next(), continentScale, 3, continentScale);
this.shape = Source.perlin(seed.next(), settings.land.continentScale, 2)
.clamp(shapeMin, 0.7)
.map(0, 1)
.warp(Source.SIMPLEX, seed.next(), continentScale / 2, 1, continentScale / 4D)
.warp(seed.next(), 50, 1, 20D);
}
@Override
public float getValue(float x, float y) {
Cell<Terrain> cell = new Cell<>();
apply(cell, x, y);
return cell.continentEdge;
}
@Override
public void apply(Cell<Terrain> cell, final float x, final float y) {
float ox = warp.getOffsetX(x, y);
float oz = warp.getOffsetY(x, y);
float px = x + ox;
float py = y + oz;
px *= frequency;
py *= frequency;
int cellX = 0;
int cellY = 0;
int xr = NoiseUtil.round(px);
int yr = NoiseUtil.round(py);
float edgeDistance = 999999.0F;
float edgeDistance2 = 999999.0F;
float valueDistance = 3.4028235E38F;
DistanceFunc dist = DistanceFunc.NATURAL;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
int xi = xr + dx;
int yi = yr + dy;
Vec2f vec = NoiseUtil.CELL_2D[NoiseUtil.hash2D(seed, xi, yi) & 255];
float vecX = xi - px + vec.x;
float vecY = yi - py + vec.y;
float distance = dist.apply(vecX, vecY);
if (distance < valueDistance) {
valueDistance = distance;
cellX = xi;
cellY = yi;
}
if (distance < edgeDistance2) {
edgeDistance2 = Math.max(edgeDistance, distance);
} else {
edgeDistance2 = Math.max(edgeDistance, edgeDistance2);
}
edgeDistance = Math.min(edgeDistance, distance);
}
}
float shapeNoise = shape.getValue(x, y);
float continentNoise = edgeValue(edgeDistance, edgeDistance2);
cell.continent = cellValue(seed, cellX, cellY);
cell.continentEdge = shapeNoise * continentNoise;
}
@Override
public void tag(Cell<Terrain> cell, float x, float y) {
}
private float cellValue(int seed, int cellX, int cellY) {
float value = NoiseUtil.valCoord2D(seed, cellX, cellY);
return NoiseUtil.map(value, -1, 1, 2);
}
private float edgeValue(float distance, float distance2) {
EdgeFunc edge = EdgeFunc.DISTANCE_2_DIV;
float value = edge.apply(distance, distance2);
float edgeValue = 1 - NoiseUtil.map(value, edge.min(), edge.max(), edge.range());
if (edgeValue < edgeMin) {
return 0F;
}
if (edgeValue > edgeMax) {
return 1F;
}
return (edgeValue - edgeMin) / edgeRange;
}
}

View File

@ -0,0 +1,37 @@
package com.terraforged.core.world.geology;
import me.dags.noise.Module;
import java.util.ArrayList;
import java.util.List;
public class Geology<T> {
private final Module selector;
private final List<Strata<T>> backing = new ArrayList<>();
public Geology(Module selector) {
this.selector = selector;
}
public Geology<T> add(Geology<T> geology) {
backing.addAll(geology.backing);
return this;
}
public Geology<T> add(Strata<T> strata) {
backing.add(strata);
return this;
}
public Strata<T> getStrata(float x, int y) {
float noise = selector.getValue(x, y);
return getStrata(noise);
}
public Strata<T> getStrata(float value) {
int index = (int) (value * backing.size());
index = Math.min(backing.size() - 1, index);
return backing.get(index);
}
}

View File

@ -0,0 +1,120 @@
package com.terraforged.core.world.geology;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.util.Seed;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Strata<T> {
private final float[] depthBuffer;
private final Module heightMod;
private final List<Stratum<T>> strata;
private Strata(Module heightMod, List<Stratum<T>> strata) {
this.strata = strata;
this.heightMod = heightMod;
this.depthBuffer = new float[strata.size()];
}
public boolean downwards(final int x, final int y, final int z, Stratum.Visitor<T> visitor) {
int py = y;
T last = null;
float sum = getDepth(x, z);
for (int i = 0; i < strata.size(); i++) {
float depth = depthBuffer[i] / sum;
int height = NoiseUtil.round(depth * y);
T value = strata.get(i).getValue();
last = value;
for (int dy = 0; dy < height; dy++) {
if (py <= y) {
boolean cont = visitor.visit(py, value);
if (!cont) {
return false;
}
}
if (--py < 0) {
return false;
}
}
}
if (last != null) {
while (py > 0) {
visitor.visit(py, last);
py--;
}
}
return true;
}
public boolean upwards(int x, int y, int z, Stratum.Visitor<T> visitor) {
int py = 0;
float sum = getDepth(x, z);
for (int i = strata.size() - 1; i > 0; i--) {
float depth = depthBuffer[i] / sum;
int height = NoiseUtil.round(depth * y);
T value = strata.get(i).getValue();
for (int dy = 0; dy < height; dy++) {
boolean cont = visitor.visit(py, value);
if (!cont) {
return false;
}
if (++py > y) {
return false;
}
}
}
return true;
}
private int getYOffset(int x, int z) {
return (int) (64 * heightMod.getValue(x, z));
}
private float getDepth(int x, int z) {
float sum = 0F;
for (int i = 0; i < strata.size(); i++) {
float depth = strata.get(i).getDepth(x, z);
sum += depth;
depthBuffer[i] = depth;
}
return sum;
}
public static <T> Builder<T> builder(int seed, me.dags.noise.source.Builder noise) {
return new Builder<>(seed, noise);
}
public static class Builder<T> {
private final Seed seed;
private final me.dags.noise.source.Builder noise;
private final List<Stratum<T>> strata = new LinkedList<>();
public Builder(int seed, me.dags.noise.source.Builder noise) {
this.seed = new Seed(seed);
this.noise = noise;
}
public Builder<T> add(T material, double depth) {
Module module = noise.seed(seed.next()).perlin().scale(depth);
strata.add(Stratum.of(material, module));
return this;
}
public Builder<T> add(Source type, T material, double depth) {
Module module = noise.seed(seed.next()).build(type).scale(depth);
strata.add(Stratum.of(material, module));
return this;
}
public Strata<T> build() {
Module height = Source.cell(seed.next(), 100);
return new Strata<>(height, new ArrayList<>(strata));
}
}
}

View File

@ -0,0 +1,40 @@
package com.terraforged.core.world.geology;
import me.dags.noise.Module;
import me.dags.noise.Source;
public class Stratum<T> {
private final T value;
private final Module depth;
public Stratum(T value, double depth) {
this(value, Source.constant(depth));
}
public Stratum(T value, Module depth) {
this.depth = depth;
this.value = value;
}
public T getValue() {
return value;
}
public float getDepth(float x, float z) {
return depth.getValue(x, z);
}
public static <T> Stratum<T> of(T t, double depth) {
return new Stratum<>(t, depth);
}
public static <T> Stratum<T> of(T t, Module depth) {
return new Stratum<>(t, depth);
}
public interface Visitor<T> {
boolean visit(int y, T value);
}
}

View File

@ -0,0 +1,55 @@
package com.terraforged.core.world.heightmap;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.cell.Extent;
import com.terraforged.core.region.Size;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.terrain.Terrain;
import java.rmi.UnexpectedException;
public interface Heightmap extends Populator, Extent {
Climate getClimate();
RiverManager getRiverManager();
@Override
default Cell<Terrain> getCell(int x, int z) {
throw new RuntimeException("Don't use this pls");
}
@Override
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
int chunkSize = Size.chunkToBlock(1);
int chunkMinX = Size.blockToChunk(minX);
int chunkMinZ = Size.blockToChunk(minZ);
int chunkMaxX = Size.blockToChunk(maxX);
int chunkMaxZ = Size.blockToChunk(maxZ);
try (ObjectPool.Item<Cell<Terrain>> cell = Cell.pooled()) {
for (int chunkZ = chunkMinZ; chunkZ <= chunkMaxZ; chunkZ++) {
for (int chunkX = chunkMinX; chunkX <= chunkMaxX; chunkX++) {
int chunkStartX = Size.chunkToBlock(chunkX);
int chunkStartZ = Size.chunkToBlock(chunkZ);
for (int dz = 0; dz < chunkSize; dz++) {
for (int dx = 0; dx < chunkSize; dx++) {
int x = chunkStartX + dx;
int z = chunkStartZ + dz;
apply(cell.getValue(), x, z);
if (x >= minX && x < maxX && z >= minZ && z < maxZ) {
int relX = x - minX;
int relZ = z - minZ;
visitor.visit(cell.getValue(), relX, relZ);
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.terraforged.core.world.heightmap;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.settings.GeneratorSettings;
public class Levels {
public final int worldHeight;
// y index of the top-most water block
public final int waterY;
private final int groundY;
// top of the first ground block (ie 1 above ground index)
public final int groundLevel;
// top of the top-most water block (ie 1 above water index)
public final int waterLevel;
// ground index mapped between 0-1 (default 63 / 256)
public final float ground;
// water index mapped between 0-1 (default 62 / 256)
public final float water;
public Levels(GeneratorSettings settings) {
worldHeight = Math.max(1, settings.world.worldHeight);
waterLevel = settings.world.seaLevel;
groundLevel = waterLevel + 1;
waterY = Math.min(waterLevel - 1, worldHeight);
groundY = Math.min(groundLevel - 1, worldHeight);
ground = NoiseUtil.div(groundY, worldHeight);
water = NoiseUtil.div(waterY, worldHeight);
}
public float scale(int level) {
return NoiseUtil.div(level, worldHeight);
}
public float water(int amount) {
return NoiseUtil.div(waterY + amount, worldHeight);
}
public float ground(int amount) {
return NoiseUtil.div(groundY + amount, worldHeight);
}
public static float getSeaLevel(GeneratorSettings settings) {
int worldHeight = Math.max(1, settings.world.worldHeight);
int waterLevel = Math.min(settings.world.seaLevel, worldHeight);
return NoiseUtil.div(waterLevel, worldHeight);
}
}

View File

@ -0,0 +1,20 @@
package com.terraforged.core.world.heightmap;
import me.dags.noise.Module;
public class RegionConfig {
public final int seed;
public final int scale;
public final Module warpX;
public final Module warpZ;
public final double warpStrength;
public RegionConfig(int seed, int scale, Module warpX, Module warpZ, double warpStrength) {
this.seed = seed;
this.scale = scale;
this.warpX = warpX;
this.warpZ = warpZ;
this.warpStrength = warpStrength;
}
}

View File

@ -0,0 +1,70 @@
package com.terraforged.core.world.heightmap;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Extent;
import com.terraforged.core.region.Region;
import com.terraforged.core.region.Size;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.world.terrain.Terrain;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public interface RegionExtent extends Extent {
int chunkToRegion(int coord);
Region getRegion(int regionX, int regionZ);
Future<Region> getRegionAsync(int regionX, int regionZ);
default ChunkReader getChunk(int chunkX, int chunkZ) {
int regionX = chunkToRegion(chunkX);
int regionZ = chunkToRegion(chunkZ);
Region region = getRegion(regionX, regionZ);
return region.getChunk(chunkX, chunkZ);
}
default List<Future<Region>> getRegions(int minRegionX, int minRegionZ, int maxRegionX, int maxRegionZ) {
List<Future<Region>> regions = new LinkedList<>();
for (int rz = minRegionZ; rz <= maxRegionZ; rz++) {
for (int rx = minRegionX; rx <= maxRegionX; rx++) {
regions.add(getRegionAsync(rx, rz));
}
}
return regions;
}
@Override
default Cell<Terrain> getCell(int x, int z) {
int regionX = chunkToRegion(Size.blockToChunk(x));
int regionZ = chunkToRegion(Size.blockToChunk(z));
Region region = getRegion(regionX, regionZ);
return region.getCell(x, z);
}
@Override
default void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor<Terrain> visitor) {
int minRegionX = chunkToRegion(Size.blockToChunk(minX));
int minRegionZ = chunkToRegion(Size.blockToChunk(minZ));
int maxRegionX = chunkToRegion(Size.blockToChunk(maxX));
int maxRegionZ = chunkToRegion(Size.blockToChunk(maxZ));
List<Future<Region>> regions = getRegions(minRegionX, minRegionZ, maxRegionX, maxRegionZ);
while (!regions.isEmpty()) {
regions.removeIf(future -> {
if (!future.isDone()) {
return false;
}
try {
Region region = future.get();
region.visit(minX, minZ, maxX, maxZ, visitor);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return true;
});
}
}
}

View File

@ -0,0 +1,206 @@
package com.terraforged.core.world.heightmap;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.func.EdgeFunc;
import me.dags.noise.func.Interpolation;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.cell.Populator;
import com.terraforged.core.module.Blender;
import com.terraforged.core.module.Lerp;
import com.terraforged.core.module.MultiBlender;
import com.terraforged.core.module.Selector;
import com.terraforged.core.settings.GeneratorSettings;
import com.terraforged.core.settings.Settings;
import com.terraforged.core.util.Seed;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.continent.ContinentBlender;
import com.terraforged.core.world.continent.ContinentMultiBlender;
import com.terraforged.core.world.continent.VoronoiContinentModule;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
import com.terraforged.core.world.terrain.provider.TerrainProvider;
public class WorldHeightmap implements Heightmap {
private static final float DEEP_OCEAN_VALUE = 0.2F;
private static final float OCEAN_VALUE = 0.3F;
private static final float BEACH_VALUE = 0.34F;
private static final float COAST_VALUE = 0.4F;
private static final float INLAND_VALUE = 0.6F;
private final Levels levels;
private final Terrains terrain;
private final Settings settings;
private final Climate climate;
private final Populator root;
private final Populator continent;
private final RiverManager riverManager;
private final TerrainProvider terrainProvider;
public WorldHeightmap(GeneratorContext context) {
context = context.copy();
this.levels = context.levels;
this.terrain = context.terrain;
this.settings = context.settings;
this.climate = new Climate(context, this);
Seed seed = context.seed;
Levels levels = context.levels;
GeneratorSettings genSettings = context.settings.generator;
Seed regionSeed = seed.nextSeed();
Seed regionWarp = seed.nextSeed();
int regionWarpScale = 400;
int regionWarpStrength = 200;
RegionConfig regionConfig = new RegionConfig(
regionSeed.get(),
context.settings.generator.land.regionSize,
Source.simplex(regionWarp.next(), regionWarpScale, 2),
Source.simplex(regionWarp.next(), regionWarpScale, 2),
regionWarpStrength
);
// controls where mountain chains form in the world
Module mountainShapeBase = Source.cellEdge(seed.next(), genSettings.land.mountainScale, EdgeFunc.DISTANCE_2_ADD)
.add(Source.cubic(seed.next(), genSettings.land.mountainScale, 1).scale(-0.05));
// sharpens the transition to create steeper mountains
Module mountainShape = mountainShapeBase
.curve(Interpolation.CURVE3)
.clamp(0, 0.9)
.map(0, 1);
// controls the shape of terrain regions
Module regionShape = Source.cell(regionConfig.seed, regionConfig.scale)
.warp(regionConfig.warpX, regionConfig.warpZ, regionConfig.warpStrength);
// the corresponding edges of terrain regions so we can fade out towards borders
Module regionEdge = Source.cellEdge(regionConfig.seed, regionConfig.scale, EdgeFunc.DISTANCE_2_DIV).invert()
.warp(regionConfig.warpX, regionConfig.warpZ, regionConfig.warpStrength)
.pow(1.5)
.clamp(0, 0.75)
.map(0, 1);
this.terrainProvider = context.terrainFactory.create(context, regionConfig, this);
// the voronoi controlled terrain regions
Populator terrainRegions = new Selector(regionShape, terrainProvider.getPopulators());
// the terrain type at region edges
Populator terrainRegionBorders = new TerrainPopulator(terrainProvider.getLandforms().steppe(seed), context.terrain.steppe);
// transitions between the unique terrain regions and the common border terrain
Populator terrain = new Lerp(
regionEdge,
terrainRegionBorders,
terrainRegions
);
// mountain populator
Populator mountains = register(terrainProvider.getLandforms().mountains(seed), context.terrain.mountains);
// controls what's ocean and what's land
this.continent = createContinent(context);
// blends between normal terrain and mountain chains
Populator land = new Blender(
mountainShape,
terrain,
mountains,
0.1F,
0.9F,
0.6F
);
// uses the continent noise to blend between deep ocean, to ocean, to coast
MultiBlender oceans = new ContinentMultiBlender(
climate,
continent,
register(terrainProvider.getLandforms().deepOcean(seed.next()), context.terrain.deepOcean),
register(Source.constant(levels.water(-7)), context.terrain.ocean),
register(Source.constant(levels.water), context.terrain.coast),
DEEP_OCEAN_VALUE, // below == deep, above == transition to shallow
OCEAN_VALUE, // below == transition to deep, above == transition to coast
COAST_VALUE // below == transition to shallow, above == coast
);
// blends between the ocean/coast terrain and land terrains
root = new ContinentBlender(
continent,
oceans,
land,
OCEAN_VALUE, // below == pure ocean
INLAND_VALUE, // above == pure land
COAST_VALUE, // split point
COAST_VALUE - 0.05F
).mask();
this.riverManager = new RiverManager(this, context);
}
public RiverManager getRiverManager() {
return riverManager;
}
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
// initial type
cell.tag = terrain.steppe;
// apply continent value/edge noise
continent.apply(cell, x, z);
// apply actuall heightmap
root.apply(cell, x, z);
// apply rivers
riverManager.apply(cell, x, z);
// apply climate data
if (cell.value <= levels.water) {
climate.apply(cell, x, z, false);
if (cell.tag == terrain.coast) {
cell.tag = terrain.ocean;
}
} else {
int range = settings.generator.biomeEdgeNoise.strength;
float dx = climate.getOffsetX(x, z, range);
float dz = climate.getOffsetZ(x, z, range);
float px = x + dx;
float pz = z + dz;
tag(cell, px, pz);
climate.apply(cell, px, pz, false);
climate.apply(cell, x, z, true);
}
}
@Override
public void tag(Cell<Terrain> cell, float x, float z) {
continent.apply(cell, x, z);
root.tag(cell, x, z);
}
public Climate getClimate() {
return climate;
}
public Populator getPopulator(Terrain terrain) {
return terrainProvider.getPopulator(terrain);
}
private TerrainPopulator register(Module module, Terrain terrain) {
TerrainPopulator populator = new TerrainPopulator(module, terrain);
terrainProvider.registerMixable(populator);
return populator;
}
private Populator createContinent(GeneratorContext context) {
return new VoronoiContinentModule(context.seed, context.settings.generator);
}
}

View File

@ -0,0 +1,80 @@
package com.terraforged.core.world.river;
import me.dags.noise.Source;
import me.dags.noise.util.NoiseUtil;
import me.dags.noise.util.Vec2f;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
public class Lake extends TerrainPopulator {
private static final int VALLEY_2 = River.VALLEY_WIDTH * River.VALLEY_WIDTH;
private final float lakeDistance2;
private final float valleyDistance2;
private final float bankAlphaMin;
private final float bankAlphaMax;
private final float bankAlphaRange;
private final Vec2f center;
private final LakeConfig config;
private final Terrains terrains;
public Lake(Vec2f center, float radius, LakeConfig config, Terrains terrains) {
super(Source.ZERO, terrains.lake);
this.center = center;
this.config = config;
this.bankAlphaMin = config.bankMin;
this.bankAlphaMax = Math.min(1, bankAlphaMin + 0.275F);
this.bankAlphaRange = bankAlphaMax - bankAlphaMin;
this.lakeDistance2 = radius * radius;
this.valleyDistance2 = VALLEY_2 - lakeDistance2;
this.terrains = terrains;
}
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
float distance2 = getDistance2(x, z);
if (distance2 > VALLEY_2) {
return;
}
float bankHeight = getBankHeight(cell);
if (distance2 > lakeDistance2) {
if (cell.value < bankHeight) {
return;
}
float valleyAlpha = 1F - ((distance2 - lakeDistance2) / valleyDistance2);
cell.value = NoiseUtil.lerp(cell.value, bankHeight, valleyAlpha);
return;
}
cell.value = Math.min(bankHeight, cell.value);
if (distance2 < lakeDistance2) {
float depthAlpha = 1F - (distance2 / lakeDistance2);
float lakeDepth = Math.min(cell.value, config.depth);
cell.value = NoiseUtil.lerp(cell.value, lakeDepth, depthAlpha);
cell.tag = terrains.lake;
}
}
@Override
public void tag(Cell<Terrain> cell, float x, float z) {
cell.tag = terrains.lake;
}
private float getDistance2(float x, float z) {
float dx = center.x - x;
float dz = center.y - z;
return (dx * dx + dz * dz);
}
private float getBankHeight(Cell<Terrain> cell) {
// scale bank height based on elevation of the terrain (higher terrain == taller banks)
float bankHeightAlpha = NoiseUtil.map(cell.value, bankAlphaMin, bankAlphaMax, bankAlphaRange);
// lerp between the min and max heights
return NoiseUtil.lerp(config.bankMin, config.bankMax, bankHeightAlpha);
}
}

View File

@ -0,0 +1,51 @@
package com.terraforged.core.world.river;
import com.terraforged.core.settings.GeneratorSettings;
import com.terraforged.core.world.heightmap.Levels;
public class LakeConfig {
public final float depth;
public final float chance;
public final float sizeMin;
public final float sizeMax;
public final float bankMin;
public final float bankMax;
public final float distanceMin;
public final float distanceMax;
private LakeConfig(Builder builder) {
depth = builder.depth;
chance = builder.chance;
sizeMin = builder.sizeMin;
sizeMax = builder.sizeMax;
bankMin = builder.bankMin;
bankMax = builder.bankMax;
distanceMin = builder.distanceMin;
distanceMax = builder.distanceMax;
}
public static LakeConfig of(GeneratorSettings.Lake settings, Levels levels) {
Builder builder = new Builder();
builder.chance = settings.chance;
builder.sizeMin = settings.sizeMin;
builder.sizeMax = settings.sizeMax;
builder.depth = levels.water(-settings.depth);
builder.distanceMin = settings.minStartDistance;
builder.distanceMax = settings.maxStartDistance;
builder.bankMin = levels.water(settings.minBankHeight);
builder.bankMax = levels.water(settings.maxBankHeight);
return new LakeConfig(builder);
}
public static class Builder {
public float chance;
public float depth = 10;
public float sizeMin = 30;
public float sizeMax = 100;
public float bankMin = 1;
public float bankMax = 8;
public float distanceMin = 0.025F;
public float distanceMax = 0.05F;
}
}

View File

@ -0,0 +1,151 @@
package com.terraforged.core.world.river;
import me.dags.noise.domain.Domain;
import me.dags.noise.util.Vec2i;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.heightmap.WorldHeightmap;
import com.terraforged.core.world.terrain.Terrain;
import java.util.Random;
/**
* Generates random positions within (length / 4) of one of the 4 corners of a region.
* The first position generated will be near any of the four corners
* The second position generated will be near any of the 3 remaining corners to ensure a reasonable distance to the first
*/
public class PosGenerator {
private final int quadSize;
private final Vec2i[] quads = new Vec2i[4];
private final int padding;
private final Domain domain;
private final Cell<Terrain> lookup;
private final WorldHeightmap heightmap;
private int i;
private int dx;
private int dz;
public PosGenerator(WorldHeightmap heightmap, Domain domain, Cell<Terrain> lookup, int size, int padding) {
this.domain = domain;
this.lookup = lookup;
this.padding = padding;
this.heightmap = heightmap;
this.quadSize = (size - (padding * 2)) / 4;
int x1 = 0;
int y1 = 0;
int x2 = 3 * quadSize;
int y2 = 3 * quadSize;
quads[index(0, 0)] = new Vec2i(x1, y1);
quads[index(1, 0)] = new Vec2i(x2, y1);
quads[index(0, 1)] = new Vec2i(x1, y2);
quads[index(1, 1)] = new Vec2i(x2, y2);
}
private void nextSeed(Random random) {
int index = random.nextInt(4);
Vec2i vec = quads[index];
i = index;
dx = padding + vec.x + random.nextInt(quadSize);
dz = padding + vec.y + random.nextInt(quadSize);
}
private void nextPos(Random random) {
int steps = 1 + random.nextInt(3);
int index = (i + steps) & 3;
Vec2i vec = quads[index];
i = index;
dx = padding + vec.x + random.nextInt(quadSize);
dz = padding + vec.y + random.nextInt(quadSize);
}
public RiverNode next(int x, int z, Random random, int attempts) {
for (int i = 0; i < attempts; i++) {
nextSeed(random);
int px = x + dx;
int pz = z + dz;
int wx = (int) domain.getX(px, pz);
int wz = (int) domain.getY(px, pz);
float value1 = heightmap.getValue(lookup, px, pz);
float value2 = heightmap.getValue(lookup, wx, wz);
RiverNode.Type type1 = RiverNode.getType(value1);
RiverNode.Type type2 = RiverNode.getType(value2);
if (type1 == type2 && type1 != RiverNode.Type.NONE) {
if (type1 == RiverNode.Type.END) {
return new RiverNode(wx, wz, type1);
}
return new RiverNode(px, pz, type1);
}
}
return null;
}
public RiverNode nextFrom(int x, int z, Random random, int attempts, RiverNode point, int mindDist2) {
for (int i = 0; i < attempts; i++) {
nextPos(random);
int px = x + dx;
int pz = z + dz;
if (dist2(px, pz, point.x, point.z) < mindDist2) {
continue;
}
int wx = (int) domain.getX(px, pz);
int wz = (int) domain.getY(px, pz);
float value1 = heightmap.getValue(lookup, px, pz);
float value2 = heightmap.getValue(lookup, wx, wz);
RiverNode.Type type1 = RiverNode.getType(value1);
RiverNode.Type type2 = RiverNode.getType(value2);
if (type1 == type2 && type1 == point.type.opposite()) {
if (type1 == RiverNode.Type.END) {
return new RiverNode(wx, wz, type1);
}
return new RiverNode(px, pz, type1);
}
}
return null;
}
public RiverNode nextType(int x, int z, Random random, int attempts, RiverNode.Type match) {
for (int i = 0; i < attempts; i++) {
nextSeed(random);
int px = x + dx;
int pz = z + dz;
int wx = (int) domain.getX(px, pz);
int wz = (int) domain.getY(px, pz);
float value1 = heightmap.getValue(lookup, px, pz);
float value2 = heightmap.getValue(lookup, wx, wz);
RiverNode.Type type1 = RiverNode.getType(value1);
RiverNode.Type type2 = RiverNode.getType(value2);
if (type1 == type2 && type1 == match) {
return new RiverNode(px, pz, type1);
}
}
return null;
}
public RiverNode nextMinHeight(int x, int z, Random random, int attempts, float minHeight) {
for (int i = 0; i < attempts; i++) {
nextPos(random);
int px = x + dx;
int pz = z + dz;
int wx = (int) domain.getX(px, pz);
int wz = (int) domain.getY(px, pz);
float value1 = heightmap.getValue(lookup, px, pz);
float value2 = heightmap.getValue(lookup, wx, wz);
if (value1 > minHeight && value2 > minHeight) {
return new RiverNode(px, pz, RiverNode.Type.START);
}
}
return null;
}
private static int index(int x, int z) {
return z * 2 + x;
}
private static float dist2(int x1, int y1, int x2, int y2) {
float dx = x2 - x1;
float dy = y2 - y1;
return dx * dx + dy * dy;
}
}

View File

@ -0,0 +1,172 @@
package com.terraforged.core.world.river;
import me.dags.noise.Module;
import me.dags.noise.Source;
import me.dags.noise.source.Line;
import me.dags.noise.util.NoiseUtil;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
public class River extends TerrainPopulator {
public static final int VALLEY_WIDTH = 275;
protected static final float DEPTH_FADE_STRENGTH = 0.5F;
public final boolean main;
private final boolean connecting;
private final float bedHeight;
private final float minBankHeight;
private final float maxBankHeight;
private final float bankAlphaMin;
private final float bankAlphaMax;
private final float bankAlphaRange;
private final Module bankVariance;
private final Line bed;
private final Line banks;
private final Line valley;
public final RiverBounds bounds;
private final Terrains terrains;
private final float depthFadeBias;
public River(RiverBounds bounds, RiverConfig config, Terrains terrains, double fadeIn, double fadeOut) {
this(bounds, config, terrains, fadeIn, fadeOut, false);
}
public River(RiverBounds bounds, RiverConfig config, Terrains terrains, double fadeIn, double fadeOut, boolean connecting) {
super(Source.ZERO, terrains.river);
Module in = Source.constant(fadeIn);
Module out = Source.constant(fadeOut);
Module bedWidth = Source.constant(config.bedWidth * config.bedWidth);
Module bankWidth = Source.constant(config.bankWidth * config.bankWidth);
Module valleyWidth = Source.constant(VALLEY_WIDTH * VALLEY_WIDTH);
this.bounds = bounds;
this.main = config.main;
this.terrains = terrains;
this.connecting = connecting;
this.bedHeight = config.bedHeight;
this.minBankHeight = config.minBankHeight;
this.maxBankHeight = config.maxBankHeight;
this.bankAlphaMin = minBankHeight;
this.bankAlphaMax = Math.min(1, minBankHeight + 0.35F);
this.bankAlphaRange = bankAlphaMax - bankAlphaMin;
this.bankVariance = Source.perlin(1234, 150, 1);
this.depthFadeBias = 1 - DEPTH_FADE_STRENGTH;
this.bed = Source.line(bounds.x1(), bounds.y1(), bounds.x2(), bounds.y2(), bedWidth, in, out, 0.1F);
this.banks = Source.line(bounds.x1(), bounds.y1(), bounds.x2(), bounds.y2(), bankWidth, in, out, 0.1F);
this.valley = Source.line(bounds.x1(), bounds.y1(), bounds.x2(), bounds.y2(), valleyWidth, Source.ZERO, Source.ZERO, 0.33F);
}
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
if (cell.value <= bedHeight) {
return;
}
carve(cell, x, z);
}
@Override
public void tag(Cell<Terrain> cell, float x, float z) {
if (!terrains.overridesRiver(cell.tag)) {
cell.tag = terrains.river;
}
}
private void carve(Cell<Terrain> cell, float x, float z) {
float valleyAlpha = valley.getValue(x, z);
if (valleyAlpha == 0) {
return;
}
// riverMask decreases the closer to the river the position gets
cell.riverMask *= (1 - valleyAlpha);
float bankHeight = getBankHeight(cell, x, z);
if (!carveValley(cell, valleyAlpha, bankHeight)) {
return;
}
// is a branching river and x,z is past the connecting point
if (connecting && banks.clipEnd(x, z)) {
return;
}
float widthModifier = banks.getWidthModifier(x, z);
float banksAlpha = banks.getValue(x, z, widthModifier);
if (banksAlpha == 0) {
return;
}
float bedHeight = getBedHeight(bankHeight, widthModifier);
if (!carveBanks(cell, banksAlpha, bedHeight)) {
return;
}
float bedAlpha = bed.getValue(x, z);
if (bedAlpha == 0) {
return;
}
carveBed(cell, bedAlpha, bedHeight);
}
private float getBankHeight(Cell<Terrain> cell, float x, float z) {
// scale bank height based on elevation of the terrain (higher terrain == taller banks)
float bankHeightAlpha = NoiseUtil.map(cell.value, bankAlphaMin, bankAlphaMax, bankAlphaRange);
// use perlin noise to add a little extra variance to the bank height
float bankHeightVariance = bankVariance.getValue(x, z);
// lerp between the min and max heights
return NoiseUtil.lerp(minBankHeight, maxBankHeight, bankHeightAlpha * bankHeightVariance);
}
private float getBedHeight(float bankHeight, float widthModifier) {
// scale depth of river by with it's width (wider == deeper)
// depthAlpha changes the river depth up ${DEPTH_FADE_STRENGTH} %
float depthAlpha = depthFadeBias + (DEPTH_FADE_STRENGTH * widthModifier);
return NoiseUtil.lerp(bankHeight, this.bedHeight, depthAlpha);
}
private boolean carveValley(Cell<Terrain> cell, float valleyAlpha, float bankHeight) {
// lerp the position's height to the riverbank height
if (cell.value > bankHeight) {
cell.value = NoiseUtil.lerp(cell.value, bankHeight, valleyAlpha);
}
return true;
}
private boolean carveBanks(Cell<Terrain> cell, float banksAlpha, float bedHeight) {
// lerp the position's height to the riverbed height (ie the riverbank slopes)
if (cell.value > bedHeight) {
cell.value = NoiseUtil.lerp(cell.value, bedHeight, banksAlpha);
tag(cell, terrains.riverBanks);
}
return true;
}
private void carveBed(Cell<Terrain> cell, float bedAlpha, float bedHeight) {
// lerp the height down to the riverbed height
cell.value = NoiseUtil.lerp(cell.value, bedHeight, bedAlpha);
tag(cell, terrains.river);
}
private void tag(Cell<Terrain> cell, Terrain tag) {
if (!terrains.overridesRiver(cell.tag)) {
cell.tag = tag;
}
}
public static boolean validStart(float value) {
return value > (70F / 256F);
}
public static boolean validEnd(float value) {
return value < (60F / 256F);
}
}

View File

@ -0,0 +1,66 @@
package com.terraforged.core.world.river;
import me.dags.noise.util.Vec2f;
public class RiverBounds {
private final int x1;
private final int y1;
private final int x2;
private final int y2;
private final int minX;
private final int minY;
private final int maxX;
private final int maxY;
public RiverBounds(int x1, int y1, int x2, int y2, int radius) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.minX = Math.min(x1, x2) - radius;
this.minY = Math.min(y1, y2) - radius;
this.maxX = Math.max(x1, x2) + radius;
this.maxY = Math.max(y1, y2) + radius;
}
public int x1() {
return x1;
}
public int y1() {
return y1;
}
public int x2() {
return x2;
}
public int y2() {
return y2;
}
public boolean overlaps(RiverBounds other) {
if (minX > other.maxX || maxX < other.minX) {
return false;
}
if (minY > other.maxY || maxY < other.minY) {
return false;
}
return true;
}
public Vec2f pos(float distance) {
int dx = x2() - x1();
int dy = y2() - y1();
return new Vec2f(x1() + dx * distance, y1() + dy * distance);
}
public static RiverBounds fromNodes(RiverNode p1, RiverNode p2) {
if (p1.type == RiverNode.Type.START) {
return new RiverBounds(p1.x, p1.z, p2.x, p2.z, 300);
} else {
return new RiverBounds(p2.x, p2.z, p1.x, p1.z, 300);
}
}
}

Some files were not shown because too many files have changed in this diff Show More