core + app
This commit is contained in:
parent
ca0579e976
commit
baeba9c98a
18
TerraForgedApp/build.gradle
Normal file
18
TerraForgedApp/build.gradle
Normal 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" }
|
||||||
|
}
|
136
TerraForgedApp/src/main/java/com/terraforged/app/Applet.java
Normal file
136
TerraForgedApp/src/main/java/com/terraforged/app/Applet.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
98
TerraForgedApp/src/main/java/com/terraforged/app/Cache.java
Normal file
98
TerraForgedApp/src/main/java/com/terraforged/app/Cache.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
TerraForgedApp/src/main/java/com/terraforged/app/Controller.java
Normal file
211
TerraForgedApp/src/main/java/com/terraforged/app/Controller.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
239
TerraForgedApp/src/main/java/com/terraforged/app/Main.java
Normal file
239
TerraForgedApp/src/main/java/com/terraforged/app/Main.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
TerraForgedApp/src/main/java/com/terraforged/app/mesh/Mesh.java
Normal file
116
TerraForgedApp/src/main/java/com/terraforged/app/mesh/Mesh.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
764
TerraForgedApp/src/main/resources/biome_data.json
Normal file
764
TerraForgedApp/src/main/resources/biome_data.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
168
TerraForgedApp/src/main/resources/biome_groups.json
Normal file
168
TerraForgedApp/src/main/resources/biome_groups.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
TerraForgedApp/src/main/resources/grass.png
Normal file
BIN
TerraForgedApp/src/main/resources/grass.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
5
TerraForgedCore/build.gradle
Normal file
5
TerraForgedCore/build.gradle
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apply plugin: "java"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(":Noise2D")
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.terraforged.core.cell;
|
||||||
|
|
||||||
|
public interface Tag {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.terraforged.core.region;
|
||||||
|
|
||||||
|
import com.terraforged.core.world.WorldGeneratorFactory;
|
||||||
|
|
||||||
|
public interface RegionCacheFactory {
|
||||||
|
|
||||||
|
RegionCache create(WorldGeneratorFactory factory);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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 {
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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<>();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.terraforged.core.world.continent;
|
||||||
|
|
||||||
|
import com.terraforged.core.cell.Populator;
|
||||||
|
|
||||||
|
public interface ContinentModule extends Populator {
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
Loading…
Reference in New Issue
Block a user