diff --git a/build.gradle b/build.gradle index 0550e6788..441a515d7 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ allprojects { apply plugin: "java" apply plugin: "application" - version = "2.3.0-dev1" + version = "2.3.0-dev2" sourceCompatibility = 8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' diff --git a/src/client/java/minicraft/core/Game.java b/src/client/java/minicraft/core/Game.java index 93c71eefe..f949b033a 100644 --- a/src/client/java/minicraft/core/Game.java +++ b/src/client/java/minicraft/core/Game.java @@ -25,7 +25,7 @@ protected Game() { public static final String NAME = "Minicraft Plus"; // This is the name on the application window. - public static final Version VERSION = new Version("2.3.0-dev1"); + public static final Version VERSION = new Version("2.3.0-dev2"); public static InputHandler input; // Input used in Game, Player, and just about all the *Menu classes. public static Player player; diff --git a/src/client/java/minicraft/core/Renderer.java b/src/client/java/minicraft/core/Renderer.java index 8d92bab9d..4c9df12fe 100644 --- a/src/client/java/minicraft/core/Renderer.java +++ b/src/client/java/minicraft/core/Renderer.java @@ -194,10 +194,10 @@ private static void renderLevel() { int yScroll = player.y - (Screen.h - 8) / 2; // Scrolls the screen in the y axis. // Stop scrolling if the screen is at the ... - if (xScroll < 0) xScroll = 0; // ...Left border. - if (yScroll < 0) yScroll = 0; // ...Top border. - if (xScroll > (level.w << 4) - Screen.w) xScroll = (level.w << 4) - Screen.w; // ...Right border. - if (yScroll > (level.h << 4) - Screen.h) yScroll = (level.h << 4) - Screen.h; // ...Bottom border. + // if (xScroll < 0) xScroll = 0; // ...Left border. + // if (yScroll < 0) yScroll = 0; // ...Top border. + // if (xScroll > (level.w << 4) - Screen.w) xScroll = (level.w << 4) - Screen.w; // ...Right border. + // if (yScroll > (level.h << 4) - Screen.h) yScroll = (level.h << 4) - Screen.h; // ...Bottom border. if (currentLevel > 3) { // If the current level is higher than 3 (which only the sky level (and dungeon) is) MinicraftImage cloud = spriteLinker.getSheet(SpriteType.Tile, "cloud_background"); for (int y = 0; y < 28; y++) diff --git a/src/client/java/minicraft/entity/mob/Player.java b/src/client/java/minicraft/entity/mob/Player.java index 385e78052..bbe292bc6 100644 --- a/src/client/java/minicraft/entity/mob/Player.java +++ b/src/client/java/minicraft/entity/mob/Player.java @@ -286,6 +286,9 @@ public void tick() { return; } + // Ensure chunks generated around player + level.loadChunksAround(x >> 4, y >> 4); + super.tick(); // Ticks Mob.java tickMultiplier(); @@ -652,7 +655,7 @@ protected void attack() { Point t = getInteractionTile(); // If the target coordinates are a valid tile. - if (t.x >= 0 && t.y >= 0 && t.x < level.w && t.y < level.h) { + // if (t.x >= 0 && t.y >= 0 && t.x < level.w && t.y < level.h) { // Get any entities (except dropped items and particles) on the tile. List tileEntities = level.getEntitiesInTiles(t.x, t.y, t.x, t.y, false, ItemEntity.class, Particle.class); @@ -679,7 +682,7 @@ protected void attack() { fishingTicks = maxFishingTicks; } } - } + // } if (done) return; // Skip the rest if interaction was handled } @@ -692,10 +695,10 @@ protected void attack() { Point t = getInteractionTile(); // Check if tile is in bounds of the map. - if (t.x >= 0 && t.y >= 0 && t.x < level.w && t.y < level.h) { + // if (t.x >= 0 && t.y >= 0 && t.x < level.w && t.y < level.h) { Tile tile = level.getTile(t.x, t.y); used = tile.hurt(level, t.x, t.y, this, random.nextInt(3) + 1, attackDir) || used; - } + // } if (used && activeItem instanceof ToolItem) ((ToolItem) activeItem).payDurability(); diff --git a/src/client/java/minicraft/level/ChunkManager.java b/src/client/java/minicraft/level/ChunkManager.java new file mode 100644 index 000000000..3f7812b7f --- /dev/null +++ b/src/client/java/minicraft/level/ChunkManager.java @@ -0,0 +1,113 @@ +package minicraft.level; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import minicraft.gfx.Point; +import minicraft.level.tile.Tile; +import minicraft.level.tile.Tiles; + +public class ChunkManager { + + public static final int CHUNK_SIZE = 64; + + public static final int CHUNK_STAGE_UNFINISHED_STAIRS = 3; + public static final int CHUNK_STAGE_DONE = 4; + + /** + * A data structure where + * [x][y] input points to CHUNK_SIZE x CHUNK_SIZE list of TileDat + */ + public Map> chunks; + + public ChunkManager() { + chunks = new HashMap<>(); + } + + public Set getAllChunks() { + HashSet out = new HashSet<>(); + for(int x : chunks.keySet()) + for(int y : chunks.get(x).keySet()) + out.add(new Point(x, y)); + return out; + } + + /** + * Return the chunk in which the tileX and tileY land + */ + private Chunk getChunk(int tileX, int tileY) { + int cX = Math.floorDiv(tileX, CHUNK_SIZE), cY = Math.floorDiv(tileY, CHUNK_SIZE); + // If [cX][cY] are not keys in chunks, put them there + if(!chunks.containsKey(cX)) + chunks.put(cX, new HashMap<>()); + if(!chunks.get(cX).containsKey(cY)) + chunks.get(cX).put(cY, new Chunk()); + return chunks.get(cX).get(cY); + } + + /** + * Returns a tile. After finding the right chunk, mods x and y to the range 0-CHUNK_SIZE as to never be out of bounds + */ + public Tile getTile(int x, int y) { + return Tiles.get(getChunk(x, y).getTileDat(x, y).id); + } + + /** + * Updates a tile. After finding the right chunk, mods x and y to the range 0-CHUNK_SIZE as to never be out of bounds + */ + public void setTile(int x, int y, Tile t, int dataVal) { + TileDat dat = getChunk(x, y).getTileDat(x, y); + dat.id = t.id; + dat.data = (short) dataVal; + } + + public int getData(int x, int y) { + return getChunk(x, y).getTileDat(x, y).data; + } + + public void setData(int x, int y, int val) { + getChunk(x, y).getTileDat(x, y).data = (short) val; + } + + public int getChunkStage(int chunkX, int chunkY) { + // If [cX][cY] are not keys in chunks, stage must be 0 + if(!chunks.containsKey(chunkX) || !chunks.get(chunkX).containsKey(chunkY)) + return 0; + return chunks.get(chunkX).get(chunkY).stage; + } + + public void setChunkStage(int chunkX, int chunkY, int stage) { + // If [chunkX][chunkY] are not keys in chunks, put them there + if(!chunks.containsKey(chunkX)) + chunks.put(chunkX, new HashMap<>()); + if(!chunks.get(chunkX).containsKey(chunkY)) + chunks.get(chunkX).put(chunkY, new Chunk()); + chunks.get(chunkX).get(chunkY).stage = (short)stage; + } + + private static class Chunk { + protected TileDat[] tiles; + protected short stage = 0; + public Chunk() { + tiles = new TileDat[CHUNK_SIZE * CHUNK_SIZE]; + } + + public TileDat getTileDat(int tileX, int tileY) { + int index = Math.floorMod(tileX, CHUNK_SIZE) + Math.floorMod(tileY, CHUNK_SIZE) * CHUNK_SIZE; + if(tiles[index] == null) + tiles[index] = new TileDat((short)0); + return tiles[index]; + } + } + + private static class TileDat { + protected short id, data; + + public TileDat(short id) { + this.id = id; + this.data = 0; + } + } +} diff --git a/src/client/java/minicraft/level/Level.java b/src/client/java/minicraft/level/Level.java index f2e34cc59..5ddf4dbd8 100644 --- a/src/client/java/minicraft/level/Level.java +++ b/src/client/java/minicraft/level/Level.java @@ -34,6 +34,7 @@ import minicraft.level.tile.Tiles; import minicraft.level.tile.TorchTile; import minicraft.level.tile.TreeTile; +import minicraft.level.tile.TreeTile.TreeType; import minicraft.util.Logging; import minicraft.util.MyUtils; @@ -67,8 +68,8 @@ public static String getDepthString(int depth) { public int w, h; // Width and height of the level private final long seed; // The used seed that was used to generate the world - public short[] tiles; // An array of all the tiles in the world. - public short[] data; // An array of the data of the tiles in the world. + public ChunkManager chunkManager; // A collection of chunks with it's own interface + private Level parentLevel = null; // reference to parent level public final TreeTile.TreeType[] treeTypes; // An array of tree types @@ -117,10 +118,11 @@ public void printLevelLoc(String prefix, int x, int y, String suffix) { } public void printTileLocs(Tile t) { - for (int x = 0; x < w; x++) - for (int y = 0; y < h; y++) - if (getTile(x, y).id == t.id) - printLevelLoc(t.name, x, y); + for(Point p : chunkManager.getAllChunks()) + for(int x = p.x * ChunkManager.CHUNK_SIZE; x < (p.x + 1) * ChunkManager.CHUNK_SIZE; x++) + for(int y = p.y * ChunkManager.CHUNK_SIZE; y < (p.y + 1) * ChunkManager.CHUNK_SIZE; y++) + if (getTile(x, y).id == t.id) + printLevelLoc(t.name, x, y); } public void printEntityLocs(Class c) { @@ -146,6 +148,7 @@ public Level(int w, int h, long seed, int level, Level parentLevel, boolean make this.w = w; this.h = h; this.seed = seed; + this.parentLevel = parentLevel; random = new Random(seed); short[][] maps; // Multidimensional array (an array within a array), used for the map @@ -186,24 +189,18 @@ public Level(int w, int h, long seed, int level, Level parentLevel, boolean make updateMobCap(); if (!makeWorld) { - int arrsize = w * h; - tiles = new short[arrsize]; - data = new short[arrsize]; + chunkManager = new ChunkManager(); return; } Logging.WORLD.debug("Making level " + level + "..."); - maps = LevelGen.createAndValidateMap(w, h, level, seed); - if (maps == null) { - Logging.WORLD.error("Level generation: Returned maps array is null"); + chunkManager = LevelGen.createAndValidateMap(w, h, level, seed); + if (chunkManager == null) { + Logging.WORLD.error("Level generation: Returned chunks array is null"); return; } - tiles = maps[0]; // Assigns the tiles in the map - data = maps[1]; // Assigns the data of the tiles - - if (level < 0) generateSpawnerStructures(); @@ -433,6 +430,43 @@ public void tick(boolean fullTick) { trySpawn(); } + public void loadChunksAround(int tileX, int tileY) { + // Update all chunks up to 3 chunks away from the player to make sure they are loaded + int cX = Math.floorDiv(tileX, ChunkManager.CHUNK_SIZE), cY = Math.floorDiv(tileY, ChunkManager.CHUNK_SIZE); + for(int x = cX - 3; x <= cX + 3; x++) + for(int y = cY - 3; y <= cY + 3; y++) + if(chunkManager.getChunkStage(x, y) != ChunkManager.CHUNK_STAGE_DONE) + loadChunk(x, y); + } + + public void loadChunk(int x, int y) { + if(chunkManager.getChunkStage(x, y) == ChunkManager.CHUNK_STAGE_UNFINISHED_STAIRS && parentLevel != null) { + if(parentLevel.chunkManager.getChunkStage(x, y) == 0) + LevelGen.generateChunk(parentLevel.chunkManager, x, y, parentLevel.depth, seed); + int S = ChunkManager.CHUNK_SIZE; + for(int i = x * S; i < x * S + S; i++) + for(int j = y * S; j < y * S + S; j++) { + if (parentLevel.getTile(i, j) == Tiles.get("Stairs Down")) { // If the tile in the level above the current one is a stairs down then... + if (depth == -4) { /// Make the obsidian wall formation around the stair in the dungeon level + Structure.dungeonGate.draw(this, i, j); // Te gate should not intersect with the boss room. + Structure.dungeonBossRoom.draw(this, w / 2, h / 2); // Generating the boss room at the center. + } else if (depth == 0) { // Surface + Logging.WORLD.trace("Setting tiles around " + i + "," + j + " to hard rock"); + setAreaTiles(i, j, 1, Tiles.get("Hard Rock"), 0); // surround the sky stairs with hard rock + } else // Any other level, the up-stairs should have dirt on all sides. + setAreaTiles(i, j, 1, Tiles.get("dirt"), 0); + + setTile(i, j, Tiles.get("Stairs Up")); // Set a stairs up tile in the same position on the current level + } else if (getTile(i, j) == Tiles.get("Stairs Up") && parentLevel.getTile(i, j) != Tiles.get("Stairs Down")) { + parentLevel.setTile(i, j, "Stairs Down"); + } + } + chunkManager.setChunkStage(x, y, ChunkManager.CHUNK_STAGE_DONE); + } + if(chunkManager.getChunkStage(x, y) == 0) + LevelGen.generateChunk(chunkManager, x, y, depth, seed); + } + public boolean entityNearPlayer(Entity entity) { for (Player player : players) { if (Math.abs(player.x - entity.x) < 128 && Math.abs(player.y - entity.y) < 76) { @@ -558,9 +592,16 @@ private void sortAndRender(Screen screen, List list) { } } + public TreeTile.TreeType getTreeType(int x, int y) { + LevelGen noise1 = new LevelGen(x, y, 1, 1, 32, -1); + LevelGen noise2 = new LevelGen(x, y, 1, 1, 32, -2); + + int idx = (int)Math.round(Math.abs(noise1.values[0] - noise2.values[0]) * 3 - 2); + return (idx >= TreeType.values().length || idx < 0) ? TreeType.OAK : TreeType.values()[idx]; + } + public Tile getTile(int x, int y) { - if (x < 0 || y < 0 || x >= w || y >= h /* || (x + y * w) >= tiles.length*/) return Tiles.get("connector tile"); - return Tiles.get(tiles[x + y * w]); + return chunkManager.getTile(x, y); } /** @@ -582,21 +623,16 @@ public void setTile(int x, int y, Tile t) { } public void setTile(int x, int y, Tile t, int dataVal) { - if (x < 0 || y < 0 || x >= w || y >= h) return; - - tiles[x + y * w] = t.id; - data[x + y * w] = (short) dataVal; - t.onTileSet(this, x, y); + chunkManager.setTile(x, y, t, dataVal); + getTile(x, y).onTileSet(this, x, y); } public int getData(int x, int y) { - if (x < 0 || y < 0 || x >= w || y >= h) return 0; - return data[x + y * w] & 0xFFFF; + return chunkManager.getData(x, y); } public void setData(int x, int y, int val) { - if (x < 0 || y < 0 || x >= w || y >= h) return; - data[x + y * w] = (short) val; + chunkManager.setData(x, y, val); } public void add(Entity e) { @@ -641,9 +677,8 @@ private void trySpawn() { int lvl = -MyUtils.clamp(player.getLevel().depth, -4, 0); for (int i = 0; i < 30 && !spawned; i++) { int rnd = random.nextInt(100); - int nx = random.nextInt(w) * 16 + 8, ny = random.nextInt(h) * 16 + 8; - double distance = Math.hypot(Math.abs(nx - player.x), Math.abs(ny - player.y)); - if (distance < 160) continue; // Spawns only far from 10 tiles away. + int nx = (random.nextInt(ChunkManager.CHUNK_SIZE*2) - ChunkManager.CHUNK_SIZE) * 16 + player.x, + ny = (random.nextInt(ChunkManager.CHUNK_SIZE*2) - ChunkManager.CHUNK_SIZE) * 16 + player.y; //System.out.println("trySpawn on level " + depth + " of lvl " + lvl + " mob w/ rand " + rnd + " at tile " + nx + "," + ny); @@ -674,21 +709,21 @@ private void trySpawn() { if (rnd <= (Updater.getTime() == Updater.Time.Night ? 22 : 33)) add((new Cow()), nx, ny); else if (rnd >= 68) add((new Pig()), nx, ny); else { // Sheep spawning - double colorRnd = random.nextDouble(); - if (colorRnd < 0.8) { // 80% for default color, i.e. white - add((new Sheep()), nx, ny); - } else if (colorRnd < 0.85) { // 5% for black - add((new Sheep(DyeItem.DyeColor.BLACK)), nx, ny); - } else if (colorRnd < 0.9) { // 5% for gray - add((new Sheep(DyeItem.DyeColor.GRAY)), nx, ny); - } else if (colorRnd < 0.95) { // 5% for light gray - add((new Sheep(DyeItem.DyeColor.LIGHT_GRAY)), nx, ny); - } else if (colorRnd < 0.98) { // 3% for brown - add((new Sheep(DyeItem.DyeColor.BROWN)), nx, ny); - } else { // 2% for pink - add((new Sheep(DyeItem.DyeColor.PINK)), nx, ny); + double colorRnd = random.nextDouble(); + if (colorRnd < 0.8) { // 80% for default color, i.e. white + add((new Sheep()), nx, ny); + } else if (colorRnd < 0.85) { // 5% for black + add((new Sheep(DyeItem.DyeColor.BLACK)), nx, ny); + } else if (colorRnd < 0.9) { // 5% for gray + add((new Sheep(DyeItem.DyeColor.GRAY)), nx, ny); + } else if (colorRnd < 0.95) { // 5% for light gray + add((new Sheep(DyeItem.DyeColor.LIGHT_GRAY)), nx, ny); + } else if (colorRnd < 0.98) { // 3% for brown + add((new Sheep(DyeItem.DyeColor.BROWN)), nx, ny); + } else { // 2% for pink + add((new Sheep(DyeItem.DyeColor.PINK)), nx, ny); + } } - } spawned = true; } @@ -953,10 +988,11 @@ public List getMatchingTiles(Tile... search) { public List getMatchingTiles(TileCheck condition) { List matches = new ArrayList<>(); - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - if (condition.check(getTile(x, y), x, y)) - matches.add(new Point(x, y)); + for(Point p : chunkManager.getAllChunks()) + for(int x = p.x * ChunkManager.CHUNK_SIZE; x < (p.x + 1) * ChunkManager.CHUNK_SIZE; x++) + for(int y = p.y * ChunkManager.CHUNK_SIZE; y < (p.y + 1) * ChunkManager.CHUNK_SIZE; y++) + if (condition.check(getTile(x, y), x, y)) + matches.add(new Point(x, y)); return matches; } @@ -1193,7 +1229,7 @@ private void generateDungeonStructures() { */ public void regenerateBossRoom() { if (depth == -4) { - Structure.dungeonBossRoom.draw(tiles, w / 2, h / 2, w); // Generating the boss room at the center. + Structure.dungeonBossRoom.draw(chunkManager, w / 2, h / 2); // Generating the boss room at the center. for (int x = w / 2 - 4; x < w / 2 + 5; x++) { // Resetting tile data. for (int y = h / 2 - 4; y < h / 2 + 5; y++) { setData(x, y, 0); diff --git a/src/client/java/minicraft/level/LevelGen.java b/src/client/java/minicraft/level/LevelGen.java index 17aba4fd1..c06c1b321 100644 --- a/src/client/java/minicraft/level/LevelGen.java +++ b/src/client/java/minicraft/level/LevelGen.java @@ -2,98 +2,60 @@ import minicraft.core.Game; import minicraft.core.io.Settings; +import minicraft.gfx.Point; import minicraft.gfx.Rectangle; -import minicraft.level.tile.FlowerTile; import minicraft.level.tile.Tiles; import minicraft.screen.RelPos; -import org.jetbrains.annotations.Nullable; +import minicraft.util.Logging; +import minicraft.util.Simplex; + import org.tinylog.Logger; +import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JOptionPane; +import java.awt.Color; +import java.awt.Graphics; import java.awt.Image; import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Random; +import java.util.Set; public class LevelGen { private static long worldSeed = 0; private static final Random random = new Random(worldSeed); + private static final Simplex noise = new Simplex(worldSeed); public double[] values; // An array of doubles, used to help making noise for the map private final int w, h; // Width and height of the map private static final int stairRadius = 15; + private static final int NOISE_LAYER_DIFF = 100; + /** * This creates noise to create random values for level generation */ - public LevelGen(int w, int h, int featureSize) { + public LevelGen(int w, int h, int featureSize) { this(0, 0, w, h, featureSize, 0); } + + public LevelGen(int w, int h, int featureSize, int layer) { this(0, 0, w, h, featureSize, layer); } + + public LevelGen(int xOffset, int yOffset, int w, int h, int featureSize) { this(xOffset, yOffset, w, h, featureSize, 0); } + + public LevelGen(int xOffset, int yOffset, int w, int h, int featureSize, int layer) { this.w = w; this.h = h; values = new double[w * h]; - /// Feature size likely determines how big the biomes are, in some way. It tends to be 16 or 32, in the code below. - for (int y = 0; y < w; y += featureSize) { - for (int x = 0; x < w; x += featureSize) { - setSample(x, y, random.nextFloat() * 2 - 1); // This method sets the random value from -1 to 1 at the given coordinate. + for(int x = 0; x < w; x++) + for(int y = 0; y < h; y++) { + setSample(x, y, noise.noise3((x + xOffset) / (float)featureSize, (y + yOffset) / (float)featureSize, layer * NOISE_LAYER_DIFF)); } - } - - int stepSize = featureSize; - double scale = 2.0 / w; - double scaleMod = 1; - do { - int halfStep = stepSize / 2; - for (int y = 0; y < h; y += stepSize) { - for (int x = 0; x < w; x += stepSize) { // This loops through the values again, by a given increment... - double a = sample(x, y); // Fetches the value at the coordinate set previously (it fetches the exact same ones that were just set above) - double b = sample(x + stepSize, y); // Fetches the value at the next coordinate over. This could possibly loop over at the end, and fetch the first value in the row instead. - double c = sample(x, y + stepSize); // Fetches the next value down, possibly looping back to the top of the column. - double d = sample(x + stepSize, y + stepSize); // Fetches the value one down, one right. - - /* - * This could probably use some explaining... Note: the number values are probably only good the first time around... - * - * This starts with taking the average of the four numbers from before (they form a little square in adjacent tiles), each of which holds a value from -1 to 1. - * Then, it basically adds a 5th number, generated the same way as before. However, this 5th number is multiplied by a few things first... - * ...by stepSize, aka featureSize, and scale, which is 2/size the first time. featureSize is 16 or 32, which is a multiple of the common level size, 128. - * Precisely, it is 128 / 8, or 128 / 4, respectively with 16 and 32. So, the equation becomes size / const * 2 / size, or, simplified, 2 / const. - * For a feature size of 32, stepSize * scale = 2 / 4 = 1/2. featureSize of 16, it's 2 / 8 = 1/4. Later on, this gets closer to 4 / 4 = 1, so... the 5th value may not change much at all in later iterations for a feature size of 32, which means it has an effect of 1, which is actually quite significant to the value that is set. - * So, it tends to decrease the 5th -1 or 1 number, sometimes making it of equal value to the other 4 numbers, sort of. It will usually change the end result by 0.5 to 0.25, perhaps; at max. - */ - double e = (a + b + c + d) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale; - setSample(x + halfStep, y + halfStep, e); // This sets the value that is right in the middle of the other 4 to an average of the four, plus a 5th number, which makes it slightly off, differing by about 0.25 or so on average, the first time around. - } - } - - // This loop does the same as before, but it takes into account some of the half steps we set in the last loop. - for (int y = 0; y < h; y += stepSize) { - for (int x = 0; x < w; x += stepSize) { - double a = sample(x, y); // middle (current) tile - double b = sample(x + stepSize, y); // right tile - double c = sample(x, y + stepSize); // bottom tile - double d = sample(x + halfStep, y + halfStep); // mid-right, mid-bottom tile - double e = sample(x + halfStep, y - halfStep); // mid-right, mid-top tile - double f = sample(x - halfStep, y + halfStep); // mid-left, mid-bottom tile - - // The 0.5 at the end is because we are going by half-steps..? - // The H is for the right and surrounding mids, and g is the bottom and surrounding mids. - double H = (a + b + d + e) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5; // Adds middle, right, mr-mb, mr-mt, and random. - double g = (a + c + d + f) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5; // Adds middle, bottom, mr-mb, ml-mb, and random. - setSample(x + halfStep, y, H); // Sets the H to the mid-right - setSample(x, y + halfStep, g); // Sets the g to the mid-bottom - } - } - - /* - * THEN... this stuff is set to repeat the system all over again! - * The featureSize is halved, allowing access to further unset mids, and the scale changes... - * The scale increases the first time, x1.8, but the second time it's x1.1, and after that probably a little less than 1. So, it generally increases a bit, maybe to 4 / w at tops. This results in the 5th random value being more significant than the first 4 ones in later iterations. - */ - stepSize /= 2; - scale *= (scaleMod + 0.8); - scaleMod *= 0.3; - } while (stepSize > 1); // This stops when the stepsize is < 1, aka 0 b/c it's an int. At this point there are no more mid values. } private double sample(int x, int y) { @@ -117,150 +79,72 @@ private void setSample(int x, int y, double value) { values[(x & (w - 1)) + (y & (h - 1)) * w] = value; } - static short[] @Nullable [] createAndValidateMap(int w, int h, int level, long seed) { + static void generateChunk(ChunkManager chunkManager, int x, int y, int level, long seed) { worldSeed = seed; - if (level == 1) - return createAndValidateSkyMap(w, h); - if (level == 0) - return createAndValidateTopMap(w, h); - if (level == -4) - return createAndValidateDungeon(w, h); - if (level > -4 && level < 0) - return createAndValidateUndergroundMap(w, h, -level); - - Logger.tag("LevelGen").error("Level index is not valid. Could not generate a level."); - - return null; + if(level == 1) + generateSkyChunk(chunkManager, x, y); + else if(level == 0) + generateTopChunk(chunkManager, x, y); + else if(level == -4) + generateDungeonChunk(chunkManager, x, y); + else if(level > -4 && level < 0) + generateUndergroundChunk(chunkManager, x, y, -level); + else + Logger.tag("LevelGen").error("Level index is not valid. Could not generate a chunk at " + x + ", " + y + " on level " + level + " with seed " + seed); + + chunkManager.setChunkStage(x, y, ChunkManager.CHUNK_STAGE_UNFINISHED_STAIRS); } - private static short[][] createAndValidateTopMap(int w, int h) { - random.setSeed(worldSeed); - do { - short[][] result = createTopMap(w, h); - - int[] count = new int[256]; - - for (int i = 0; i < w * h; i++) { - count[result[0][i] & 0xffff]++; - } - if (count[Tiles.get("rock").id & 0xffff] < 100) continue; - if (count[Tiles.get("sand").id & 0xffff] < 100) continue; - if (count[Tiles.get("grass").id & 0xffff] < 100) continue; - if (count[Tiles.get("tree").id & 0xffff] < 100) continue; - - if (count[Tiles.get("Stairs Down").id & 0xffff] < w / 21) - continue; // Size 128 = 6 stairs min - - return result; - - } while (true); - } - - private static short[] @Nullable [] createAndValidateUndergroundMap(int w, int h, int depth) { - random.setSeed(worldSeed); - do { - short[][] result = createUndergroundMap(w, h, depth); - - int[] count = new int[256]; - - for (int i = 0; i < w * h; i++) { - count[result[0][i] & 0xffff]++; - } - if (count[Tiles.get("rock").id & 0xffff] < 100) continue; - if (count[Tiles.get("dirt").id & 0xffff] < 100) continue; - if (count[(Tiles.get("iron Ore").id & 0xffff) + depth - 1] < 20) continue; - - if (depth < 3 && count[Tiles.get("Stairs Down").id & 0xffff] < w / 32) - continue; // Size 128 = 4 stairs min - - return result; - - } while (true); - } - - private static short[][] createAndValidateDungeon(int w, int h) { - random.setSeed(worldSeed); - - do { - short[][] result = createDungeon(w, h); - - int[] count = new int[256]; - - for (int i = 0; i < w * h; i++) { - count[result[0][i] & 0xffff]++; - } - if (count[Tiles.get("Obsidian").id & 0xffff] + count[Tiles.get("dirt").id & 0xffff] < 100) continue; - if (count[Tiles.get("Obsidian Wall").id & 0xffff] < 100) continue; - - return result; - - } while (true); + static ChunkManager createAndValidateMap(int w, int h, int level, long seed) { + ChunkManager cm = new ChunkManager(); + for(int i = 0; i < w / ChunkManager.CHUNK_SIZE; i++) + for(int j = 0; j < h / ChunkManager.CHUNK_SIZE; j++) + generateChunk(cm, i, j, level, seed); + return cm; } - private static short[] @Nullable [] createAndValidateSkyMap(int w, int h) { + private static void generateTopChunk(ChunkManager map, int chunkX, int chunkY) { random.setSeed(worldSeed); + noise.setSeed(worldSeed); - do { - short[][] result = createSkyMap(w, h); - - int[] count = new int[256]; - - for (int i = 0; i < w * h; i++) { - count[result[0][i] & 0xffff]++; - } - if (count[Tiles.get("cloud").id & 0xffff] < 2000) continue; - if (count[Tiles.get("Stairs Down").id & 0xffff] < w / 64) - continue; // size 128 = 2 stairs min - - return result; + // Brevity + int S = ChunkManager.CHUNK_SIZE; - } while (true); - } - - private static short[][] createTopMap(int w, int h) { // Create surface map // creates a bunch of value maps, some with small size... - LevelGen mnoise1 = new LevelGen(w, h, 16); - LevelGen mnoise2 = new LevelGen(w, h, 16); - LevelGen mnoise3 = new LevelGen(w, h, 16); + LevelGen mnoise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, 0); + LevelGen mnoise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, 1); + LevelGen mnoise3 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, 2); // ...and some with larger size. - LevelGen noise1 = new LevelGen(w, h, 32); - LevelGen noise2 = new LevelGen(w, h, 32); - - short[] map = new short[w * h]; - short[] data = new short[w * h]; + LevelGen noise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 32, 3); + LevelGen noise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 32, 4); - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int i = x + y * w; + List rocks = new ArrayList<>(); - double val = Math.abs(noise1.values[i] - noise2.values[i]) * 3 - 2; + int tileX = chunkX * S, tileY = chunkY * S; + for(int y = tileY; y < tileY + S; y++) + for(int x = tileX; x < tileX + S; x++) { + int i = (x - tileX) + (y - tileY) * S; + double val = Math.abs(noise1.values[i] - noise2.values[i]) * 3 - 1; double mval = Math.abs(mnoise1.values[i] - mnoise2.values[i]); mval = Math.abs(mval - mnoise3.values[i]) * 3 - 2; // This calculates a sort of distance based on the current coordinate. - double xd = x / (w - 1.0) * 2 - 1; - double yd = y / (h - 1.0) * 2 - 1; - if (xd < 0) xd = -xd; - if (yd < 0) yd = -yd; - double dist = Math.max(xd, yd); - dist = dist * dist * dist * dist; - dist = dist * dist * dist * dist; - val += 1 - dist * 20; switch ((String) Settings.get("Type")) { case "minicraft.settings.type.island": if (val < -0.5) { if (Settings.get("Theme").equals("minicraft.settings.theme.hell")) - map[i] = Tiles.get("lava").id; + map.setTile(x, y, Tiles.get("lava"), 0); else - map[i] = Tiles.get("water").id; + map.setTile(x, y, Tiles.get("water"), 0); } else if (val > 0.5 && mval < -1.5) { - map[i] = Tiles.get("rock").id; + rocks.add(new Point(x,y)); + map.setTile(x, y, Tiles.get("rock"), 0); } else { - map[i] = Tiles.get("grass").id; + map.setTile(x, y, Tiles.get("grass"), 0); } break; @@ -268,55 +152,57 @@ private static short[][] createTopMap(int w, int h) { // Create surface map if (val < -1.5) { if (Settings.get("Theme").equals("minicraft.settings.theme.hell")) { - map[i] = Tiles.get("lava").id; + map.setTile(x, y, Tiles.get("lava"), 0); } else { - map[i] = Tiles.get("water").id; + map.setTile(x, y, Tiles.get("water"), 0); } } else if (val > 0.5 && mval < -1.5) { - map[i] = Tiles.get("rock").id; + rocks.add(new Point(x,y)); + map.setTile(x, y, Tiles.get("rock"), 0); } else { - map[i] = Tiles.get("grass").id; + map.setTile(x, y, Tiles.get("grass"), 0); } break; case "minicraft.settings.type.mountain": if (val < -0.4) { - map[i] = Tiles.get("grass").id; + map.setTile(x, y, Tiles.get("grass"), 0); } else if (val > 0.5 && mval < -1.5) { if (Settings.get("Theme").equals("minicraft.settings.theme.hell")) { - map[i] = Tiles.get("lava").id; + map.setTile(x, y, Tiles.get("lava"), 0); } else { - map[i] = Tiles.get("water").id; + map.setTile(x, y, Tiles.get("water"), 0); } } else { - map[i] = Tiles.get("rock").id; + rocks.add(new Point(x,y)); + map.setTile(x, y, Tiles.get("rock"), 0); } break; case "minicraft.settings.type.irregular": if (val < -0.5 && mval < -0.5) { if (Settings.get("Theme").equals("minicraft.settings.theme.hell")) { - map[i] = Tiles.get("lava").id; + map.setTile(x, y, Tiles.get("lava"), 0); } if (!Settings.get("Theme").equals("minicraft.settings.theme.hell")) { - map[i] = Tiles.get("water").id; + map.setTile(x, y, Tiles.get("water"), 0); } } else if (val > 0.5 && mval < -1.5) { - map[i] = Tiles.get("rock").id; + rocks.add(new Point(x,y)); + map.setTile(x, y, Tiles.get("rock"), 0); } else { - map[i] = Tiles.get("grass").id; + map.setTile(x, y, Tiles.get("grass"), 0); } break; } + } - } if (Settings.get("Theme").equals("minicraft.settings.theme.desert")) { - - for (int i = 0; i < w * h / 200; i++) { - int xs = random.nextInt(w); - int ys = random.nextInt(h); + for (int i = 0; i < S * S / 200; i++) { + int xs = random.nextInt(S); + int ys = random.nextInt(S); for (int k = 0; k < 10; k++) { int x = xs + random.nextInt(21) - 10; int y = ys + random.nextInt(21) - 10; @@ -325,9 +211,9 @@ private static short[][] createTopMap(int w, int h) { // Create surface map int yo = y + random.nextInt(5) - random.nextInt(5); for (int yy = yo - 1; yy <= yo + 1; yy++) for (int xx = xo - 1; xx <= xo + 1; xx++) - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("sand").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("sand"), 0); } } } @@ -336,10 +222,9 @@ private static short[][] createTopMap(int w, int h) { // Create surface map } if (!Settings.get("Theme").equals("minicraft.settings.theme.desert")) { - - for (int i = 0; i < w * h / 2800; i++) { - int xs = random.nextInt(w); - int ys = random.nextInt(h); + for (int i = 0; i < S * S / 2800; i++) { + int xs = random.nextInt(S); + int ys = random.nextInt(S); for (int k = 0; k < 10; k++) { int x = xs + random.nextInt(21) - 10; int y = ys + random.nextInt(21) - 10; @@ -348,9 +233,9 @@ private static short[][] createTopMap(int w, int h) { // Create surface map int yo = y + random.nextInt(5) - random.nextInt(5); for (int yy = yo - 1; yy <= yo + 1; yy++) for (int xx = xo - 1; xx <= xo + 1; xx++) - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("sand").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("sand"), 0); } } } @@ -359,30 +244,30 @@ private static short[][] createTopMap(int w, int h) { // Create surface map } if (Settings.get("Theme").equals("minicraft.settings.theme.forest")) { - for (int i = 0; i < w * h / 200; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 200; i++) { + int x = random.nextInt(S); + int y = random.nextInt(S); for (int j = 0; j < 200; j++) { int xx = x + random.nextInt(15) - random.nextInt(15); int yy = y + random.nextInt(15) - random.nextInt(15); - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("tree").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("tree"), 0); } } } } } if (!Settings.get("Theme").equals("minicraft.settings.theme.forest") && !Settings.get("Theme").equals("minicraft.settings.theme.plain")) { - for (int i = 0; i < w * h / 1200; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 1200; i++) { + int x = random.nextInt(S); + int y = random.nextInt(S); for (int j = 0; j < 200; j++) { int xx = x + random.nextInt(15) - random.nextInt(15); int yy = y + random.nextInt(15) - random.nextInt(15); - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("tree").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("tree"), 0); } } } @@ -390,186 +275,161 @@ private static short[][] createTopMap(int w, int h) { // Create surface map } if (Settings.get("Theme").equals("minicraft.settings.theme.plain")) { - for (int i = 0; i < w * h / 2800; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 2800; i++) { + int x = random.nextInt(S); + int y = random.nextInt(S); for (int j = 0; j < 200; j++) { int xx = x + random.nextInt(15) - random.nextInt(15); int yy = y + random.nextInt(15) - random.nextInt(15); - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("tree").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("tree"), 0); } } } } } if (!Settings.get("Theme").equals("minicraft.settings.theme.plain")) { - for (int i = 0; i < w * h / 400; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 400; i++) { + int x = random.nextInt(S); + int y = random.nextInt(S); for (int j = 0; j < 200; j++) { int xx = x + random.nextInt(15) - random.nextInt(15); int yy = y + random.nextInt(15) - random.nextInt(15); - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("tree").id; + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("tree"), 0); } } } } } - for (int i = 0; i < w * h / 400; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 400; i++) { + int x = random.nextInt(S); + int y = random.nextInt(S); int col = random.nextInt(4) * random.nextInt(4); for (int j = 0; j < 30; j++) { int xx = x + random.nextInt(5) - random.nextInt(5); int yy = y + random.nextInt(5) - random.nextInt(5); - if (xx >= 0 && yy >= 0 && xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("grass").id) { - map[xx + yy * w] = Tiles.get("flower").id; - data[xx + yy * w] = (short) (col + random.nextInt(3)); // Data determines what the flower is + if (xx >= 0 && yy >= 0 && xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("grass")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("flower"), col + random.nextInt(3)); // Data determines what the flower is } } } } - for (int i = 0; i < w * h / 100; i++) { - int xx = random.nextInt(w); - int yy = random.nextInt(h); - if (xx < w && yy < h) { - if (map[xx + yy * w] == Tiles.get("sand").id) { - map[xx + yy * w] = Tiles.get("cactus").id; + for (int i = 0; i < S * S / 100; i++) { + int xx = random.nextInt(S); + int yy = random.nextInt(S); + if (xx < S && yy < S) { + if (map.getTile(xx + tileX, yy + tileY) == Tiles.get("sand")) { + map.setTile(xx + tileX, yy + tileY, Tiles.get("cactus"), 0); } } } int count = 0; - //if (Game.debug) System.out.println("Generating stairs for surface level..."); - stairsLoop: - for (int i = 0; i < w * h / 100; i++) { // Loops a certain number of times, more for bigger world sizes. - int x = random.nextInt(w - 2) + 1; - int y = random.nextInt(h - 2) + 1; + while(!rocks.isEmpty() && count < S / 21) { // Loops a certain number of times, more for bigger world sizes. + int i = random.nextInt(rocks.size()); + + Point p = rocks.get(i); + rocks.remove(i); // The first loop, which checks to make sure that a new stairs tile will be completely surrounded by rock. - for (int yy = y - 1; yy <= y + 1; yy++) - for (int xx = x - 1; xx <= x + 1; xx++) - if (map[xx + yy * w] != Tiles.get("rock").id) + for (int yy = p.y - 1; yy <= p.y + 1; yy++) + for (int xx = p.x - 1; xx <= p.x + 1; xx++) + if (map.getTile(xx, yy) != Tiles.get("rock")) continue stairsLoop; // This should prevent any stairsDown tile from being within 30 tiles of any other stairsDown tile. - for (int yy = Math.max(0, y - stairRadius); yy <= Math.min(h - 1, y + stairRadius); yy++) - for (int xx = Math.max(0, x - stairRadius); xx <= Math.min(w - 1, x + stairRadius); xx++) - if (map[xx + yy * w] == Tiles.get("Stairs Down").id) + for (int yy = p.y - stairRadius; yy <= p.y + stairRadius; yy++) + for (int xx = p.x - stairRadius; xx <= p.x + stairRadius; xx++) + if (map.getTile(xx, yy) == Tiles.get("Stairs Down")) continue stairsLoop; - map[x + y * w] = Tiles.get("Stairs Down").id; - + map.setTile(p.x, p.y, Tiles.get("Stairs Down"), 0); count++; - if (count >= w / 21) break; } - - //System.out.println("min="+min); - //System.out.println("max="+max); - //average /= w*h; - //System.out.println(average); - - return new short[][] { map, data }; } - private static short[][] createDungeon(int w, int h) { - LevelGen noise1 = new LevelGen(w, h, 10); - LevelGen noise2 = new LevelGen(w, h, 10); + private static void generateDungeonChunk(ChunkManager map, int chunkX, int chunkY) { + int S = ChunkManager.CHUNK_SIZE; + LevelGen noise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 10, 0); + LevelGen noise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 10, 1); - short[] map = new short[w * h]; - short[] data = new short[w * h]; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int i = x + y * w; + int tileX = chunkX * S, tileY = chunkY * S; + for(int y = tileY; y < tileY + S; y++) { + for(int x = tileX; x < tileX + S; x++) { + int i = (x - tileX) + (y - tileY) * S; - double val = Math.abs(noise1.values[i] - noise2.values[i]) * 3 - 2; - - double xd = x / (w - 1.1) * 2 - 1; - double yd = y / (h - 1.1) * 2 - 1; - if (xd < 0) xd = -xd; - if (yd < 0) yd = -yd; - double dist = Math.max(xd, yd); - dist = dist * dist * dist * dist; - dist = dist * dist * dist * dist; - val = -val * 1 - 2.2; - val += 1 - dist * 2; + double val = Math.abs(noise1.values[i] - noise2.values[i]) * -3 + 3.5; if (val < -0.05) { - map[i] = Tiles.get("Obsidian Wall").id; + map.setTile(x, y, Tiles.get("Obsidian Wall"), 0); } else if (val >= -0.05 && val < -0.03) { - map[i] = Tiles.get("Lava").id; + map.setTile(x, y, Tiles.get("Lava"), 0); } else { if (random.nextInt(2) == 1) { if (random.nextInt(2) == 1) { - map[i] = Tiles.get("Obsidian").id; + map.setTile(x, y, Tiles.get("Obsidian"), 0); } else { - map[i] = Tiles.get("Raw Obsidian").id; + map.setTile(x, y, Tiles.get("Raw Obsidian"), 0); } } else { - map[i] = Tiles.get("dirt").id; + map.setTile(x, y, Tiles.get("dirt"), 0); } } } } decorLoop: - for (int i = 0; i < w * h / 450; i++) { - int x = random.nextInt(w - 2) + 1; - int y = random.nextInt(h - 2) + 1; + for (int i = 0; i < S * S / 450; i++) { + int x = random.nextInt(S - 2) + 1; + int y = random.nextInt(S - 2) + 1; for (int yy = y - 1; yy <= y + 1; yy++) { for (int xx = x - 1; xx <= x + 1; xx++) { - if (map[xx + yy * w] != Tiles.get("Obsidian").id) + if (map.getTile(xx, yy) != Tiles.get("Obsidian")) continue decorLoop; } } if (x > 8 && y > 8) { - if (x < w - 8 && y < w - 8) { + if (x < S - 8 && y < S - 8) { if (random.nextInt(2) == 0) - Structure.ornateLavaPool.draw(map, x, y, w); + Structure.ornateLavaPool.draw(map, x, y); } } } - - return new short[][] { map, data }; } + private static void generateUndergroundChunk(ChunkManager map, int chunkX, int chunkY, int depth) { + random.setSeed(worldSeed); + noise.setSeed(worldSeed); + int S = ChunkManager.CHUNK_SIZE; + LevelGen mnoise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 0); + LevelGen mnoise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 1); + LevelGen mnoise3 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 2); - private static short[][] createUndergroundMap(int w, int h, int depth) { - LevelGen mnoise1 = new LevelGen(w, h, 16); - LevelGen mnoise2 = new LevelGen(w, h, 16); - LevelGen mnoise3 = new LevelGen(w, h, 16); - - LevelGen nnoise1 = new LevelGen(w, h, 16); - LevelGen nnoise2 = new LevelGen(w, h, 16); - LevelGen nnoise3 = new LevelGen(w, h, 16); - - LevelGen wnoise1 = new LevelGen(w, h, 16); - LevelGen wnoise2 = new LevelGen(w, h, 16); - LevelGen wnoise3 = new LevelGen(w, h, 16); + LevelGen nnoise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 3); + LevelGen nnoise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 4); + LevelGen nnoise3 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 5); - LevelGen noise1 = new LevelGen(w, h, 32); - LevelGen noise2 = new LevelGen(w, h, 32); + LevelGen wnoise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 6); + LevelGen wnoise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 7); + LevelGen wnoise3 = new LevelGen(chunkX * S, chunkY * S, S, S, 16, depth * 11 + 8); - short[] map = new short[w * h]; - short[] data = new short[w * h]; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int i = x + y * w; + LevelGen noise1 = new LevelGen(chunkX * S, chunkY * S, S, S, 32, depth * 11 + 9); + LevelGen noise2 = new LevelGen(chunkX * S, chunkY * S, S, S, 32, depth * 11 + 10); - /// for the x=0 or y=0 i's, values[i] is always between -1 and 1. - /// so, val is between -2 and 4. - /// the rest are between -2 and 7. + int tileX = chunkX * S, tileY = chunkY * S; + for(int y = tileY; y < tileY + S; y++) { + for(int x = tileX; x < tileX + S; x++) { + int i = (x - tileX) + (y - tileY) * S; double val = Math.abs(noise1.values[i] - noise2.values[i]) * 3 - 2; @@ -582,172 +442,128 @@ private static short[][] createUndergroundMap(int w, int h, int depth) { double wval = Math.abs(wnoise1.values[i] - wnoise2.values[i]); wval = Math.abs(wval - wnoise3.values[i]) * 3 - 2; - double xd = x / (w - 1.0) * 2 - 1; - double yd = y / (h - 1.0) * 2 - 1; - if (xd < 0) xd = -xd; - if (yd < 0) yd = -yd; - double dist = Math.max(xd, yd); - dist = Math.pow(dist, 8); - val += 1 - dist * 20; - if (val > -1 && wval < -1 + (depth) / 2.0 * 3) { - if (depth == 3) map[i] = Tiles.get("lava").id; - else if (depth == 1) map[i] = Tiles.get("dirt").id; - else map[i] = Tiles.get("water").id; + if (depth == 3) map.setTile(x, y, Tiles.get("lava"), 0); + else if (depth == 1) map.setTile(x, y, Tiles.get("dirt"), 0); + else map.setTile(x, y, Tiles.get("water"), 0); } else if (val > -2 && (mval < -1.7 || nval < -1.4)) { - map[i] = Tiles.get("dirt").id; + map.setTile(x, y, Tiles.get("dirt"), 0); } else { - map[i] = Tiles.get("rock").id; - + map.setTile(x, y, Tiles.get("rock"), 0); } } } { int r = 2; - for (int i = 0; i < w * h / 400; i++) { - int x = random.nextInt(w); - int y = random.nextInt(h); + for (int i = 0; i < S * S / 200; i++) { + int x = tileX + random.nextInt(S); + int y = tileY + random.nextInt(S); for (int j = 0; j < 30; j++) { int xx = x + random.nextInt(5) - random.nextInt(5); int yy = y + random.nextInt(5) - random.nextInt(5); - if (xx >= r && yy >= r && xx < w - r && yy < h - r) { - if (map[xx + yy * w] == Tiles.get("rock").id) { - map[xx + yy * w] = (short) ((Tiles.get("iron Ore").id & 0xffff) + depth - 1); + // if (xx >= r && yy >= r && xx < S - r && yy < S - r) { + if (map.getTile(xx, yy) == Tiles.get("rock")) { + map.setTile(xx, yy, Tiles.get((short)(Tiles.get("iron Ore").id + depth - 1)), 0); } - } + // } } for (int j = 0; j < 10; j++) { int xx = x + random.nextInt(3) - random.nextInt(2); int yy = y + random.nextInt(3) - random.nextInt(2); - if (xx >= r && yy >= r && xx < w - r && yy < h - r) { - if (map[xx + yy * w] == Tiles.get("rock").id) { - map[xx + yy * w] = (short) (Tiles.get("Lapis").id & 0xffff); + // if (xx >= r && yy >= r && xx < S - r && yy < S - r) { + if (map.getTile(xx, yy) == Tiles.get("rock")) { + map.setTile(xx, yy, Tiles.get("Lapis"), 0); } - } + // } } } } - if (depth > 2) { // The level above dungeon. - int r = 1; - int xm = w / 2; - int ym = h / 2; - int side = 6; // The side of the lock is 5, and pluses margin with 1. - int edgeMargin = w / 20; // The distance between the world enge and the lock sides. - Rectangle lockRect = new Rectangle(0, 0, side, side, 0); - Rectangle bossRoomRect = new Rectangle(xm, ym, 20, 20, Rectangle.CENTER_DIMS); - do { // Trying to generate a lock not intersecting to the boss room in the dungeon. - int xx = random.nextInt(w); - int yy = random.nextInt(h); - lockRect.setPosition(xx, yy, RelPos.CENTER); - if (lockRect.getTop() > edgeMargin && lockRect.getLeft() > edgeMargin && - lockRect.getRight() < w - edgeMargin && lockRect.getBottom() < h - edgeMargin && - !lockRect.intersects(bossRoomRect)) { - - Structure.dungeonLock.draw(map, xx, yy, w); - - /// The "& 0xffff" is a common way to convert a short to an unsigned int, which basically prevents negative values... except... this doesn't do anything if you flip it back to a short again... - map[xx + yy * w] = (short) (Tiles.get("Stairs Down").id & 0xffff); - break; // The generation is successful. - } - } while (true); - } - if (depth < 3) { int count = 0; stairsLoop: - for (int i = 0; i < w * h / 100; i++) { - int x = random.nextInt(w - 20) + 10; - int y = random.nextInt(h - 20) + 10; + for (int i = 0; i < S * S / 100; i++) { + int x = tileX + random.nextInt(S); + int y = tileY + random.nextInt(S); for (int yy = y - 1; yy <= y + 1; yy++) for (int xx = x - 1; xx <= x + 1; xx++) - if (map[xx + yy * w] != Tiles.get("rock").id) continue stairsLoop; + if (map.getTile(xx, yy) != Tiles.get("rock")) continue stairsLoop; // This should prevent any stairsDown tile from being within 30 tiles of any other stairsDown tile. - for (int yy = Math.max(0, y - stairRadius); yy <= Math.min(h - 1, y + stairRadius); yy++) - for (int xx = Math.max(0, x - stairRadius); xx <= Math.min(w - 1, x + stairRadius); xx++) - if (map[xx + yy * w] == Tiles.get("Stairs Down").id) continue stairsLoop; + for (int yy = Math.max(0, y - stairRadius); yy <= Math.min(S - 1, y + stairRadius); yy++) + for (int xx = Math.max(0, x - stairRadius); xx <= Math.min(S - 1, x + stairRadius); xx++) + if (map.getTile(xx, yy) == Tiles.get("Stairs Down")) continue stairsLoop; - map[x + y * w] = Tiles.get("Stairs Down").id; + map.setTile(x, y, Tiles.get("Stairs Down"), 0); count++; - if (count >= w / 32) break; + if (count >= S / 32) break; } } - return new short[][] { map, data }; } - private static short[][] createSkyMap(int w, int h) { - LevelGen noise1 = new LevelGen(w, h, 8); - LevelGen noise2 = new LevelGen(w, h, 8); - - short[] map = new short[w * h]; - short[] data = new short[w * h]; + private static void generateSkyChunk(ChunkManager map, int chunkX, int chunkY) { + int S = ChunkManager.CHUNK_SIZE; + LevelGen noise1 = new LevelGen(S * chunkX, S * chunkY, S, S, 8, 0); + LevelGen noise2 = new LevelGen(S * chunkX, S * chunkY, S, S, 8, 1); - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int i = x + y * w; + int tileX = chunkX * S, tileY = chunkY * S; + for(int y = tileY; y < tileY + S; y++) { + for(int x = tileX; x < tileX + S; x++) { + int i = (x - tileX) + (y - tileY) * S; double val = Math.abs(noise1.values[i] - noise2.values[i]) * 3 - 2; - double xd = x / (w - 1.0) * 2 - 1; - double yd = y / (h - 1.0) * 2 - 1; - if (xd < 0) xd = -xd; - if (yd < 0) yd = -yd; - double dist = Math.max(xd, yd); - dist = dist * dist * dist * dist; - dist = dist * dist * dist * dist; + double dist = 0; val = -val * 1 - 2.2; - val += 1 - dist * 20; + val += 1.75 - dist * 20; if (val < -0.25) { - map[i] = Tiles.get("Infinite Fall").id; + map.setTile(x, y, Tiles.get("Infinite Fall"), 0); } else { - map[i] = Tiles.get("cloud").id; + map.setTile(x, y, Tiles.get("cloud"), 0); } } } stairsLoop: - for (int i = 0; i < w * h / 50; i++) { - int x = random.nextInt(w - 2) + 1; - int y = random.nextInt(h - 2) + 1; + for (int i = 0; i < S * S / 50; i++) { + int x = tileX + random.nextInt(S - 2) + 1; + int y = tileY + random.nextInt(S - 2) + 1; for (int yy = y - 1; yy <= y + 1; yy++) { for (int xx = x - 1; xx <= x + 1; xx++) { - if (map[xx + yy * w] == Tiles.get("Infinite Fall").id) continue stairsLoop; + if (map.getTile(xx, yy) == Tiles.get("Infinite Fall")) continue stairsLoop; } } - map[x + y * w] = Tiles.get("Cloud Cactus").id; + map.setTile(x, y, Tiles.get("Cloud Cactus"), 0); } int count = 0; stairsLoop: - for (int i = 0; i < w * h; i++) { - int x = random.nextInt(w - 2) + 1; - int y = random.nextInt(h - 2) + 1; + for (int i = 0; i < S * S; i++) { + int x = random.nextInt(S - 2) + 1; + int y = random.nextInt(S - 2) + 1; for (int yy = y - 1; yy <= y + 1; yy++) { for (int xx = x - 1; xx <= x + 1; xx++) { - if (map[xx + yy * w] != Tiles.get("cloud").id) continue stairsLoop; + if (map.getTile(xx, yy) != Tiles.get("cloud")) continue stairsLoop; } } // This should prevent any stairsDown tile from being within 30 tiles of any other stairsDown tile. - for (int yy = Math.max(0, y - stairRadius); yy <= Math.min(h - 1, y + stairRadius); yy++) - for (int xx = Math.max(0, x - stairRadius); xx <= Math.min(w - 1, x + stairRadius); xx++) - if (map[xx + yy * w] == Tiles.get("Stairs Down").id) continue stairsLoop; + for (int yy = Math.max(0, y - stairRadius); yy <= Math.min(S - 1, y + stairRadius); yy++) + for (int xx = Math.max(0, x - stairRadius); xx <= Math.min(S - 1, x + stairRadius); xx++) + if (map.getTile(xx, yy) == Tiles.get("Stairs Down")) continue stairsLoop; - map[x + y * w] = Tiles.get("Stairs Down").id; + map.setTile(x, y, Tiles.get("Stairs Down"), 0); count++; - if (count >= w / 64) break; + if (count >= S / 64) break; } - - return new short[][] { map, data }; } public static void main(String[] args) { @@ -790,10 +606,9 @@ public static void main(String[] args) { int lvl = maplvls[idx++ % maplvls.length]; if (lvl > 1 || lvl < -4) continue; - short[][] fullmap = LevelGen.createAndValidateMap(w, h, lvl, LevelGen.worldSeed); + ChunkManager map = LevelGen.createAndValidateMap(w, h, lvl, LevelGen.worldSeed); - if (fullmap == null) continue; - short[] map = fullmap[0]; + if (map == null) continue; BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); int[] pixels = new int[w * h]; @@ -801,25 +616,25 @@ public static void main(String[] args) { for (int x = 0; x < w; x++) { int i = x + y * w; - if (map[i] == Tiles.get("water").id) pixels[i] = 0x000080; - if (map[i] == Tiles.get("iron Ore").id) pixels[i] = 0x000080; - if (map[i] == Tiles.get("gold Ore").id) pixels[i] = 0x000080; - if (map[i] == Tiles.get("gem Ore").id) pixels[i] = 0x000080; - if (map[i] == Tiles.get("grass").id) pixels[i] = 0x208020; - if (map[i] == Tiles.get("rock").id) pixels[i] = 0xa0a0a0; - if (map[i] == Tiles.get("dirt").id) pixels[i] = 0x604040; - if (map[i] == Tiles.get("sand").id) pixels[i] = 0xa0a040; - if (map[i] == Tiles.get("Stone Bricks").id) pixels[i] = 0xa0a040; - if (map[i] == Tiles.get("tree").id) pixels[i] = 0x003000; - if (map[i] == Tiles.get("Obsidian Wall").id) pixels[i] = 0x0aa0a0; - if (map[i] == Tiles.get("Obsidian").id) pixels[i] = 0x000000; - if (map[i] == Tiles.get("lava").id) pixels[i] = 0xffff2020; - if (map[i] == Tiles.get("cloud").id) pixels[i] = 0xa0a0a0; - if (map[i] == Tiles.get("Stairs Down").id) pixels[i] = 0xffffffff; - if (map[i] == Tiles.get("Stairs Up").id) pixels[i] = 0xffffffff; - if (map[i] == Tiles.get("Cloud Cactus").id) pixels[i] = 0xffff00ff; - if (map[i] == Tiles.get("Ornate Obsidian").id) pixels[i] = 0x000f0a; - if (map[i] == Tiles.get("Raw Obsidian").id) pixels[i] = 0x0a0080; + if (map.getTile(x, y) == Tiles.get("water")) pixels[i] = 0x000080; + if (map.getTile(x, y) == Tiles.get("iron Ore")) pixels[i] = 0x000080; + if (map.getTile(x, y) == Tiles.get("gold Ore")) pixels[i] = 0x000080; + if (map.getTile(x, y) == Tiles.get("gem Ore")) pixels[i] = 0x000080; + if (map.getTile(x, y) == Tiles.get("grass")) pixels[i] = 0x208020; + if (map.getTile(x, y) == Tiles.get("rock")) pixels[i] = 0xa0a0a0; + if (map.getTile(x, y) == Tiles.get("dirt")) pixels[i] = 0x604040; + if (map.getTile(x, y) == Tiles.get("sand")) pixels[i] = 0xa0a040; + if (map.getTile(x, y) == Tiles.get("Stone Bricks")) pixels[i] = 0xa0a040; + if (map.getTile(x, y) == Tiles.get("tree")) pixels[i] = 0x003000; + if (map.getTile(x, y) == Tiles.get("Obsidian Wall")) pixels[i] = 0x0aa0a0; + if (map.getTile(x, y) == Tiles.get("Obsidian")) pixels[i] = 0x000000; + if (map.getTile(x, y) == Tiles.get("lava")) pixels[i] = 0xffff2020; + if (map.getTile(x, y) == Tiles.get("cloud")) pixels[i] = 0xa0a0a0; + if (map.getTile(x, y) == Tiles.get("Stairs Down")) pixels[i] = 0xffffffff; + if (map.getTile(x, y) == Tiles.get("Stairs Up")) pixels[i] = 0xffffffff; + if (map.getTile(x, y) == Tiles.get("Cloud Cactus")) pixels[i] = 0xffff00ff; + if (map.getTile(x, y) == Tiles.get("Ornate Obsidian")) pixels[i] = 0x000f0a; + if (map.getTile(x, y) == Tiles.get("Raw Obsidian")) pixels[i] = 0x0a0080; } } img.setRGB(0, 0, w, h, pixels, 0, w); @@ -830,4 +645,6 @@ public static void main(String[] args) { else LevelGen.worldSeed++; } } + + // Used to easily interface with a list of chunks } diff --git a/src/client/java/minicraft/level/Structure.java b/src/client/java/minicraft/level/Structure.java index 26be44858..4e4c1537f 100644 --- a/src/client/java/minicraft/level/Structure.java +++ b/src/client/java/minicraft/level/Structure.java @@ -50,9 +50,9 @@ public void draw(Level level, int xt, int yt, Consumer furnitureHandl } } - public void draw(short[] map, int xt, int yt, int mapWidth) { + public void draw(ChunkManager map, int xt, int yt) { for (TilePoint p : tiles) - map[(xt + p.x) + (yt + p.y) * mapWidth] = p.t.id; + map.setTile(xt + p.x, yt + p.y, p.t, 0); } public void setData(String keys, String data) { diff --git a/src/client/java/minicraft/level/tile/Tile.java b/src/client/java/minicraft/level/tile/Tile.java index 68a53a053..ca83aeb56 100644 --- a/src/client/java/minicraft/level/tile/Tile.java +++ b/src/client/java/minicraft/level/tile/Tile.java @@ -198,8 +198,8 @@ public static String getData(int depth, int x, int y) { Level curLevel = World.levels[lvlidx]; int pos = x + curLevel.w * y; - int tileid = curLevel.tiles[pos]; - int tiledata = curLevel.data[pos]; + int tileid = curLevel.getTile(x, y).id; + int tiledata = curLevel.getData(x, y); return lvlidx + ";" + pos + ";" + tileid + ";" + tiledata; } catch (NullPointerException | IndexOutOfBoundsException ignored) { diff --git a/src/client/java/minicraft/level/tile/TreeTile.java b/src/client/java/minicraft/level/tile/TreeTile.java index 6b9a76160..9cdaf07a1 100644 --- a/src/client/java/minicraft/level/tile/TreeTile.java +++ b/src/client/java/minicraft/level/tile/TreeTile.java @@ -68,19 +68,19 @@ public boolean connectsToGrass(Level level, int x, int y) { public void render(Screen screen, Level level, int x, int y) { Tiles.get("Grass").render(screen, level, x, y); - TreeType thisType = level.treeTypes[x + y * level.w]; + TreeType thisType = level.getTreeType(x, y); // Checking whether the target direction has targeted the same TreeTile - boolean isUpTileSame = level.getTile(x, y - 1) == this && thisType == level.treeTypes[x + (y - 1) * level.w]; - boolean isLeftTileSame = level.getTile(x - 1, y) == this && thisType == level.treeTypes[(x - 1) + y * level.w]; - boolean isRightTileSame = level.getTile(x + 1, y) == this && thisType == level.treeTypes[(x + 1) + y * level.w]; - boolean isDownTileSame = level.getTile(x, y + 1) == this && thisType == level.treeTypes[x + (y + 1) * level.w]; - boolean isUpLeftTileSame = level.getTile(x - 1, y - 1) == this && thisType == level.treeTypes[(x - 1) + (y - 1) * level.w]; - boolean isUpRightTileSame = level.getTile(x + 1, y - 1) == this && thisType == level.treeTypes[(x + 1) + (y - 1) * level.w]; - boolean isDownLeftTileSame = level.getTile(x - 1, y + 1) == this && thisType == level.treeTypes[(x - 1) + (y + 1) * level.w]; - boolean isDownRightTileSame = level.getTile(x + 1, y + 1) == this && thisType == level.treeTypes[(x + 1) + (y + 1) * level.w]; - - Sprite sprite = level.treeTypes[x + y * level.w].treeSprite.getSprite(); - Sprite spriteFull = level.treeTypes[x + y * level.w].treeSpriteFull.getSprite(); + boolean isUpTileSame = level.getTile(x, y - 1) == this && thisType == level.getTreeType(x, y - 1); + boolean isLeftTileSame = level.getTile(x - 1, y) == this && thisType == level.getTreeType(x - 1, y); + boolean isRightTileSame = level.getTile(x + 1, y) == this && thisType == level.getTreeType(x + 1, y); + boolean isDownTileSame = level.getTile(x, y + 1) == this && thisType == level.getTreeType(x, y + 1); + boolean isUpLeftTileSame = level.getTile(x - 1, y - 1) == this && thisType == level.getTreeType(x - 1, y - 1); + boolean isUpRightTileSame = level.getTile(x + 1, y - 1) == this && thisType == level.getTreeType(x + 1, y - 1); + boolean isDownLeftTileSame = level.getTile(x - 1, y + 1) == this && thisType == level.getTreeType(x - 1, y + 1); + boolean isDownRightTileSame = level.getTile(x + 1, y + 1) == this && thisType == level.getTreeType(x + 1, y + 1); + + Sprite sprite = level.getTreeType(x, y).treeSprite.getSprite(); + Sprite spriteFull = level.getTreeType(x, y).treeSpriteFull.getSprite(); if (isUpTileSame && isUpLeftTileSame && isLeftTileSame) { screen.render((x << 4) + 0, (y << 4) + 0, spriteFull.spritePixels[0][1]); diff --git a/src/client/java/minicraft/saveload/LegacyLoad.java b/src/client/java/minicraft/saveload/LegacyLoad.java index 033679079..11c6e0aaa 100644 --- a/src/client/java/minicraft/saveload/LegacyLoad.java +++ b/src/client/java/minicraft/saveload/LegacyLoad.java @@ -33,6 +33,7 @@ import minicraft.item.PotionItem; import minicraft.item.PotionType; import minicraft.item.StackableItem; +import minicraft.level.ChunkManager; import minicraft.level.Level; import minicraft.level.tile.Tiles; import minicraft.screen.LoadingDisplay; @@ -224,21 +225,23 @@ public void loadWorld(String filename) { int lvldepth = Integer.parseInt(data.get(2)); Settings.set("size", lvlw); - short[] tiles = new short[lvlw * lvlh]; - short[] tdata = new short[lvlw * lvlh]; + ChunkManager map = new ChunkManager(); for (int x = 0; x < lvlw - 1; x++) { for (int y = 0; y < lvlh - 1; y++) { int tileArrIdx = y + x * lvlw; - int tileidx = x + y * lvlw; // The tiles are saved with x outer loop, and y inner loop, meaning that the list reads down, then right one, rather than right, then down one. - Load.loadTile(worldVer, tiles, tdata, tileArrIdx, Tiles.oldids.get(Integer.parseInt(data.get(tileidx + 3))), + int tileidx = y + x * lvlh; // The tiles are saved with x outer loop, and y inner loop, meaning that the list reads down, then right one, rather than right, then down one. + Load.loadTile(worldVer, map, x, y, Tiles.oldids.get(Integer.parseInt(data.get(tileidx + 3))), extradata.get(tileidx)); } } + for(int x = 0; x < lvlw / ChunkManager.CHUNK_SIZE; x++) + for(int y = 0; y < lvlh / ChunkManager.CHUNK_SIZE; y++) + map.setChunkStage(x, y, ChunkManager.CHUNK_STAGE_DONE); + World.levels[l] = new Level(lvlw, lvlh, lvldepth, null, false); - World.levels[l].tiles = tiles; - World.levels[l].data = tdata; + World.levels[l].chunkManager = map; } } diff --git a/src/client/java/minicraft/saveload/Load.java b/src/client/java/minicraft/saveload/Load.java index 132365b0a..08b0026f5 100644 --- a/src/client/java/minicraft/saveload/Load.java +++ b/src/client/java/minicraft/saveload/Load.java @@ -50,6 +50,7 @@ import minicraft.item.PotionType; import minicraft.item.Recipe; import minicraft.item.StackableItem; +import minicraft.level.ChunkManager; import minicraft.level.Level; import minicraft.level.tile.Tiles; import minicraft.network.Network; @@ -86,8 +87,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; @@ -222,7 +225,10 @@ public Load(String worldname, boolean loadGame) { LoadingDisplay.setPercentage(0); loadGame("Game"); // More of the version will be determined here - loadWorld("Level"); + if(worldVer.compareTo(new Version("2.3.0-dev2")) < 0) + loadWorld("Level"); + else + loadWorldInf("Level"); loadEntities("Entities"); loadInventory("Inventory", Game.player.getInventory()); loadPlayer("Player", Game.player); @@ -400,6 +406,22 @@ private void loadFromFile(String filename) { LoadingDisplay.progress(percentInc); } + private void loadFromFile(String filename, List data) { + data.clear(); + + String total; + try { + total = loadFromFile(filename, true); + if (total.length() > 0) { // Safe splitting with JSON styled element. + data.addAll(splitUnwrappedCommas(total)); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + + LoadingDisplay.progress(percentInc); + } + /** * Source: Note: This method is copied from MiniMods. */ @@ -716,13 +738,12 @@ private void loadWorld(String filename) { long seed = hasSeed ? Long.parseLong(data.get(2)) : 0; Settings.set("size", lvlw); - short[] tiles = new short[lvlw * lvlh]; - short[] tdata = new short[lvlw * lvlh]; + ChunkManager map = new ChunkManager(); for (int x = 0; x < lvlw; x++) { for (int y = 0; y < lvlh; y++) { int tileArrIdx = y + x * lvlw; - int tileidx = x + y * lvlw; // the tiles are saved with x outer loop, and y inner loop, meaning that the list reads down, then right one, rather than right, then down one. + int tileidx = y + x * lvlh; // the tiles are saved with x outer loop, and y inner loop, meaning that the list reads down, then right one, rather than right, then down one. String tilename = data.get(tileidx + (hasSeed ? 4 : 3)); if (worldVer.compareTo(new Version("1.9.4-dev6")) < 0) { int tileID = Integer.parseInt(tilename); // they were id numbers, not names, at this point @@ -774,7 +795,8 @@ private void loadWorld(String filename) { } } - loadTile(worldVer, tiles, tdata, tileArrIdx, tilename, extradata.get(tileidx)); + // Tiles are read in an ord + loadTile(worldVer, map, x, y, tilename, extradata.get(tileidx)); } } @@ -782,15 +804,17 @@ private void loadWorld(String filename) { World.levels[lvlidx] = new Level(lvlw, lvlh, seed, l, parent, false); Level curLevel = World.levels[lvlidx]; - curLevel.tiles = tiles; - curLevel.data = tdata; + curLevel.chunkManager = map; // Tile initialization for (int x = 0; x < curLevel.w; ++x) { for (int y = 0; y < curLevel.h; ++y) { - Tiles.get(curLevel.tiles[x + y * curLevel.w]).onTileSet(curLevel, x, y); + curLevel.getTile(x, y).onTileSet(curLevel, x, y); } } + for(int x = 0; x < curLevel.w / ChunkManager.CHUNK_SIZE; x++) + for(int y = 0; y < curLevel.h / ChunkManager.CHUNK_SIZE; y++) + curLevel.chunkManager.setChunkStage(x, y, ChunkManager.CHUNK_STAGE_DONE); if (Logging.logLevel) curLevel.printTileLocs(Tiles.get("Stairs Down")); @@ -874,20 +898,130 @@ private void loadWorld(String filename) { } } + private void loadWorldInf(String filename) { + loadFromFile(location + "/Game" + extension, extradata); + long seed = Long.parseLong(extradata.get(1)); + for (int l = World.maxLevelDepth; l >= World.minLevelDepth; l--) { + LoadingDisplay.setMessage(Level.getDepthString(l), false); + int lvlidx = World.lvlIdx(l); + loadFromFile(location + filename + lvlidx + "/index" + extension, data); + + Set chunks = new HashSet<>(); + while(data.size() >= 2) + chunks.add(new Point(Integer.parseInt(data.remove(0)), Integer.parseInt(data.remove(0)))); + + ChunkManager map = new ChunkManager(); + Level parent = World.levels[World.lvlIdx(l + 1)]; + World.levels[lvlidx] = new Level((int)Settings.get("size"), (int)Settings.get("size"), seed, l, parent, false); + + Level curLevel = World.levels[lvlidx]; + curLevel.chunkManager = map; + for(Point c : chunks) { + loadFromFile(location + filename + lvlidx + "/t." + c.x + "." + c.y + extension, data); + loadFromFile(location + filename + lvlidx + "/d." + c.x + "." + c.y + extension, extradata); + for(int x = 0; x < ChunkManager.CHUNK_SIZE; x++) { + for(int y = 0; y < ChunkManager.CHUNK_SIZE; y++) { + int tileidx = y + x * ChunkManager.CHUNK_SIZE; // the tiles are saved with x outer loop, and y inner loop, meaning that the list reads down, then right one, rather than right, then down one. + int tX = x + c.x * ChunkManager.CHUNK_SIZE, tY = y + c.y * ChunkManager.CHUNK_SIZE; + loadTile(worldVer, map, tX, tY, data.get(tileidx), extradata.get(tileidx)); + map.getTile(tX, tY).onTileSet(curLevel, tX, tY); + } + } + map.setChunkStage(c.x, c.y, ChunkManager.CHUNK_STAGE_DONE); + } + + if (Logging.logLevel) curLevel.printTileLocs(Tiles.get("Stairs Down")); + + if (parent == null) continue; + /// confirm that there are stairs in all the places that should have stairs. + // if there isn't, mark the chunk as needing stairs + for (minicraft.gfx.Point p : parent.getMatchingTiles(Tiles.get("Stairs Down"))) { + if (curLevel.getTile(p.x, p.y) != Tiles.get("Stairs Up")) { + curLevel.chunkManager.setChunkStage(p.x / ChunkManager.CHUNK_SIZE, p.y / ChunkManager.CHUNK_SIZE, ChunkManager.CHUNK_STAGE_UNFINISHED_STAIRS); + } + } + for (minicraft.gfx.Point p : curLevel.getMatchingTiles(Tiles.get("Stairs Up"))) { + if (parent.getTile(p.x, p.y) != Tiles.get("Stairs Down")) { + parent.chunkManager.setChunkStage(p.x / ChunkManager.CHUNK_SIZE, p.y / ChunkManager.CHUNK_SIZE, ChunkManager.CHUNK_STAGE_UNFINISHED_STAIRS); + } + } + } + + LoadingDisplay.setMessage("minicraft.displays.loading.message.quests"); + + if (new File(location + "Quests.json").exists()) { + Logging.SAVELOAD.warn("Quest.json exists and it has been deprecated; renaming..."); + try { + Files.move(Paths.get(location, "Quests.json"), Paths.get(location, "Quests.json_old"), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + Logging.SAVELOAD.warn("Quest.json renamed failed."); + } + } + + boolean advancementsLoadSucceeded = false; + if (new File(location + "advancements.json").exists()) { + try { + JSONObject questsObj = new JSONObject(loadFromFile(location + "advancements.json", true)); + @SuppressWarnings("unused") + Version dataVersion = new Version(questsObj.getString("Version")); + TutorialDisplayHandler.load(questsObj); + AdvancementElement.loadRecipeUnlockingElements(questsObj); + QuestsDisplay.load(questsObj); + advancementsLoadSucceeded = true; + } catch (IOException e) { + Logging.SAVELOAD.error(e, "Unable to load advancements.json, loading default quests instead."); + } + } else { + Logging.SAVELOAD.debug("advancements.json not found, loading default quests instead."); + } + + if (!advancementsLoadSucceeded) { + TutorialDisplayHandler.reset(false); + AdvancementElement.resetRecipeUnlockingElements(); + QuestsDisplay.resetGameQuests(); + } + + boolean signsLoadSucceeded = false; + if (new File(location + "signs.json").exists()) { + try { + JSONObject fileObj = new JSONObject(loadFromFile(location + "signs.json", true)); + @SuppressWarnings("unused") + Version dataVersion = new Version(fileObj.getString("Version")); + JSONArray dataObj = fileObj.getJSONArray("signs"); + HashMap, List> signTexts = new HashMap<>(); + for (int i = 0; i < dataObj.length(); i++) { + JSONObject signObj = dataObj.getJSONObject(i); + signTexts.put( + new AbstractMap.SimpleImmutableEntry<>(signObj.getInt("level"), new Point(signObj.getInt("x"), signObj.getInt("y"))), + signObj.getJSONArray("lines").toList().stream().map(e -> (String) e).collect(Collectors.toList()) + ); + } + + SignDisplay.loadSignTexts(signTexts); + signsLoadSucceeded = true; + } catch (IOException e) { + Logging.SAVELOAD.error(e, "Unable to load signs.json, reset sign data instead."); + } + } else { + Logging.SAVELOAD.debug("signs.json not found, reset sign data instead."); + } + + if (!signsLoadSucceeded) { + SignDisplay.resetSignTexts(); + } + } + private static final Pattern OLD_TORCH_TILE_REGEX = Pattern.compile("TORCH ([\\w ]+)"); - public static void loadTile(Version worldVer, short[] tiles, short[] data, int idx, String tileName, String tileData) { + public static void loadTile(Version worldVer, ChunkManager map, int x, int y, String tileName, String tileData) { Matcher matcher; if ((matcher = OLD_TORCH_TILE_REGEX.matcher(tileName.toUpperCase())).matches()) { - tiles[idx] = 57; // ID of TORCH tile - data[idx] = Tiles.get(matcher.group(1)).id; + map.setTile(x, y, Tiles.get("Torch"), Tiles.get(matcher.group(1)).id); } else { - tiles[idx] = Tiles.get(tileName).id; - if (worldVer.compareTo(new Version("2.3.0-dev1")) < 0 && tileName.equalsIgnoreCase("FLOWER")) { - data[idx] = 0; - } else { - data[idx] = Short.parseShort(tileData); - } + short data = 0; + if (worldVer.compareTo(new Version("2.3.0-dev1")) > 0 || !tileName.equalsIgnoreCase("FLOWER")) + data = Short.parseShort(tileData); + map.setTile(x, y, Tiles.get(tileName), data); } } diff --git a/src/client/java/minicraft/saveload/Save.java b/src/client/java/minicraft/saveload/Save.java index d1e52a678..a8f429fd4 100644 --- a/src/client/java/minicraft/saveload/Save.java +++ b/src/client/java/minicraft/saveload/Save.java @@ -32,6 +32,7 @@ import minicraft.item.Item; import minicraft.item.PotionType; import minicraft.item.Recipe; +import minicraft.level.ChunkManager; import minicraft.screen.AchievementsDisplay; import minicraft.screen.CraftingDisplay; import minicraft.screen.LoadingDisplay; @@ -54,7 +55,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; public class Save { @@ -270,7 +270,7 @@ private void writeUnlocks() { private void writeWorld(String filename) { LoadingDisplay.setMessage("minicraft.displays.loading.message.levels"); for (int l = 0; l < World.levels.length; l++) { - String worldSize = String.valueOf(Settings.get("size")); + /*String worldSize = String.valueOf(Settings.get("size")); data.add(worldSize); data.add(worldSize); data.add(Long.toString(World.levels[l].getSeed())); @@ -282,17 +282,26 @@ private void writeWorld(String filename) { } } - writeToFile(location + filename + l + extension, data); - } - - for (int l = 0; l < World.levels.length; l++) { - for (int x = 0; x < World.levels[l].w; x++) { - for (int y = 0; y < World.levels[l].h; y++) { - data.add(String.valueOf(World.levels[l].getData(x, y))); - } + writeToFile(location + filename + l + extension, data);*/ + new File(location + filename + l).mkdir(); + List index = new ArrayList<>(); + ChunkManager c = World.levels[l].chunkManager; + for(Point p : c.getAllChunks()) { + if(c.getChunkStage(p.x, p.y) != ChunkManager.CHUNK_STAGE_DONE) + continue; + index.add(String.valueOf(p.x)); + index.add(String.valueOf(p.y)); + List tiles = new ArrayList<>(); + for(int x = 0; x < ChunkManager.CHUNK_SIZE; x++) + for(int y = 0; y < ChunkManager.CHUNK_SIZE; y++) { + int tX = x+p.x*ChunkManager.CHUNK_SIZE, tY = y+p.y*ChunkManager.CHUNK_SIZE; + tiles.add(String.valueOf(World.levels[l].getTile(tX, tY).name)); + data.add(String.valueOf(World.levels[l].getData(tX, tY))); + } + writeToFile(location + filename + l + "/d." + String.valueOf(p.x) + "." + String.valueOf(p.y) + extension, data); + writeToFile(location + filename + l + "/t." + String.valueOf(p.x) + "." + String.valueOf(p.y) + extension, tiles); } - - writeToFile(location + filename + l + "data" + extension, data); + writeToFile(location + filename + l + "/index" + extension, index); } { // Advancements diff --git a/src/client/java/minicraft/util/Simplex.java b/src/client/java/minicraft/util/Simplex.java new file mode 100644 index 000000000..031218553 --- /dev/null +++ b/src/client/java/minicraft/util/Simplex.java @@ -0,0 +1,782 @@ +/** + * SHAMELESSLY stolen from [K.jpg's OpenSimplex 2, faster variant](https://github.com/KdotJPG/OpenSimplex2) + * Check it out, unliscensed and pretty cool + */ + +package minicraft.util; + +public class Simplex { + + // A bunch of funny magic numbers + private final long PRIME_X = 0x5205402B9270C86FL; + private final long PRIME_Y = 0x598CD327003817B5L; + private final long PRIME_Z = 0x5BCC226E9FA0BACBL; + private final long PRIME_W = 0x56CC5227E58F554BL; + private final long HASH_MULTIPLIER = 0x53A3F72DEEC546F5L; + private final long SEED_FLIP_3D = -0x52D547B2E96ED629L; + private final long SEED_OFFSET_4D = 0xE83DC3E0DA7164DL; + + private final double ROOT2OVER2 = 0.7071067811865476; + private final double SKEW_2D = 0.366025403784439; + private final double UNSKEW_2D = -0.21132486540518713; + + private final double ROOT3OVER3 = 0.577350269189626; + private final double FALLBACK_ROTATE_3D = 2.0 / 3.0; + private final double ROTATE_3D_ORTHOGONALIZER = UNSKEW_2D; + + private final float SKEW_4D = -0.138196601125011f; + private final float UNSKEW_4D = 0.309016994374947f; + private final float LATTICE_STEP_4D = 0.2f; + + private final int N_GRADS_2D_EXPONENT = 7; + private final int N_GRADS_3D_EXPONENT = 8; + private final int N_GRADS_4D_EXPONENT = 9; + private final int N_GRADS_2D = 1 << N_GRADS_2D_EXPONENT; + private final int N_GRADS_3D = 1 << N_GRADS_3D_EXPONENT; + private final int N_GRADS_4D = 1 << N_GRADS_4D_EXPONENT; + + private final double NORMALIZER_2D = 0.01001634121365712; + private final double NORMALIZER_3D = 0.07969837668935331; + private final double NORMALIZER_4D = 0.0220065933241897; + + private final float RSQUARED_2D = 0.5f; + private final float RSQUARED_3D = 0.6f; + private final float RSQUARED_4D = 0.6f; + + private long seed; + + public Simplex(long seed) { + this.seed = seed; + } + + public Simplex() { + this(System.nanoTime()); + } + + public void setSeed(long seed) { + this.seed = seed; + } + + /* + * Noise Evaluators + */ + + /** + * 2D Simplex noise, standard lattice orientation. + */ + public float noise2(double x, double y) { + + // Get points for A2* lattice + double s = SKEW_2D * (x + y); + double xs = x + s, ys = y + s; + + return noise2_UnskewedBase(xs, ys); + } + + /** + * 2D Simplex noise, with Y pointing down the main diagonal. + * Might be better for a 2D sandbox style game, where Y is vertical. + * Probably slightly less optimal for heightmaps or continent maps, + * unless your map is centered around an equator. It's a subtle + * difference, but the option is here to make it an easy choice. + */ + public float noise2_ImproveX(double x, double y) { + + // Skew transform and rotation baked into one. + double xx = x * ROOT2OVER2; + double yy = y * (ROOT2OVER2 * (1 + 2 * SKEW_2D)); + + return noise2_UnskewedBase(yy + xx, yy - xx); + } + + /** + * 2D Simplex noise base. + */ + private float noise2_UnskewedBase(double xs, double ys) { + + // Get base points and offsets. + int xsb = fastFloor(xs), ysb = fastFloor(ys); + float xi = (float)(xs - xsb), yi = (float)(ys - ysb); + + // Prime pre-multiplication for hash. + long xsbp = xsb * PRIME_X, ysbp = ysb * PRIME_Y; + + // Unskew. + float t = (xi + yi) * (float)UNSKEW_2D; + float dx0 = xi + t, dy0 = yi + t; + + // First vertex. + float value = 0; + float a0 = RSQUARED_2D - dx0 * dx0 - dy0 * dy0; + if (a0 > 0) { + value = (a0 * a0) * (a0 * a0) * grad(xsbp, ysbp, dx0, dy0); + } + + // Second vertex. + float a1 = (float)(2 * (1 + 2 * UNSKEW_2D) * (1 / UNSKEW_2D + 2)) * t + ((float)(-2 * (1 + 2 * UNSKEW_2D) * (1 + 2 * UNSKEW_2D)) + a0); + if (a1 > 0) { + float dx1 = dx0 - (float)(1 + 2 * UNSKEW_2D); + float dy1 = dy0 - (float)(1 + 2 * UNSKEW_2D); + value += (a1 * a1) * (a1 * a1) * grad(xsbp + PRIME_X, ysbp + PRIME_Y, dx1, dy1); + } + + // Third vertex. + if (dy0 > dx0) { + float dx2 = dx0 - (float)UNSKEW_2D; + float dy2 = dy0 - (float)(UNSKEW_2D + 1); + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * grad(xsbp, ysbp + PRIME_Y, dx2, dy2); + } + } + else + { + float dx2 = dx0 - (float)(UNSKEW_2D + 1); + float dy2 = dy0 - (float)UNSKEW_2D; + float a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2; + if (a2 > 0) { + value += (a2 * a2) * (a2 * a2) * grad(xsbp + PRIME_X, ysbp, dx2, dy2); + } + } + + return value; + } + + /* Default noise3 */ + public float noise3(double x, double y, double z) { return noise3_ImproveXY(x, y, z); } + + /** + * 3D OpenSimplex2 noise, with better visual isotropy in (X, Y). + * Recommended for 3D terrain and time-varied animations. + * The Z coordinate should always be the "different" coordinate in whatever your use case is. + * If Y is vertical in world coordinates, call noise3_ImproveXZ(x, z, Y) or use noise3_XZBeforeY. + * If Z is vertical in world coordinates, call noise3_ImproveXZ(x, y, Z). + * For a time varied animation, call noise3_ImproveXY(x, y, T). + */ + public float noise3_ImproveXY(double x, double y, double z) { + + // Re-orient the cubic lattices without skewing, so Z points up the main lattice diagonal, + // and the planes formed by XY are moved far out of alignment with the cube faces. + // Orthonormal rotation. Not a skew transform. + double xy = x + y; + double s2 = xy * ROTATE_3D_ORTHOGONALIZER; + double zz = z * ROOT3OVER3; + double xr = x + s2 + zz; + double yr = y + s2 + zz; + double zr = xy * -ROOT3OVER3 + zz; + + // Evaluate both lattices to form a BCC lattice. + return noise3_UnrotatedBase(xr, yr, zr); + } + + /** + * 3D OpenSimplex2 noise, with better visual isotropy in (X, Z). + * Recommended for 3D terrain and time-varied animations. + * The Y coordinate should always be the "different" coordinate in whatever your use case is. + * If Y is vertical in world coordinates, call noise3_ImproveXZ(x, Y, z). + * If Z is vertical in world coordinates, call noise3_ImproveXZ(x, Z, y) or use noise3_ImproveXY. + * For a time varied animation, call noise3_ImproveXZ(x, T, y) or use noise3_ImproveXY. + */ + public float noise3_ImproveXZ(double x, double y, double z) { + + // Re-orient the cubic lattices without skewing, so Y points up the main lattice diagonal, + // and the planes formed by XZ are moved far out of alignment with the cube faces. + // Orthonormal rotation. Not a skew transform. + double xz = x + z; + double s2 = xz * ROTATE_3D_ORTHOGONALIZER; + double yy = y * ROOT3OVER3; + double xr = x + s2 + yy; + double zr = z + s2 + yy; + double yr = xz * -ROOT3OVER3 + yy; + + // Evaluate both lattices to form a BCC lattice. + return noise3_UnrotatedBase(xr, yr, zr); + } + + /** + * 3D OpenSimplex2 noise, fallback rotation option + * Use noise3_ImproveXY or noise3_ImproveXZ instead, wherever appropriate. + * They have less diagonal bias. This function's best use is as a fallback. + */ + public float noise3_Fallback(double x, double y, double z) { + + // Re-orient the cubic lattices via rotation, to produce a familiar look. + // Orthonormal rotation. Not a skew transform. + double r = FALLBACK_ROTATE_3D * (x + y + z); + double xr = r - x, yr = r - y, zr = r - z; + + // Evaluate both lattices to form a BCC lattice. + return noise3_UnrotatedBase(xr, yr, zr); + } + + /** + * Generate overlapping cubic lattices for 3D OpenSimplex2 noise. + */ + private float noise3_UnrotatedBase(double xr, double yr, double zr) { + + // Get base points and offsets. + int xrb = fastRound(xr), yrb = fastRound(yr), zrb = fastRound(zr); + float xri = (float)(xr - xrb), yri = (float)(yr - yrb), zri = (float)(zr - zrb); + + // -1 if positive, 1 if negative. + int xNSign = (int)(-1.0f - xri) | 1, yNSign = (int)(-1.0f - yri) | 1, zNSign = (int)(-1.0f - zri) | 1; + + // Compute absolute values, using the above as a shortcut. This was faster in my tests for some reason. + float ax0 = xNSign * -xri, ay0 = yNSign * -yri, az0 = zNSign * -zri; + + // Prime pre-multiplication for hash. + long xrbp = xrb * PRIME_X, yrbp = yrb * PRIME_Y, zrbp = zrb * PRIME_Z; + + // Loop: Pick an edge on each lattice copy. + float value = 0; + float a = (RSQUARED_3D - xri * xri) - (yri * yri + zri * zri); + long pSeed = seed; + for (int l = 0; ; l++) { + + // Closest point on cube. + if (a > 0) { + value += (a * a) * (a * a) * grad(xrbp, yrbp, zrbp, xri, yri, zri); + } + + // Second-closest point. + if (ax0 >= ay0 && ax0 >= az0) { + float b = a + ax0 + ax0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * grad(xrbp - xNSign * PRIME_X, yrbp, zrbp, xri + xNSign, yri, zri); + } + } + else if (ay0 > ax0 && ay0 >= az0) { + float b = a + ay0 + ay0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * grad(xrbp, yrbp - yNSign * PRIME_Y, zrbp, xri, yri + yNSign, zri); + } + } + else + { + float b = a + az0 + az0; + if (b > 1) { + b -= 1; + value += (b * b) * (b * b) * grad(xrbp, yrbp, zrbp - zNSign * PRIME_Z, xri, yri, zri + zNSign); + } + } + + // Break from loop if we're done, skipping updates below. + if (l == 1) break; + + // Update absolute value. + ax0 = 0.5f - ax0; + ay0 = 0.5f - ay0; + az0 = 0.5f - az0; + + // Update relative coordinate. + xri = xNSign * ax0; + yri = yNSign * ay0; + zri = zNSign * az0; + + // Update falloff. + a += (0.75f - ax0) - (ay0 + az0); + + // Update prime for hash. + xrbp += (xNSign >> 1) & PRIME_X; + yrbp += (yNSign >> 1) & PRIME_Y; + zrbp += (zNSign >> 1) & PRIME_Z; + + // Update the reverse sign indicators. + xNSign = -xNSign; + yNSign = -yNSign; + zNSign = -zNSign; + + // And finally update the seed for the other lattice copy. + seed ^= SEED_FLIP_3D; + } + + seed = pSeed; + + return value; + } + + /** + * 4D OpenSimplex2 noise, with XYZ oriented like noise3_ImproveXY + * and W for an extra degree of freedom. W repeats eventually. + * Recommended for time-varied animations which texture a 3D object (W=time) + * in a space where Z is vertical + */ + public float noise4_ImproveXYZ_ImproveXY(double x, double y, double z, double w) { + + double xy = x + y; + double s2 = xy * -0.21132486540518699998; + double zz = z * 0.28867513459481294226; + double ww = w * 0.2236067977499788; + double xr = x + (zz + ww + s2), yr = y + (zz + ww + s2); + double zr = xy * -0.57735026918962599998 + (zz + ww); + double wr = z * -0.866025403784439 + ww; + + return noise4_UnskewedBase(xr, yr, zr, wr); + } + + /** + * 4D OpenSimplex2 noise, with XYZ oriented like noise3_ImproveXZ + * and W for an extra degree of freedom. W repeats eventually. + * Recommended for time-varied animations which texture a 3D object (W=time) + * in a space where Y is vertical + */ + public float noise4_ImproveXYZ_ImproveXZ(double x, double y, double z, double w) { + + double xz = x + z; + double s2 = xz * -0.21132486540518699998; + double yy = y * 0.28867513459481294226; + double ww = w * 0.2236067977499788; + double xr = x + (yy + ww + s2), zr = z + (yy + ww + s2); + double yr = xz * -0.57735026918962599998 + (yy + ww); + double wr = y * -0.866025403784439 + ww; + + return noise4_UnskewedBase(xr, yr, zr, wr); + } + + /** + * 4D OpenSimplex2 noise, with XYZ oriented like noise3_Fallback + * and W for an extra degree of freedom. W repeats eventually. + * Recommended for time-varied animations which texture a 3D object (W=time) + * where there isn't a clear distinction between horizontal and vertical + */ + public float noise4_ImproveXYZ(double x, double y, double z, double w) { + + double xyz = x + y + z; + double ww = w * 0.2236067977499788; + double s2 = xyz * -0.16666666666666666 + ww; + double xs = x + s2, ys = y + s2, zs = z + s2, ws = -0.5 * xyz + ww; + + return noise4_UnskewedBase(xs, ys, zs, ws); + } + + /** + * 4D OpenSimplex2 noise, with XY and ZW forming orthogonal triangular-based planes. + * Recommended for 3D terrain, where X and Y (or Z and W) are horizontal. + * Recommended for noise(x, y, sin(time), cos(time)) trick. + */ + public float noise4_ImproveXY_ImproveZW(double x, double y, double z, double w) { + + double s2 = (x + y) * -0.178275657951399372 + (z + w) * 0.215623393288842828; + double t2 = (z + w) * -0.403949762580207112 + (x + y) * -0.375199083010075342; + double xs = x + s2, ys = y + s2, zs = z + t2, ws = w + t2; + + return noise4_UnskewedBase(xs, ys, zs, ws); + } + + /** + * 4D OpenSimplex2 noise, fallback lattice orientation. + */ + public float noise4_Fallback(double x, double y, double z, double w) { + + // Get points for A4 lattice + double s = SKEW_4D * (x + y + z + w); + double xs = x + s, ys = y + s, zs = z + s, ws = w + s; + + return noise4_UnskewedBase(xs, ys, zs, ws); + } + + /** + * 4D OpenSimplex2 noise base. + */ + private float noise4_UnskewedBase(double xs, double ys, double zs, double ws) { + + // Get base points and offsets + int xsb = fastFloor(xs), ysb = fastFloor(ys), zsb = fastFloor(zs), wsb = fastFloor(ws); + float xsi = (float)(xs - xsb), ysi = (float)(ys - ysb), zsi = (float)(zs - zsb), wsi = (float)(ws - wsb); + + // Determine which lattice we can be confident has a contributing point its corresponding cell's base simplex. + // We only look at the spaces between the diagonal planes. This proved effective in all of my tests. + float siSum = (xsi + ysi) + (zsi + wsi); + int startingLattice = (int)(siSum * 1.25); + + long pSeed = seed; + + // Offset for seed based on first lattice copy. + seed += startingLattice * SEED_OFFSET_4D; + + // Offset for lattice point relative positions (skewed) + float startingLatticeOffset = startingLattice * -LATTICE_STEP_4D; + xsi += startingLatticeOffset; ysi += startingLatticeOffset; zsi += startingLatticeOffset; wsi += startingLatticeOffset; + + // Prep for vertex contributions. + float ssi = (siSum + startingLatticeOffset * 4) * UNSKEW_4D; + + // Prime pre-multiplication for hash. + long xsvp = xsb * PRIME_X, ysvp = ysb * PRIME_Y, zsvp = zsb * PRIME_Z, wsvp = wsb * PRIME_W; + + // Five points to add, total, from five copies of the A4 lattice. + float value = 0; + for (int i = 0; ; i++) { + + // Next point is the closest vertex on the 4-simplex whose base vertex is the aforementioned vertex. + double score0 = 1.0 + ssi * (-1.0 / UNSKEW_4D); // Seems slightly faster than 1.0-xsi-ysi-zsi-wsi + if (xsi >= ysi && xsi >= zsi && xsi >= wsi && xsi >= score0) { + xsvp += PRIME_X; + xsi -= 1; + ssi -= UNSKEW_4D; + } + else if (ysi > xsi && ysi >= zsi && ysi >= wsi && ysi >= score0) { + ysvp += PRIME_Y; + ysi -= 1; + ssi -= UNSKEW_4D; + } + else if (zsi > xsi && zsi > ysi && zsi >= wsi && zsi >= score0) { + zsvp += PRIME_Z; + zsi -= 1; + ssi -= UNSKEW_4D; + } + else if (wsi > xsi && wsi > ysi && wsi > zsi && wsi >= score0) { + wsvp += PRIME_W; + wsi -= 1; + ssi -= UNSKEW_4D; + } + + // gradient contribution with falloff. + float dx = xsi + ssi, dy = ysi + ssi, dz = zsi + ssi, dw = wsi + ssi; + float a = (dx * dx + dy * dy) + (dz * dz + dw * dw); + if (a < RSQUARED_4D) { + a -= RSQUARED_4D; + a *= a; + value += a * a * grad(xsvp, ysvp, zsvp, wsvp, dx, dy, dz, dw); + } + + // Break from loop if we're done, skipping updates below. + if (i == 4) break; + + // Update for next lattice copy shifted down by <-0.2, -0.2, -0.2, -0.2>. + xsi += LATTICE_STEP_4D; ysi += LATTICE_STEP_4D; zsi += LATTICE_STEP_4D; wsi += LATTICE_STEP_4D; + ssi += LATTICE_STEP_4D * 4 * UNSKEW_4D; + seed -= SEED_OFFSET_4D; + + // Because we don't always start on the same lattice copy, there's a special reset case. + if (i == startingLattice) { + xsvp -= PRIME_X; + ysvp -= PRIME_Y; + zsvp -= PRIME_Z; + wsvp -= PRIME_W; + seed += SEED_OFFSET_4D * 5; + } + } + + seed = pSeed; + + return value; + } + + /* + * Utility + */ + + private float grad(long xsvp, long ysvp, float dx, float dy) { + long hash = seed ^ xsvp ^ ysvp; + hash *= HASH_MULTIPLIER; + hash ^= hash >> (64 - N_GRADS_2D_EXPONENT + 1); + int gi = (int)hash & ((N_GRADS_2D - 1) << 1); + return GRADIENTS_2D[gi | 0] * dx + GRADIENTS_2D[gi | 1] * dy; + } + + private float grad(long xrvp, long yrvp, long zrvp, float dx, float dy, float dz) { + long hash = (seed ^ xrvp) ^ (yrvp ^ zrvp); + hash *= HASH_MULTIPLIER; + hash ^= hash >> (64 - N_GRADS_3D_EXPONENT + 2); + int gi = (int)hash & ((N_GRADS_3D - 1) << 2); + return GRADIENTS_3D[gi | 0] * dx + GRADIENTS_3D[gi | 1] * dy + GRADIENTS_3D[gi | 2] * dz; + } + + private float grad(long xsvp, long ysvp, long zsvp, long wsvp, float dx, float dy, float dz, float dw) { + long hash = seed ^ (xsvp ^ ysvp) ^ (zsvp ^ wsvp); + hash *= HASH_MULTIPLIER; + hash ^= hash >> (64 - N_GRADS_4D_EXPONENT + 2); + int gi = (int)hash & ((N_GRADS_4D - 1) << 2); + return (GRADIENTS_4D[gi | 0] * dx + GRADIENTS_4D[gi | 1] * dy) + (GRADIENTS_4D[gi | 2] * dz + GRADIENTS_4D[gi | 3] * dw); + } + + private int fastFloor(double x) { + int xi = (int)x; + return x < xi ? xi - 1 : xi; + } + + private int fastRound(double x) { + return x < 0 ? (int)(x - 0.5) : (int)(x + 0.5); + } + + /* + * gradients + */ + + private float[] GRADIENTS_2D; + private float[] GRADIENTS_3D; + private float[] GRADIENTS_4D; + { + + GRADIENTS_2D = new float[N_GRADS_2D * 2]; + float[] grad2 = { + 0.38268343236509f, 0.923879532511287f, + 0.923879532511287f, 0.38268343236509f, + 0.923879532511287f, -0.38268343236509f, + 0.38268343236509f, -0.923879532511287f, + -0.38268343236509f, -0.923879532511287f, + -0.923879532511287f, -0.38268343236509f, + -0.923879532511287f, 0.38268343236509f, + -0.38268343236509f, 0.923879532511287f, + //-------------------------------------// + 0.130526192220052f, 0.99144486137381f, + 0.608761429008721f, 0.793353340291235f, + 0.793353340291235f, 0.608761429008721f, + 0.99144486137381f, 0.130526192220051f, + 0.99144486137381f, -0.130526192220051f, + 0.793353340291235f, -0.60876142900872f, + 0.608761429008721f, -0.793353340291235f, + 0.130526192220052f, -0.99144486137381f, + -0.130526192220052f, -0.99144486137381f, + -0.608761429008721f, -0.793353340291235f, + -0.793353340291235f, -0.608761429008721f, + -0.99144486137381f, -0.130526192220052f, + -0.99144486137381f, 0.130526192220051f, + -0.793353340291235f, 0.608761429008721f, + -0.608761429008721f, 0.793353340291235f, + -0.130526192220052f, 0.99144486137381f, + }; + for (int i = 0; i < grad2.length; i++) { + grad2[i] = (float)(grad2[i] / NORMALIZER_2D); + } + for (int i = 0, j = 0; i < GRADIENTS_2D.length; i++, j++) { + if (j == grad2.length) j = 0; + GRADIENTS_2D[i] = grad2[j]; + } + + GRADIENTS_3D = new float[N_GRADS_3D * 4]; + float[] grad3 = { + 2.22474487139f, 2.22474487139f, -1.0f, 0.0f, + 2.22474487139f, 2.22474487139f, 1.0f, 0.0f, + 3.0862664687972017f, 1.1721513422464978f, 0.0f, 0.0f, + 1.1721513422464978f, 3.0862664687972017f, 0.0f, 0.0f, + -2.22474487139f, 2.22474487139f, -1.0f, 0.0f, + -2.22474487139f, 2.22474487139f, 1.0f, 0.0f, + -1.1721513422464978f, 3.0862664687972017f, 0.0f, 0.0f, + -3.0862664687972017f, 1.1721513422464978f, 0.0f, 0.0f, + -1.0f, -2.22474487139f, -2.22474487139f, 0.0f, + 1.0f, -2.22474487139f, -2.22474487139f, 0.0f, + 0.0f, -3.0862664687972017f, -1.1721513422464978f, 0.0f, + 0.0f, -1.1721513422464978f, -3.0862664687972017f, 0.0f, + -1.0f, -2.22474487139f, 2.22474487139f, 0.0f, + 1.0f, -2.22474487139f, 2.22474487139f, 0.0f, + 0.0f, -1.1721513422464978f, 3.0862664687972017f, 0.0f, + 0.0f, -3.0862664687972017f, 1.1721513422464978f, 0.0f, + //--------------------------------------------------------------------// + -2.22474487139f, -2.22474487139f, -1.0f, 0.0f, + -2.22474487139f, -2.22474487139f, 1.0f, 0.0f, + -3.0862664687972017f, -1.1721513422464978f, 0.0f, 0.0f, + -1.1721513422464978f, -3.0862664687972017f, 0.0f, 0.0f, + -2.22474487139f, -1.0f, -2.22474487139f, 0.0f, + -2.22474487139f, 1.0f, -2.22474487139f, 0.0f, + -1.1721513422464978f, 0.0f, -3.0862664687972017f, 0.0f, + -3.0862664687972017f, 0.0f, -1.1721513422464978f, 0.0f, + -2.22474487139f, -1.0f, 2.22474487139f, 0.0f, + -2.22474487139f, 1.0f, 2.22474487139f, 0.0f, + -3.0862664687972017f, 0.0f, 1.1721513422464978f, 0.0f, + -1.1721513422464978f, 0.0f, 3.0862664687972017f, 0.0f, + -1.0f, 2.22474487139f, -2.22474487139f, 0.0f, + 1.0f, 2.22474487139f, -2.22474487139f, 0.0f, + 0.0f, 1.1721513422464978f, -3.0862664687972017f, 0.0f, + 0.0f, 3.0862664687972017f, -1.1721513422464978f, 0.0f, + -1.0f, 2.22474487139f, 2.22474487139f, 0.0f, + 1.0f, 2.22474487139f, 2.22474487139f, 0.0f, + 0.0f, 3.0862664687972017f, 1.1721513422464978f, 0.0f, + 0.0f, 1.1721513422464978f, 3.0862664687972017f, 0.0f, + 2.22474487139f, -2.22474487139f, -1.0f, 0.0f, + 2.22474487139f, -2.22474487139f, 1.0f, 0.0f, + 1.1721513422464978f, -3.0862664687972017f, 0.0f, 0.0f, + 3.0862664687972017f, -1.1721513422464978f, 0.0f, 0.0f, + 2.22474487139f, -1.0f, -2.22474487139f, 0.0f, + 2.22474487139f, 1.0f, -2.22474487139f, 0.0f, + 3.0862664687972017f, 0.0f, -1.1721513422464978f, 0.0f, + 1.1721513422464978f, 0.0f, -3.0862664687972017f, 0.0f, + 2.22474487139f, -1.0f, 2.22474487139f, 0.0f, + 2.22474487139f, 1.0f, 2.22474487139f, 0.0f, + 1.1721513422464978f, 0.0f, 3.0862664687972017f, 0.0f, + 3.0862664687972017f, 0.0f, 1.1721513422464978f, 0.0f, + }; + for (int i = 0; i < grad3.length; i++) { + grad3[i] = (float)(grad3[i] / NORMALIZER_3D); + } + for (int i = 0, j = 0; i < GRADIENTS_3D.length; i++, j++) { + if (j == grad3.length) j = 0; + GRADIENTS_3D[i] = grad3[j]; + } + + GRADIENTS_4D = new float[N_GRADS_4D * 4]; + float[] grad4 = { + -0.6740059517812944f, -0.3239847771997537f, -0.3239847771997537f, 0.5794684678643381f, + -0.7504883828755602f, -0.4004672082940195f, 0.15296486218853164f, 0.5029860367700724f, + -0.7504883828755602f, 0.15296486218853164f, -0.4004672082940195f, 0.5029860367700724f, + -0.8828161875373585f, 0.08164729285680945f, 0.08164729285680945f, 0.4553054119602712f, + -0.4553054119602712f, -0.08164729285680945f, -0.08164729285680945f, 0.8828161875373585f, + -0.5029860367700724f, -0.15296486218853164f, 0.4004672082940195f, 0.7504883828755602f, + -0.5029860367700724f, 0.4004672082940195f, -0.15296486218853164f, 0.7504883828755602f, + -0.5794684678643381f, 0.3239847771997537f, 0.3239847771997537f, 0.6740059517812944f, + -0.6740059517812944f, -0.3239847771997537f, 0.5794684678643381f, -0.3239847771997537f, + -0.7504883828755602f, -0.4004672082940195f, 0.5029860367700724f, 0.15296486218853164f, + -0.7504883828755602f, 0.15296486218853164f, 0.5029860367700724f, -0.4004672082940195f, + -0.8828161875373585f, 0.08164729285680945f, 0.4553054119602712f, 0.08164729285680945f, + -0.4553054119602712f, -0.08164729285680945f, 0.8828161875373585f, -0.08164729285680945f, + -0.5029860367700724f, -0.15296486218853164f, 0.7504883828755602f, 0.4004672082940195f, + -0.5029860367700724f, 0.4004672082940195f, 0.7504883828755602f, -0.15296486218853164f, + -0.5794684678643381f, 0.3239847771997537f, 0.6740059517812944f, 0.3239847771997537f, + -0.6740059517812944f, 0.5794684678643381f, -0.3239847771997537f, -0.3239847771997537f, + -0.7504883828755602f, 0.5029860367700724f, -0.4004672082940195f, 0.15296486218853164f, + -0.7504883828755602f, 0.5029860367700724f, 0.15296486218853164f, -0.4004672082940195f, + -0.8828161875373585f, 0.4553054119602712f, 0.08164729285680945f, 0.08164729285680945f, + -0.4553054119602712f, 0.8828161875373585f, -0.08164729285680945f, -0.08164729285680945f, + -0.5029860367700724f, 0.7504883828755602f, -0.15296486218853164f, 0.4004672082940195f, + -0.5029860367700724f, 0.7504883828755602f, 0.4004672082940195f, -0.15296486218853164f, + -0.5794684678643381f, 0.6740059517812944f, 0.3239847771997537f, 0.3239847771997537f, + 0.5794684678643381f, -0.6740059517812944f, -0.3239847771997537f, -0.3239847771997537f, + 0.5029860367700724f, -0.7504883828755602f, -0.4004672082940195f, 0.15296486218853164f, + 0.5029860367700724f, -0.7504883828755602f, 0.15296486218853164f, -0.4004672082940195f, + 0.4553054119602712f, -0.8828161875373585f, 0.08164729285680945f, 0.08164729285680945f, + 0.8828161875373585f, -0.4553054119602712f, -0.08164729285680945f, -0.08164729285680945f, + 0.7504883828755602f, -0.5029860367700724f, -0.15296486218853164f, 0.4004672082940195f, + 0.7504883828755602f, -0.5029860367700724f, 0.4004672082940195f, -0.15296486218853164f, + 0.6740059517812944f, -0.5794684678643381f, 0.3239847771997537f, 0.3239847771997537f, + //------------------------------------------------------------------------------------------// + -0.753341017856078f, -0.37968289875261624f, -0.37968289875261624f, -0.37968289875261624f, + -0.7821684431180708f, -0.4321472685365301f, -0.4321472685365301f, 0.12128480194602098f, + -0.7821684431180708f, -0.4321472685365301f, 0.12128480194602098f, -0.4321472685365301f, + -0.7821684431180708f, 0.12128480194602098f, -0.4321472685365301f, -0.4321472685365301f, + -0.8586508742123365f, -0.508629699630796f, 0.044802370851755174f, 0.044802370851755174f, + -0.8586508742123365f, 0.044802370851755174f, -0.508629699630796f, 0.044802370851755174f, + -0.8586508742123365f, 0.044802370851755174f, 0.044802370851755174f, -0.508629699630796f, + -0.9982828964265062f, -0.03381941603233842f, -0.03381941603233842f, -0.03381941603233842f, + -0.37968289875261624f, -0.753341017856078f, -0.37968289875261624f, -0.37968289875261624f, + -0.4321472685365301f, -0.7821684431180708f, -0.4321472685365301f, 0.12128480194602098f, + -0.4321472685365301f, -0.7821684431180708f, 0.12128480194602098f, -0.4321472685365301f, + 0.12128480194602098f, -0.7821684431180708f, -0.4321472685365301f, -0.4321472685365301f, + -0.508629699630796f, -0.8586508742123365f, 0.044802370851755174f, 0.044802370851755174f, + 0.044802370851755174f, -0.8586508742123365f, -0.508629699630796f, 0.044802370851755174f, + 0.044802370851755174f, -0.8586508742123365f, 0.044802370851755174f, -0.508629699630796f, + -0.03381941603233842f, -0.9982828964265062f, -0.03381941603233842f, -0.03381941603233842f, + -0.37968289875261624f, -0.37968289875261624f, -0.753341017856078f, -0.37968289875261624f, + -0.4321472685365301f, -0.4321472685365301f, -0.7821684431180708f, 0.12128480194602098f, + -0.4321472685365301f, 0.12128480194602098f, -0.7821684431180708f, -0.4321472685365301f, + 0.12128480194602098f, -0.4321472685365301f, -0.7821684431180708f, -0.4321472685365301f, + -0.508629699630796f, 0.044802370851755174f, -0.8586508742123365f, 0.044802370851755174f, + 0.044802370851755174f, -0.508629699630796f, -0.8586508742123365f, 0.044802370851755174f, + 0.044802370851755174f, 0.044802370851755174f, -0.8586508742123365f, -0.508629699630796f, + -0.03381941603233842f, -0.03381941603233842f, -0.9982828964265062f, -0.03381941603233842f, + -0.37968289875261624f, -0.37968289875261624f, -0.37968289875261624f, -0.753341017856078f, + -0.4321472685365301f, -0.4321472685365301f, 0.12128480194602098f, -0.7821684431180708f, + -0.4321472685365301f, 0.12128480194602098f, -0.4321472685365301f, -0.7821684431180708f, + 0.12128480194602098f, -0.4321472685365301f, -0.4321472685365301f, -0.7821684431180708f, + -0.508629699630796f, 0.044802370851755174f, 0.044802370851755174f, -0.8586508742123365f, + 0.044802370851755174f, -0.508629699630796f, 0.044802370851755174f, -0.8586508742123365f, + 0.044802370851755174f, 0.044802370851755174f, -0.508629699630796f, -0.8586508742123365f, + -0.03381941603233842f, -0.03381941603233842f, -0.03381941603233842f, -0.9982828964265062f, + -0.3239847771997537f, -0.6740059517812944f, -0.3239847771997537f, 0.5794684678643381f, + -0.4004672082940195f, -0.7504883828755602f, 0.15296486218853164f, 0.5029860367700724f, + 0.15296486218853164f, -0.7504883828755602f, -0.4004672082940195f, 0.5029860367700724f, + 0.08164729285680945f, -0.8828161875373585f, 0.08164729285680945f, 0.4553054119602712f, + -0.08164729285680945f, -0.4553054119602712f, -0.08164729285680945f, 0.8828161875373585f, + -0.15296486218853164f, -0.5029860367700724f, 0.4004672082940195f, 0.7504883828755602f, + 0.4004672082940195f, -0.5029860367700724f, -0.15296486218853164f, 0.7504883828755602f, + 0.3239847771997537f, -0.5794684678643381f, 0.3239847771997537f, 0.6740059517812944f, + -0.3239847771997537f, -0.3239847771997537f, -0.6740059517812944f, 0.5794684678643381f, + -0.4004672082940195f, 0.15296486218853164f, -0.7504883828755602f, 0.5029860367700724f, + 0.15296486218853164f, -0.4004672082940195f, -0.7504883828755602f, 0.5029860367700724f, + 0.08164729285680945f, 0.08164729285680945f, -0.8828161875373585f, 0.4553054119602712f, + -0.08164729285680945f, -0.08164729285680945f, -0.4553054119602712f, 0.8828161875373585f, + -0.15296486218853164f, 0.4004672082940195f, -0.5029860367700724f, 0.7504883828755602f, + 0.4004672082940195f, -0.15296486218853164f, -0.5029860367700724f, 0.7504883828755602f, + 0.3239847771997537f, 0.3239847771997537f, -0.5794684678643381f, 0.6740059517812944f, + -0.3239847771997537f, -0.6740059517812944f, 0.5794684678643381f, -0.3239847771997537f, + -0.4004672082940195f, -0.7504883828755602f, 0.5029860367700724f, 0.15296486218853164f, + 0.15296486218853164f, -0.7504883828755602f, 0.5029860367700724f, -0.4004672082940195f, + 0.08164729285680945f, -0.8828161875373585f, 0.4553054119602712f, 0.08164729285680945f, + -0.08164729285680945f, -0.4553054119602712f, 0.8828161875373585f, -0.08164729285680945f, + -0.15296486218853164f, -0.5029860367700724f, 0.7504883828755602f, 0.4004672082940195f, + 0.4004672082940195f, -0.5029860367700724f, 0.7504883828755602f, -0.15296486218853164f, + 0.3239847771997537f, -0.5794684678643381f, 0.6740059517812944f, 0.3239847771997537f, + -0.3239847771997537f, -0.3239847771997537f, 0.5794684678643381f, -0.6740059517812944f, + -0.4004672082940195f, 0.15296486218853164f, 0.5029860367700724f, -0.7504883828755602f, + 0.15296486218853164f, -0.4004672082940195f, 0.5029860367700724f, -0.7504883828755602f, + 0.08164729285680945f, 0.08164729285680945f, 0.4553054119602712f, -0.8828161875373585f, + -0.08164729285680945f, -0.08164729285680945f, 0.8828161875373585f, -0.4553054119602712f, + -0.15296486218853164f, 0.4004672082940195f, 0.7504883828755602f, -0.5029860367700724f, + 0.4004672082940195f, -0.15296486218853164f, 0.7504883828755602f, -0.5029860367700724f, + 0.3239847771997537f, 0.3239847771997537f, 0.6740059517812944f, -0.5794684678643381f, + -0.3239847771997537f, 0.5794684678643381f, -0.6740059517812944f, -0.3239847771997537f, + -0.4004672082940195f, 0.5029860367700724f, -0.7504883828755602f, 0.15296486218853164f, + 0.15296486218853164f, 0.5029860367700724f, -0.7504883828755602f, -0.4004672082940195f, + 0.08164729285680945f, 0.4553054119602712f, -0.8828161875373585f, 0.08164729285680945f, + -0.08164729285680945f, 0.8828161875373585f, -0.4553054119602712f, -0.08164729285680945f, + -0.15296486218853164f, 0.7504883828755602f, -0.5029860367700724f, 0.4004672082940195f, + 0.4004672082940195f, 0.7504883828755602f, -0.5029860367700724f, -0.15296486218853164f, + 0.3239847771997537f, 0.6740059517812944f, -0.5794684678643381f, 0.3239847771997537f, + -0.3239847771997537f, 0.5794684678643381f, -0.3239847771997537f, -0.6740059517812944f, + -0.4004672082940195f, 0.5029860367700724f, 0.15296486218853164f, -0.7504883828755602f, + 0.15296486218853164f, 0.5029860367700724f, -0.4004672082940195f, -0.7504883828755602f, + 0.08164729285680945f, 0.4553054119602712f, 0.08164729285680945f, -0.8828161875373585f, + -0.08164729285680945f, 0.8828161875373585f, -0.08164729285680945f, -0.4553054119602712f, + -0.15296486218853164f, 0.7504883828755602f, 0.4004672082940195f, -0.5029860367700724f, + 0.4004672082940195f, 0.7504883828755602f, -0.15296486218853164f, -0.5029860367700724f, + 0.3239847771997537f, 0.6740059517812944f, 0.3239847771997537f, -0.5794684678643381f, + 0.5794684678643381f, -0.3239847771997537f, -0.6740059517812944f, -0.3239847771997537f, + 0.5029860367700724f, -0.4004672082940195f, -0.7504883828755602f, 0.15296486218853164f, + 0.5029860367700724f, 0.15296486218853164f, -0.7504883828755602f, -0.4004672082940195f, + 0.4553054119602712f, 0.08164729285680945f, -0.8828161875373585f, 0.08164729285680945f, + 0.8828161875373585f, -0.08164729285680945f, -0.4553054119602712f, -0.08164729285680945f, + 0.7504883828755602f, -0.15296486218853164f, -0.5029860367700724f, 0.4004672082940195f, + 0.7504883828755602f, 0.4004672082940195f, -0.5029860367700724f, -0.15296486218853164f, + 0.6740059517812944f, 0.3239847771997537f, -0.5794684678643381f, 0.3239847771997537f, + 0.5794684678643381f, -0.3239847771997537f, -0.3239847771997537f, -0.6740059517812944f, + 0.5029860367700724f, -0.4004672082940195f, 0.15296486218853164f, -0.7504883828755602f, + 0.5029860367700724f, 0.15296486218853164f, -0.4004672082940195f, -0.7504883828755602f, + 0.4553054119602712f, 0.08164729285680945f, 0.08164729285680945f, -0.8828161875373585f, + 0.8828161875373585f, -0.08164729285680945f, -0.08164729285680945f, -0.4553054119602712f, + 0.7504883828755602f, -0.15296486218853164f, 0.4004672082940195f, -0.5029860367700724f, + 0.7504883828755602f, 0.4004672082940195f, -0.15296486218853164f, -0.5029860367700724f, + 0.6740059517812944f, 0.3239847771997537f, 0.3239847771997537f, -0.5794684678643381f, + 0.03381941603233842f, 0.03381941603233842f, 0.03381941603233842f, 0.9982828964265062f, + -0.044802370851755174f, -0.044802370851755174f, 0.508629699630796f, 0.8586508742123365f, + -0.044802370851755174f, 0.508629699630796f, -0.044802370851755174f, 0.8586508742123365f, + -0.12128480194602098f, 0.4321472685365301f, 0.4321472685365301f, 0.7821684431180708f, + 0.508629699630796f, -0.044802370851755174f, -0.044802370851755174f, 0.8586508742123365f, + 0.4321472685365301f, -0.12128480194602098f, 0.4321472685365301f, 0.7821684431180708f, + 0.4321472685365301f, 0.4321472685365301f, -0.12128480194602098f, 0.7821684431180708f, + 0.37968289875261624f, 0.37968289875261624f, 0.37968289875261624f, 0.753341017856078f, + 0.03381941603233842f, 0.03381941603233842f, 0.9982828964265062f, 0.03381941603233842f, + -0.044802370851755174f, 0.044802370851755174f, 0.8586508742123365f, 0.508629699630796f, + -0.044802370851755174f, 0.508629699630796f, 0.8586508742123365f, -0.044802370851755174f, + -0.12128480194602098f, 0.4321472685365301f, 0.7821684431180708f, 0.4321472685365301f, + 0.508629699630796f, -0.044802370851755174f, 0.8586508742123365f, -0.044802370851755174f, + 0.4321472685365301f, -0.12128480194602098f, 0.7821684431180708f, 0.4321472685365301f, + 0.4321472685365301f, 0.4321472685365301f, 0.7821684431180708f, -0.12128480194602098f, + 0.37968289875261624f, 0.37968289875261624f, 0.753341017856078f, 0.37968289875261624f, + 0.03381941603233842f, 0.9982828964265062f, 0.03381941603233842f, 0.03381941603233842f, + -0.044802370851755174f, 0.8586508742123365f, -0.044802370851755174f, 0.508629699630796f, + -0.044802370851755174f, 0.8586508742123365f, 0.508629699630796f, -0.044802370851755174f, + -0.12128480194602098f, 0.7821684431180708f, 0.4321472685365301f, 0.4321472685365301f, + 0.508629699630796f, 0.8586508742123365f, -0.044802370851755174f, -0.044802370851755174f, + 0.4321472685365301f, 0.7821684431180708f, -0.12128480194602098f, 0.4321472685365301f, + 0.4321472685365301f, 0.7821684431180708f, 0.4321472685365301f, -0.12128480194602098f, + 0.37968289875261624f, 0.753341017856078f, 0.37968289875261624f, 0.37968289875261624f, + 0.9982828964265062f, 0.03381941603233842f, 0.03381941603233842f, 0.03381941603233842f, + 0.8586508742123365f, -0.044802370851755174f, -0.044802370851755174f, 0.508629699630796f, + 0.8586508742123365f, -0.044802370851755174f, 0.508629699630796f, -0.044802370851755174f, + 0.7821684431180708f, -0.12128480194602098f, 0.4321472685365301f, 0.4321472685365301f, + 0.8586508742123365f, 0.508629699630796f, -0.044802370851755174f, -0.044802370851755174f, + 0.7821684431180708f, 0.4321472685365301f, -0.12128480194602098f, 0.4321472685365301f, + 0.7821684431180708f, 0.4321472685365301f, 0.4321472685365301f, -0.12128480194602098f, + 0.753341017856078f, 0.37968289875261624f, 0.37968289875261624f, 0.37968289875261624f, + }; + for (int i = 0; i < grad4.length; i++) { + grad4[i] = (float)(grad4[i] / NORMALIZER_4D); + } + for (int i = 0, j = 0; i < GRADIENTS_4D.length; i++, j++) { + if (j == grad4.length) j = 0; + GRADIENTS_4D[i] = grad4[j]; + } + } +}