diff --git a/TerraForgedApp/src/main/java/com/terraforged/app/renderer/Renderer.java b/TerraForgedApp/src/main/java/com/terraforged/app/renderer/Renderer.java index fc79524..c2472f0 100644 --- a/TerraForgedApp/src/main/java/com/terraforged/app/renderer/Renderer.java +++ b/TerraForgedApp/src/main/java/com/terraforged/app/renderer/Renderer.java @@ -68,7 +68,7 @@ public abstract class Renderer { return height * el; } else if (applet.controller.getColorMode() == Applet.BIOME_TYPE) { Color c = cell.biomeType.getColor(); - if (cell.riverMask < 0.2) { + if (cell.riverMask < 0.025) { c = Color.white; } float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/settings/RiverSettings.java b/TerraForgedCore/src/main/java/com/terraforged/core/settings/RiverSettings.java index 18cf41f..4723f47 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/settings/RiverSettings.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/settings/RiverSettings.java @@ -35,6 +35,10 @@ public class RiverSettings { /** * RIVER PROPERTIES */ + @Range(min = 0.0F, max = 5F) + @Comment("Controls how frequently rivers generate") + public float riverFrequency = 1; + public River primaryRivers = new River(5, 2, 8, 25, 8, 0.75F); public River secondaryRiver = new River(4, 1, 6, 15, 5, 0.75F); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/PosGenerator.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/PosGenerator.java index 27f7a74..72071bb 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/PosGenerator.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/PosGenerator.java @@ -40,6 +40,7 @@ import java.util.Random; */ public class PosGenerator { + private final int size; private final int quadSize; private final Vec2i[] quads = new Vec2i[4]; @@ -57,6 +58,7 @@ public class PosGenerator { this.lookup = lookup; this.padding = padding; this.heightmap = heightmap; + this.size = size; this.quadSize = (size - (padding * 2)) / 4; int x1 = 0; int y1 = 0; @@ -130,6 +132,49 @@ public class PosGenerator { return null; } + public RiverNode nextRelaxed(int x, int z, Random random, int attempts) { + for (int i = 0; i < attempts; i++) { + int px = x + random.nextInt(size); + int pz = z + random.nextInt(size); + int wx = (int) domain.getX(px, pz); + int wz = (int) domain.getY(px, pz); + float value1 = getHeight(px, pz); + float value2 = getHeight(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 nextFromRelaxed(int x, int z, Random random, int attempts, RiverNode point, int mindDist2) { + for (int i = 0; i < attempts; i++) { + int px = x + random.nextInt(size); + int pz = z + random.nextInt(size); + 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 = getHeight(px, pz); + float value2 = getHeight(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); diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverContext.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverContext.java new file mode 100644 index 0000000..721787f --- /dev/null +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverContext.java @@ -0,0 +1,18 @@ +package com.terraforged.core.world.river; + +public class RiverContext { + + public final float frequency; + public final RiverConfig primary; + public final RiverConfig secondary; + public final RiverConfig tertiary; + public final LakeConfig lakes; + + public RiverContext(float frequency, RiverConfig primary, RiverConfig secondary, RiverConfig tertiary, LakeConfig lakes) { + this.frequency = frequency; + this.primary = primary; + this.secondary = secondary; + this.tertiary = tertiary; + this.lakes = lakes; + } +} diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java index f1f05bf..d982a39 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverManager.java @@ -39,18 +39,13 @@ public class RiverManager { private static final int QUAD_SIZE = (1 << RiverRegion.SCALE) / 2; - private final LakeConfig lakes; - private final RiverConfig primary; - private final RiverConfig secondary; - private final RiverConfig tertiary; private final Heightmap heightmap; private final GeneratorContext context; + private final RiverContext riverContext; private final Cache cache = new Cache<>(60, 60, TimeUnit.SECONDS, () -> new ConcurrentHashMap<>()); public RiverManager(Heightmap heightmap, GeneratorContext context) { - this.heightmap = heightmap; - this.context = context; - this.primary = RiverConfig.builder(context.levels) + RiverConfig primary = RiverConfig.builder(context.levels) .bankHeight(context.settings.rivers.primaryRivers.minBankHeight, context.settings.rivers.primaryRivers.maxBankHeight) .bankWidth(context.settings.rivers.primaryRivers.bankWidth) .bedWidth(context.settings.rivers.primaryRivers.bedWidth) @@ -59,7 +54,7 @@ public class RiverManager { .length(2500) .main(true) .build(); - this.secondary = RiverConfig.builder(context.levels) + RiverConfig secondary = RiverConfig.builder(context.levels) .bankHeight(context.settings.rivers.secondaryRiver.minBankHeight, context.settings.rivers.secondaryRiver.maxBankHeight) .bankWidth(context.settings.rivers.secondaryRiver.bankWidth) .bedWidth(context.settings.rivers.secondaryRiver.bedWidth) @@ -67,7 +62,7 @@ public class RiverManager { .fade(context.settings.rivers.secondaryRiver.fade) .length(1000) .build(); - this.tertiary = RiverConfig.builder(context.levels) + RiverConfig tertiary = RiverConfig.builder(context.levels) .bankHeight(context.settings.rivers.tertiaryRivers.minBankHeight, context.settings.rivers.tertiaryRivers.maxBankHeight) .bankWidth(context.settings.rivers.tertiaryRivers.bankWidth) .bedWidth(context.settings.rivers.tertiaryRivers.bedWidth) @@ -75,7 +70,11 @@ public class RiverManager { .fade(context.settings.rivers.tertiaryRivers.fade) .length(500) .build(); - this.lakes = LakeConfig.of(context.settings.rivers.lake, context.levels); + LakeConfig lakes = LakeConfig.of(context.settings.rivers.lake, context.levels); + + this.heightmap = heightmap; + this.context = context; + this.riverContext = new RiverContext(context.settings.rivers.riverFrequency, primary, secondary, tertiary, lakes); } public void apply(Cell cell, float x, float z) { @@ -103,7 +102,7 @@ public class RiverManager { long id = NoiseUtil.seed(rx, rz); RiverRegion region = cache.get(id); if (region == null) { - region = new RiverRegion(rx, rz, heightmap, context, primary, secondary, tertiary, lakes); + region = new RiverRegion(rx, rz, heightmap, context, riverContext); cache.put(id, region); } return region; diff --git a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java index 718c302..7f12cd0 100644 --- a/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java +++ b/TerraForgedCore/src/main/java/com/terraforged/core/world/river/RiverRegion.java @@ -57,15 +57,39 @@ public class RiverRegion { private final List rivers; private final List lakes = new LinkedList<>(); - public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverConfig primary, RiverConfig secondary, RiverConfig tertiary, LakeConfig lake) { + private final int primaryLimit; + private final int secondaryLimit; + private final int secondaryRelaxedLimit; + private final int forkLimit; + private final int tertiaryLimit; + private final int primaryAttempts; + private final int secondaryAttempts; + private final int secondaryRelaxedAttempts; + private final int forkAttempts; + private final int tertiaryAttempts; + + public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) { int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt(); - this.lake = lake; - this.primary = primary; - this.secondary = secondary; - this.tertiary = tertiary; + this.lake = riverContext.lakes; + this.primary = riverContext.primary; + this.secondary = riverContext.secondary; + this.tertiary = riverContext.tertiary; this.terrains = context.terrain; this.domain = Domain.warp(seed, 400, 1, 400) - .add(Domain.warp(seed + 1, 80, 1, 35));; + .add(Domain.warp(seed + 1, 80, 1, 35)); + + this.primaryLimit = NoiseUtil.round(10 * riverContext.frequency); + this.secondaryLimit = NoiseUtil.round(20 * riverContext.frequency); + this.secondaryRelaxedLimit = NoiseUtil.round(30 * riverContext.frequency); + this.forkLimit = NoiseUtil.round(40 * riverContext.frequency); + this.tertiaryLimit = NoiseUtil.round(50 * riverContext.frequency); + + this.primaryAttempts = NoiseUtil.round(100 * riverContext.frequency); + this.secondaryAttempts = NoiseUtil.round(100 * riverContext.frequency); + this.secondaryRelaxedAttempts = NoiseUtil.round(50 * riverContext.frequency); + this.forkAttempts = NoiseUtil.round(75 * riverContext.frequency); + this.tertiaryAttempts = NoiseUtil.round(50 * riverContext.frequency); + try (ObjectPool.Item> cell = Cell.pooled()) { PosGenerator pos = new PosGenerator(heightmap, domain, cell.getValue(),1 << SCALE, River.VALLEY_WIDTH); this.rivers = generate(regionX, regionZ, pos); @@ -91,19 +115,23 @@ public class RiverRegion { Random random = new Random(regionSeed); List rivers = new LinkedList<>(); - for (int i = 0; rivers.size() < 4 && i < 50; i++) { + for (int i = 0; rivers.size() < primaryLimit && i < primaryAttempts; i++) { generateRiver(x, z, pos, primary, random, rivers); } - for (int i = 0; rivers.size() < 15 && i < 100; i++) { + for (int i = 0; rivers.size() < secondaryLimit && i < secondaryAttempts; i++) { generateRiver(x, z, pos, secondary, random, rivers); } - for (int i = 0; rivers.size() < 25 && i < 75; i++) { + for (int i = 0; rivers.size() < secondaryRelaxedLimit && i < secondaryRelaxedAttempts; i++) { + generateRiverRelaxed(x, z, pos, secondary, random, rivers); + } + + for (int i = 0; rivers.size() < forkLimit && i < forkAttempts; i++) { generateRiverFork(x, z, pos, tertiary, random, rivers); } - for (int i = 0; rivers.size() < 40 && i < 50; i++) { + for (int i = 0; rivers.size() < tertiaryLimit && i < tertiaryAttempts; i++) { generateRiver(x, z, pos, tertiary, random, rivers); } @@ -218,6 +246,32 @@ public class RiverRegion { return rivers.add(new River(bounds, forkConfig, terrains, forkConfig.fade, 0, true)); } + private boolean generateRiverRelaxed(int x, int z, PosGenerator pos, RiverConfig config, Random random, List rivers) { + // generate either a river start or end node + RiverNode p1 = pos.nextRelaxed(x, z, random, 50); + if (p1 == null) { + return false; + } + + // generate a node with a min distance from p1 and that has the opposite node type to p1 + RiverNode p2 = pos.nextFromRelaxed(x, z, random,50, p1, config.length2); + if (p2 == null) { + return false; + } + + // avoid collisions with existing rivers + RiverBounds bounds = RiverBounds.fromNodes(p1, p2); + for (River river : rivers) { + if (bounds.overlaps(river.bounds)) { + return false; + } + } + + generateLake(bounds, random); + + return rivers.add(new River(bounds, config, terrains, config.fade, 0)); + } + private void generateLake(RiverBounds bounds, Random random) { if (random.nextFloat() < lake.chance) { float size = lake.sizeMin + (random.nextFloat() * lake.sizeMax); diff --git a/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java index 50d8c84..82305fe 100644 --- a/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java +++ b/TerraForgedMod/src/main/java/com/terraforged/mod/chunk/TerraChunkGenerator.java @@ -208,7 +208,7 @@ public class TerraChunkGenerator extends ObfHelperChunkGenerator