├── settings.gradle ├── .artwork ├── ingame.png └── smooth_lightning.png ├── run └── natives │ ├── lwjgl.dll │ ├── lwjgl64.dll │ ├── OpenAL32.dll │ └── OpenAL64.dll ├── src └── main │ ├── resources │ ├── font.png │ ├── icons.png │ └── terrain.png │ └── java │ └── de │ └── labystudio │ ├── game │ ├── util │ │ ├── EnumWorldBlockLayer.java │ │ ├── MathHelper.java │ │ ├── HitResult.java │ │ ├── EnumBlockFace.java │ │ ├── Timer.java │ │ ├── TextureManager.java │ │ └── BoundingBox.java │ ├── world │ │ ├── generator │ │ │ ├── NoiseGenerator.java │ │ │ ├── noise │ │ │ │ ├── NoiseGeneratorCombined.java │ │ │ │ ├── NoiseGeneratorOctaves.java │ │ │ │ └── NoiseGeneratorPerlin.java │ │ │ └── WorldGenerator.java │ │ ├── block │ │ │ ├── BlockDirt.java │ │ │ ├── BlockSand.java │ │ │ ├── BlockStone.java │ │ │ ├── BlockLeave.java │ │ │ ├── BlockLog.java │ │ │ ├── BlockGrass.java │ │ │ ├── BlockPos.java │ │ │ ├── BlockWater.java │ │ │ └── Block.java │ │ ├── chunk │ │ │ ├── format │ │ │ │ ├── WorldLoadingProgress.java │ │ │ │ ├── WorldFormat.java │ │ │ │ ├── ChunkFormat.java │ │ │ │ └── RegionFormat.java │ │ │ ├── Chunk.java │ │ │ └── ChunkSection.java │ │ ├── WorldRenderer.java │ │ └── World.java │ ├── render │ │ ├── world │ │ │ ├── IWorldAccess.java │ │ │ └── BlockRenderer.java │ │ ├── GLAllocation.java │ │ ├── gui │ │ │ ├── GuiRenderer.java │ │ │ └── FontRenderer.java │ │ ├── Tessellator.java │ │ └── Frustum.java │ ├── MinecraftWindow.java │ ├── Minecraft.java │ └── player │ │ └── Player.java │ └── tools │ └── GLConverter.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── README.md ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'java-minecraft' 2 | 3 | -------------------------------------------------------------------------------- /.artwork/ingame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/.artwork/ingame.png -------------------------------------------------------------------------------- /run/natives/lwjgl.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/run/natives/lwjgl.dll -------------------------------------------------------------------------------- /run/natives/lwjgl64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/run/natives/lwjgl64.dll -------------------------------------------------------------------------------- /run/natives/OpenAL32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/run/natives/OpenAL32.dll -------------------------------------------------------------------------------- /run/natives/OpenAL64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/run/natives/OpenAL64.dll -------------------------------------------------------------------------------- /src/main/resources/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/src/main/resources/font.png -------------------------------------------------------------------------------- /src/main/resources/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/src/main/resources/icons.png -------------------------------------------------------------------------------- /.artwork/smooth_lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/.artwork/smooth_lightning.png -------------------------------------------------------------------------------- /src/main/resources/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/src/main/resources/terrain.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LabyStudio/java-minecraft/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/EnumWorldBlockLayer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public enum EnumWorldBlockLayer { 4 | SOLID, 5 | CUTOUT 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/* 4 | .idea 5 | /bin/ 6 | .idea 7 | out 8 | .gradle 9 | *.iml 10 | build 11 | 12 | # Exclude run except natives 13 | !run/ 14 | run/* 15 | !run/natives/ -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/generator/NoiseGenerator.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.generator; 2 | 3 | public abstract class NoiseGenerator { 4 | public abstract double perlin(double x, double z); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/world/IWorldAccess.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render.world; 2 | 3 | public interface IWorldAccess { 4 | 5 | short getBlockAt(int x, int y, int z); 6 | 7 | int getLightAt(int x, int y, int z); 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockDirt.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | public class BlockDirt extends Block { 4 | 5 | public BlockDirt(int id, int textureSlot) { 6 | super(id, textureSlot); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockSand.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | public class BlockSand extends Block { 4 | 5 | public BlockSand(int id, int textureSlot) { 6 | super(id, textureSlot); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockStone.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | public class BlockStone extends Block { 4 | 5 | public BlockStone(int id, int textureSlot) { 6 | super(id, textureSlot); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/format/WorldLoadingProgress.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk.format; 2 | 3 | import de.labystudio.game.world.chunk.ChunkSection; 4 | 5 | public interface WorldLoadingProgress { 6 | 7 | void onLoad(int x, int z, ChunkSection[] chunkSectionLayers); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockLeave.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | public class BlockLeave extends Block { 4 | 5 | public BlockLeave(int id, int textureSlot) { 6 | super(id, textureSlot); 7 | } 8 | 9 | @Override 10 | public float getOpacity() { 11 | return 0.3F; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/MathHelper.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public class MathHelper { 4 | 5 | /** 6 | * Returns the greatest integer less than or equal to the double argument 7 | */ 8 | public static int floor_double(double value) { 9 | int i = (int) value; 10 | return value < (double) i ? i - 1 : i; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/HitResult.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public class HitResult { 4 | public int x; 5 | public int y; 6 | public int z; 7 | public EnumBlockFace face; 8 | 9 | public HitResult(int x, int y, int z, EnumBlockFace face) { 10 | this.x = x; 11 | this.y = y; 12 | this.z = z; 13 | this.face = face; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockLog.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | import de.labystudio.game.util.EnumBlockFace; 4 | 5 | public class BlockLog extends Block { 6 | 7 | public BlockLog(int id, int textureSlot) { 8 | super(id, textureSlot); 9 | } 10 | 11 | @Override 12 | public int getTextureForFace(EnumBlockFace face) { 13 | return this.textureSlotId + (face.isYAxis() ? 1 : 0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockGrass.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | import de.labystudio.game.util.EnumBlockFace; 4 | 5 | public class BlockGrass extends Block { 6 | 7 | public BlockGrass(int id, int textureSlot) { 8 | super(id, textureSlot); 9 | } 10 | 11 | @Override 12 | public int getTextureForFace(EnumBlockFace face) { 13 | switch (face) { 14 | case TOP: 15 | return this.textureSlotId; 16 | case BOTTOM: 17 | return this.textureSlotId + 1; 18 | default: 19 | return this.textureSlotId + 2; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/generator/noise/NoiseGeneratorCombined.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.generator.noise; 2 | 3 | import de.labystudio.game.world.generator.NoiseGenerator; 4 | 5 | public final class NoiseGeneratorCombined extends NoiseGenerator { 6 | 7 | private final NoiseGenerator firstGenerator; 8 | private final NoiseGenerator secondGenerator; 9 | 10 | public NoiseGeneratorCombined(NoiseGenerator firstGenerator, NoiseGenerator secondGenerator) { 11 | this.firstGenerator = firstGenerator; 12 | this.secondGenerator = secondGenerator; 13 | } 14 | 15 | @Override 16 | public final double perlin(double x, double z) { 17 | return this.firstGenerator.perlin(x + this.secondGenerator.perlin(x, z), z); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/EnumBlockFace.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public enum EnumBlockFace { 4 | TOP(0, 1, 0), 5 | BOTTOM(0, -1, 0), 6 | NORTH(-1, 0, 0), 7 | EAST(0, 0, -1), 8 | SOUTH(1, 0, 0), 9 | WEST(0, 0, 1); 10 | 11 | public int x; 12 | public int y; 13 | public int z; 14 | 15 | EnumBlockFace(int x, int y, int z) { 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public float getShading() { 22 | return this.isXAxis() ? 0.6F : this.isYAxis() ? 1.0F : 0.8F; 23 | } 24 | 25 | public boolean isXAxis() { 26 | return this.x != 0; 27 | } 28 | 29 | public boolean isYAxis() { 30 | return this.y != 0; 31 | } 32 | 33 | public boolean isZAxis() { 34 | return this.z != 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockPos.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | import java.util.Objects; 4 | 5 | public class BlockPos { 6 | 7 | private int x; 8 | private int y; 9 | private int z; 10 | 11 | public BlockPos(int x, int y, int z) { 12 | this.x = x; 13 | this.y = y; 14 | this.z = z; 15 | } 16 | 17 | public int getX() { 18 | return x; 19 | } 20 | 21 | public int getY() { 22 | return y; 23 | } 24 | 25 | public int getZ() { 26 | return z; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | BlockPos blockPos = (BlockPos) o; 34 | return x == blockPos.x && 35 | y == blockPos.y && 36 | z == blockPos.z; 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return Objects.hash(x, y, z); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/generator/noise/NoiseGeneratorOctaves.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.generator.noise; 2 | 3 | import de.labystudio.game.world.generator.NoiseGenerator; 4 | 5 | import java.util.Random; 6 | 7 | public final class NoiseGeneratorOctaves extends NoiseGenerator { 8 | 9 | private final NoiseGeneratorPerlin[] generatorCollection; 10 | private final int octaves; 11 | 12 | public NoiseGeneratorOctaves(Random random, int octaves) { 13 | this.octaves = octaves; 14 | this.generatorCollection = new NoiseGeneratorPerlin[octaves]; 15 | 16 | for (int i = 0; i < octaves; ++i) { 17 | this.generatorCollection[i] = new NoiseGeneratorPerlin(random); 18 | } 19 | } 20 | 21 | @Override 22 | public final double perlin(double x, double z) { 23 | double total = 0.0; 24 | double frequency = 1.0; 25 | for (int i = 0; i < this.octaves; ++i) { 26 | total += this.generatorCollection[i].perlin(x / frequency, z / frequency) * frequency; 27 | frequency *= 2.0; 28 | } 29 | return total; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/BlockWater.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | import de.labystudio.game.render.world.IWorldAccess; 4 | import de.labystudio.game.util.BoundingBox; 5 | import de.labystudio.game.util.EnumBlockFace; 6 | 7 | public class BlockWater extends Block { 8 | 9 | public BlockWater(int id, int textureSlot) { 10 | super(id, textureSlot); 11 | } 12 | 13 | @Override 14 | public float getOpacity() { 15 | return 0.3F; 16 | } 17 | 18 | @Override 19 | public boolean isSolid() { 20 | return false; 21 | } 22 | 23 | @Override 24 | public boolean shouldRenderFace(IWorldAccess world, int x, int y, int z, EnumBlockFace face) { 25 | short typeId = world.getBlockAt(x + face.x, y + face.y, z + face.z); 26 | return typeId == 0 || typeId != this.id && Block.getById(typeId).isTransparent(); 27 | } 28 | 29 | @Override 30 | public BoundingBox getBoundingBox(IWorldAccess world, int x, int y, int z) { 31 | BoundingBox aabb = this.boundingBox.clone(); 32 | if (world.getBlockAt(x, y + 1, z) != this.id) { 33 | aabb.maxY = 1.0F - 0.12F; 34 | } 35 | return aabb; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/Chunk.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk; 2 | 3 | import de.labystudio.game.world.World; 4 | import de.labystudio.game.world.WorldRenderer; 5 | 6 | public class Chunk { 7 | 8 | private World world; 9 | private ChunkSection[] sections; 10 | 11 | private final int x; 12 | private final int z; 13 | 14 | public Chunk(World world, int x, int z) { 15 | this.world = world; 16 | this.x = x; 17 | this.z = z; 18 | 19 | this.sections = new ChunkSection[16]; 20 | for (int y = 0; y < 16; y++) { 21 | this.sections[y] = new ChunkSection(world, x, y, z); 22 | } 23 | } 24 | 25 | public ChunkSection getSection(int y) { 26 | return this.sections[y]; 27 | } 28 | 29 | public ChunkSection[] getSections() { 30 | return this.sections; 31 | } 32 | 33 | public void setSections(ChunkSection[] sections) { 34 | this.sections = sections; 35 | } 36 | 37 | public int getX() { 38 | return x; 39 | } 40 | 41 | public int getZ() { 42 | return z; 43 | } 44 | 45 | public boolean isEmpty() { 46 | for (ChunkSection chunkSection : this.sections) { 47 | if (!chunkSection.isEmpty()) { 48 | return false; 49 | } 50 | } 51 | return true; 52 | } 53 | 54 | public void rebuild(WorldRenderer renderer) { 55 | for (ChunkSection chunkSection : this.sections) { 56 | chunkSection.rebuild(renderer); 57 | } 58 | } 59 | 60 | public void queueForRebuild() { 61 | for (ChunkSection chunkSection : this.sections) { 62 | chunkSection.queueForRebuild(); 63 | } 64 | } 65 | 66 | public static long getIndex(int x, int z) { 67 | return (long) x & 4294967295L | ((long) z & 4294967295L) << 32; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft recode in Java 2 | 3 | This is a sandbox that provides all basic features of Minecraft.
4 | The main purpose of this project is to understand the render and physics engine of Minecraft.
5 | It is a fork of the [first version](https://github.com/thecodeofnotch/rd-131655) of Minecraft. 6 | 7 | This project was ported to javascript here: [js-minecraft](https://github.com/LabyStudio/js-minecraft). 8 | 9 | ### Feature Overview 10 | - Block rendering 11 | - Block collision 12 | - Player movement 13 | - Walking 14 | - Sprinting 15 | - Sneaking 16 | - Flying 17 | - Swimming 18 | - Dynamic lightning 19 | - Smooth lightning 20 | - Anvil world loading/saving 21 | - Perlin world generation 22 | - Frustum Culling 23 | - Fog 24 | - Underwater fog 25 | - HUD rendering 26 | - Cross-hair 27 | - Font rendering 28 | - Dynamic FOV 29 | 30 | ![Ingame](.artwork/ingame.png) 31 | 32 | ### Setup 33 | - Clone the project 34 | - Set your working directory to ``./run`` 35 | - Run main class ``de.labystudio.game.Minecraft`` 36 | - Wait a few seconds for the world generation 37 | 38 | ### Controls 39 | ``` 40 | W: Forward 41 | S: Backwards 42 | A: Left 43 | D: Right 44 | 45 | Left click: Destroy block 46 | Right click: Place block 47 | Middle click: Pick block 48 | 49 | Space: Jump 50 | Double Space: Toggle flying 51 | Q: Sneaking 52 | Shift: Sprinting 53 | ESC: Toggle game focus 54 | 55 | R: Return to spawn 56 | ``` 57 | 58 | ### Smooth lightning example 59 | ![Smooth Lightning](.artwork/smooth_lightning.png) 60 | 61 | ### Known issues 62 | - Mouse over block calculation is acting weird and doesn't work sometimes 63 | - No light updates during world generation 64 | 65 | ### Planned 66 | - Generate new chunk if not generated yet (Infinite map) 67 | - Multiplayer 68 | - Entity rendering 69 | - Loading screen 70 | 71 |
72 | 73 | NOT OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG. -------------------------------------------------------------------------------- /src/main/java/de/labystudio/tools/GLConverter.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.tools; 2 | 3 | 4 | import org.lwjgl.opengl.GL11; 5 | 6 | import java.awt.Toolkit; 7 | import java.awt.datatransfer.Clipboard; 8 | import java.awt.datatransfer.DataFlavor; 9 | import java.awt.datatransfer.StringSelection; 10 | import java.lang.reflect.Field; 11 | 12 | public class GLConverter { 13 | 14 | public static void main(String[] args) throws Exception { 15 | String data = (String) Toolkit.getDefaultToolkit() 16 | .getSystemClipboard().getData(DataFlavor.stringFlavor); 17 | 18 | String out = ""; 19 | 20 | for (String line : data.split("\n")) { 21 | 22 | if (line.contains("GL11.") || line.contains("GLU.") || line.contains("ARBVertexBufferObject.")) { 23 | Field[] fields = GL11.class.getFields(); 24 | for (Field field : fields) { 25 | Object value = field.get(null); 26 | 27 | if (value instanceof Integer) { 28 | int i = (int) value; 29 | 30 | 31 | line = line.replace("(" + i + ");", "(GL11." + field.getName() + ");"); 32 | line = line.replace("(" + i + ",", "(GL11." + field.getName() + ","); 33 | line = line.replace("(" + i + ",", "(GL11." + field.getName() +","); 34 | line = line.replace(" " + i + ")", " GL11." + field.getName() + ")"); 35 | line = line.replace(" " + i + ", ", " GL11." + field.getName() + ", "); 36 | } 37 | } 38 | } 39 | 40 | out += line + "\n"; 41 | } 42 | 43 | StringSelection selection = new StringSelection(out); 44 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 45 | clipboard.setContents(selection, selection); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/GLAllocation.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render; 2 | 3 | import org.lwjgl.opengl.GL11; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | import java.nio.FloatBuffer; 8 | import java.nio.IntBuffer; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class GLAllocation { 13 | 14 | private static final List displayLists = new ArrayList<>(); 15 | private static final List textureNames = new ArrayList<>(); 16 | 17 | public static synchronized int generateDisplayLists(int amount) { 18 | int id = GL11.glGenLists(amount); 19 | 20 | displayLists.add(id); 21 | displayLists.add(amount); 22 | 23 | return id; 24 | } 25 | 26 | public static synchronized void generateTextureNames(IntBuffer intbuffer) { 27 | GL11.glGenTextures(intbuffer); 28 | 29 | for (int i = intbuffer.position(); i < intbuffer.limit(); i++) { 30 | textureNames.add(intbuffer.get(i)); 31 | } 32 | } 33 | 34 | public static synchronized void deleteTexturesAndDisplayLists() { 35 | for (int i = 0; i < displayLists.size(); i += 2) { 36 | GL11.glDeleteLists(displayLists.get(i), displayLists.get(i + 1)); 37 | } 38 | 39 | IntBuffer intbuffer = createDirectIntBuffer(textureNames.size()); 40 | intbuffer.flip(); 41 | GL11.glDeleteTextures(intbuffer); 42 | 43 | for (Integer textureName : textureNames) { 44 | intbuffer.put(textureName); 45 | } 46 | 47 | intbuffer.flip(); 48 | GL11.glDeleteTextures(intbuffer); 49 | 50 | displayLists.clear(); 51 | textureNames.clear(); 52 | } 53 | 54 | public static IntBuffer createDirectIntBuffer(int size) { 55 | return createDirectByteBuffer(size << 2).asIntBuffer(); 56 | } 57 | 58 | public static FloatBuffer createDirectFloatBuffer(int size) { 59 | return createDirectByteBuffer(size << 2).asFloatBuffer(); 60 | } 61 | 62 | public static synchronized ByteBuffer createDirectByteBuffer(int size) { 63 | return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/Timer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public class Timer { 4 | 5 | private static final long NS_PER_SECOND = 1000000000L; 6 | private static final long MAX_NS_PER_UPDATE = 1000000000L; 7 | private static final int MAX_TICKS_PER_UPDATE = 100; 8 | 9 | /** 10 | * Amount of ticks per second 11 | */ 12 | private final float ticksPerSecond; 13 | 14 | /** 15 | * Last time updated in nano seconds 16 | */ 17 | private long lastTime = System.nanoTime(); 18 | 19 | /** 20 | * Scale the tick speed 21 | */ 22 | public float timeScale = 1.0F; 23 | 24 | /** 25 | * Framerate of the advanceTime update 26 | */ 27 | public float fps = 0.0F; 28 | 29 | /** 30 | * Passed time since last game update 31 | */ 32 | public float passedTime = 0.0F; 33 | 34 | /** 35 | * The amount of ticks for the current game update. 36 | * It's the passed time as an integer 37 | */ 38 | public int ticks; 39 | 40 | /** 41 | * The overflow of the current tick, caused by casting the passed time to an integer 42 | */ 43 | public float partialTicks; 44 | 45 | /** 46 | * Timer to control the tick speed independently of the framerate 47 | * 48 | * @param ticksPerSecond Amount of ticks per second 49 | */ 50 | public Timer(float ticksPerSecond) { 51 | this.ticksPerSecond = ticksPerSecond; 52 | } 53 | 54 | /** 55 | * This function calculates the amount of ticks required to reach the ticksPerSecond. 56 | * Call this function in the main render loop of the game 57 | */ 58 | public void advanceTime() { 59 | long now = System.nanoTime(); 60 | long passedNs = now - this.lastTime; 61 | 62 | // Store nano time of this update 63 | this.lastTime = now; 64 | 65 | // Maximum and minimum 66 | passedNs = Math.max(0, passedNs); 67 | passedNs = Math.min(MAX_NS_PER_UPDATE, passedNs); 68 | 69 | // Calculate fps 70 | this.fps = (float) (NS_PER_SECOND / passedNs); 71 | 72 | // Calculate passed time and ticks 73 | this.passedTime += passedNs * this.timeScale * this.ticksPerSecond / NS_PER_SECOND; 74 | this.ticks = (int) this.passedTime; 75 | 76 | // Maximum ticks per update 77 | this.ticks = Math.min(MAX_TICKS_PER_UPDATE, this.ticks); 78 | 79 | // Calculate the overflow of the current tick 80 | this.passedTime -= this.ticks; 81 | this.partialTicks = this.passedTime; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/block/Block.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.block; 2 | 3 | import de.labystudio.game.render.world.IWorldAccess; 4 | import de.labystudio.game.util.BoundingBox; 5 | import de.labystudio.game.util.EnumBlockFace; 6 | import de.labystudio.game.world.WorldRenderer; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public abstract class Block { 12 | 13 | private static final Map blocks = new HashMap<>(); 14 | 15 | public static BlockStone STONE = new BlockStone(1, 0); 16 | public static BlockGrass GRASS = new BlockGrass(2, 1); 17 | public static BlockDirt DIRT = new BlockDirt(3, 2); 18 | public static BlockLog LOG = new BlockLog(17, 4); 19 | public static BlockLeave LEAVE = new BlockLeave(18, 6); 20 | public static BlockWater WATER = new BlockWater(9, 7); 21 | public static BlockSand SAND = new BlockSand(12, 8); 22 | 23 | protected final int id; 24 | protected final int textureSlotId; 25 | 26 | // Block bounding box 27 | protected BoundingBox boundingBox = new BoundingBox(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F); 28 | 29 | protected Block(int id) { 30 | this(id, id); 31 | } 32 | 33 | protected Block(int id, int textureSlotId) { 34 | this.id = id; 35 | this.textureSlotId = textureSlotId; 36 | blocks.put((short) id, this); 37 | } 38 | 39 | public static Block getById(short typeId) { 40 | return blocks.get(typeId); 41 | } 42 | 43 | public int getId() { 44 | return id; 45 | } 46 | 47 | public int getTextureForFace(EnumBlockFace face) { 48 | return this.textureSlotId; 49 | } 50 | 51 | public boolean isTransparent() { 52 | return getOpacity() < 1.0F; 53 | } 54 | 55 | public boolean shouldRenderFace(IWorldAccess world, int x, int y, int z, EnumBlockFace face) { 56 | short typeId = world.getBlockAt(x + face.x, y + face.y, z + face.z); 57 | return typeId == 0 || Block.getById(typeId).isTransparent(); 58 | } 59 | 60 | public boolean isSolid() { 61 | return true; 62 | } 63 | 64 | public float getOpacity() { 65 | return 1.0F; 66 | } 67 | 68 | public BoundingBox getBoundingBox(IWorldAccess world, int x, int y, int z) { 69 | return this.boundingBox; 70 | } 71 | 72 | public void render(WorldRenderer worldRenderer, IWorldAccess world, int x, int y, int z) { 73 | worldRenderer.getBlockRenderer().renderBlock(world, this, x, y, z); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/TextureManager.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | import org.lwjgl.BufferUtils; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.image.BufferedImage; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.ByteBuffer; 10 | 11 | import static org.lwjgl.opengl.GL11.*; 12 | import static org.lwjgl.util.glu.GLU.gluBuild2DMipmaps; 13 | 14 | public class TextureManager { 15 | private static int lastId = Integer.MIN_VALUE; 16 | 17 | /** 18 | * Load a texture into OpenGL 19 | * 20 | * @param resourceName Resource path of the image 21 | * @param mode Texture filter mode (GL_NEAREST, GL_LINEAR) 22 | * @return Texture id of OpenGL 23 | */ 24 | public static int loadTexture(String resourceName, int mode) { 25 | // Generate a new texture id 26 | int id = glGenTextures(); 27 | 28 | // Bind this texture id 29 | bind(id); 30 | 31 | // Set texture filter mode 32 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mode); 33 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mode); 34 | 35 | // Read from resources 36 | InputStream inputStream = TextureManager.class.getResourceAsStream(resourceName); 37 | 38 | try { 39 | // Read to buffered image 40 | BufferedImage bufferedImage = ImageIO.read(inputStream); 41 | 42 | // Get image size 43 | int width = bufferedImage.getWidth(); 44 | int height = bufferedImage.getHeight(); 45 | 46 | // Write image pixels into array 47 | int[] pixels = new int[width * height]; 48 | bufferedImage.getRGB(0, 0, width, height, pixels, 0, width); 49 | 50 | // Flip RGB order of the integers 51 | for (int i = 0; i < pixels.length; i++) { 52 | int alpha = pixels[i] >> 24 & 0xFF; 53 | int red = pixels[i] >> 16 & 0xFF; 54 | int green = pixels[i] >> 8 & 0xFF; 55 | int blue = pixels[i] & 0xFF; 56 | 57 | // ARGB to ABGR 58 | pixels[i] = alpha << 24 | blue << 16 | green << 8 | red; 59 | } 60 | 61 | // Create bytebuffer from pixel array 62 | ByteBuffer byteBuffer = BufferUtils.createByteBuffer(width * height * 4); 63 | byteBuffer.asIntBuffer().put(pixels); 64 | 65 | // Write texture to opengl 66 | gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, byteBuffer); 67 | } catch (IOException exception) { 68 | throw new RuntimeException("Could not load texture " + resourceName, exception); 69 | } 70 | 71 | return id; 72 | } 73 | 74 | /** 75 | * Bind the texture to OpenGL using the id from {@link #loadTexture(String, int)} 76 | * 77 | * @param id Texture id 78 | */ 79 | public static void bind(int id) { 80 | if (id != lastId) { 81 | glBindTexture(GL_TEXTURE_2D, id); 82 | lastId = id; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/gui/GuiRenderer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render.gui; 2 | 3 | import de.labystudio.game.MinecraftWindow; 4 | import de.labystudio.game.render.Tessellator; 5 | import de.labystudio.game.util.TextureManager; 6 | import org.lwjgl.opengl.GL11; 7 | 8 | public class GuiRenderer { 9 | 10 | private int textureId; 11 | private final int zLevel = 0; 12 | 13 | private int scaleFactor; 14 | private int width; 15 | private int height; 16 | 17 | public void loadTextures() { 18 | this.textureId = TextureManager.loadTexture("/icons.png", GL11.GL_NEAREST); 19 | } 20 | 21 | public void init(MinecraftWindow gameWindow) { 22 | this.width = gameWindow.displayWidth; 23 | this.height = gameWindow.displayHeight; 24 | for (this.scaleFactor = 1; this.width / (this.scaleFactor + 1) >= 320 && this.height / (this.scaleFactor + 1) >= 240; this.scaleFactor++) { 25 | } 26 | this.width = this.width / this.scaleFactor; 27 | this.height = this.height / this.scaleFactor; 28 | } 29 | 30 | public void setupCamera() { 31 | GL11.glClear(256); 32 | GL11.glMatrixMode(5889); 33 | GL11.glLoadIdentity(); 34 | GL11.glOrtho(0.0D, this.width, this.height, 0.0D, 1000D, 3000D); 35 | GL11.glMatrixMode(5888); 36 | GL11.glLoadIdentity(); 37 | GL11.glTranslatef(0.0F, 0.0F, -2000F); 38 | } 39 | 40 | public void renderCrosshair() { 41 | GL11.glEnable(GL11.GL_BLEND); 42 | GL11.glBlendFunc(GL11.GL_ONE_MINUS_DST_COLOR, GL11.GL_ONE_MINUS_SRC_COLOR); 43 | GL11.glEnable(GL11.GL_TEXTURE_2D); 44 | GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); 45 | 46 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.textureId); 47 | this.drawTexturedModalRect(this.width / 2 - 7, this.height / 2 - 7, 0, 0, 16, 16); 48 | 49 | GL11.glDisable(GL11.GL_BLEND); 50 | } 51 | 52 | public void drawTexturedModalRect(int left, int top, int offsetX, int offsetY, int width, int height) { 53 | Tessellator tessellator = Tessellator.instance; 54 | tessellator.startDrawingQuads(); 55 | this.drawTexturedModalRect(tessellator, left, top, offsetX, offsetY, width, height, width, height, 256, 256); 56 | tessellator.draw(); 57 | } 58 | 59 | public void drawTexturedModalRect(Tessellator tessellator, int x, int y, 60 | int u, int v, int uWidth, int vHeight, 61 | int width, int height, 62 | float bitMapWidth, float bitmapHeight) { 63 | 64 | float factorX = 1.0F / bitMapWidth; 65 | float factorY = 1.0F / bitmapHeight; 66 | 67 | tessellator.addVertexWithUV(x, y + height, this.zLevel, u * factorX, (v + vHeight) * factorY); 68 | tessellator.addVertexWithUV(x + width, y + height, this.zLevel, (u + uWidth) * factorX, (v + vHeight) * factorY); 69 | tessellator.addVertexWithUV(x + width, y, this.zLevel, (u + uWidth) * factorX, v * factorY); 70 | tessellator.addVertexWithUV(x, y, this.zLevel, u * factorX, v * factorY); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/generator/noise/NoiseGeneratorPerlin.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.generator.noise; 2 | 3 | import de.labystudio.game.world.generator.NoiseGenerator; 4 | 5 | import java.util.Random; 6 | 7 | public final class NoiseGeneratorPerlin extends NoiseGenerator { 8 | private final int[] permutations; 9 | 10 | public NoiseGeneratorPerlin() { 11 | this(new Random()); 12 | } 13 | 14 | public NoiseGeneratorPerlin(Random random) { 15 | this.permutations = new int[512]; 16 | for (int i = 0; i < 256; ++i) { 17 | this.permutations[i] = i; 18 | } 19 | for (int i = 0; i < 256; ++i) { 20 | final int n = random.nextInt(256 - i) + i; 21 | final int n2 = this.permutations[i]; 22 | this.permutations[i] = this.permutations[n]; 23 | this.permutations[n] = n2; 24 | this.permutations[i + 256] = this.permutations[i]; 25 | } 26 | } 27 | 28 | private static double fade(double t) { 29 | // Fade function as defined by Ken Perlin. This eases coordinate values 30 | // so that they will "ease" towards integral values. This ends up smoothing 31 | // the final output. 32 | return t * t * t * (t * (t * 6 - 15) + 10); // 6t^5 - 15t^4 + 10t^3 33 | } 34 | 35 | private static double lerp(double x, double a, double b) { 36 | return a + x * (b - a); 37 | } 38 | 39 | private static double grad(int hash, double x, double y, double z) { 40 | int h = hash & 15; // Take the hashed value and take the first 4 bits of it (15 == 0b1111) 41 | double u = h < 8 /* 0b1000 */ ? x : y; // If the most significant bit (MSB) of the hash is 0 then set u = x. Otherwise y. 42 | 43 | double v; // In Ken Perlin's original implementation this was another conditional operator (?:). I 44 | // expanded it for readability. 45 | if (h < 4 /* 0b0100 */) // If the first and second significant bits are 0 set v = y 46 | v = y; 47 | else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/) // If the first and second significant bits are 1 set v = x 48 | v = x; 49 | else // If the first and second significant bits are not equal (0/1, 1/0) set v = z 50 | v = z; 51 | 52 | return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative. Then return their addition. 53 | } 54 | 55 | @Override 56 | public final double perlin(double x, double z) { 57 | double y; 58 | 59 | int xi = (int) Math.floor(x) & 0xFF; 60 | int zi = (int) Math.floor(z) & 0xFF; 61 | int yi = (int) Math.floor(0.0) & 0xFF; 62 | 63 | x -= Math.floor(x); 64 | z -= Math.floor(z); 65 | y = 0.0 - Math.floor(0.0); 66 | 67 | double u = fade(x); 68 | double w = fade(z); 69 | double v = fade(y); 70 | 71 | int xzi = this.permutations[xi] + zi; 72 | int xzyi = this.permutations[xzi] + yi; 73 | 74 | xzi = this.permutations[xzi + 1] + yi; 75 | xi = this.permutations[xi + 1] + zi; 76 | zi = this.permutations[xi] + yi; 77 | xi = this.permutations[xi + 1] + yi; 78 | 79 | return lerp(v, 80 | lerp(w, 81 | lerp(u, 82 | grad(this.permutations[xzyi], x, z, y), 83 | grad(this.permutations[zi], x - 1.0, z, y)), 84 | lerp(u, 85 | grad(this.permutations[xzi], x, z - 1.0, y), 86 | grad(this.permutations[xi], x - 1.0, z - 1.0, y))), 87 | lerp(w, 88 | lerp(u, 89 | grad(this.permutations[xzyi + 1], x, z, y - 1.0), 90 | grad(this.permutations[zi + 1], x - 1.0, z, y - 1.0)), 91 | lerp(u, 92 | grad(this.permutations[xzi + 1], x, z - 1.0, y - 1.0), 93 | grad(this.permutations[xi + 1], x - 1.0, z - 1.0, y - 1.0)))); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/WorldRenderer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world; 2 | 3 | import de.labystudio.game.render.Frustum; 4 | import de.labystudio.game.render.GLAllocation; 5 | import de.labystudio.game.render.world.BlockRenderer; 6 | import de.labystudio.game.util.EnumWorldBlockLayer; 7 | import de.labystudio.game.util.TextureManager; 8 | import de.labystudio.game.world.chunk.Chunk; 9 | import de.labystudio.game.world.chunk.ChunkSection; 10 | import org.lwjgl.opengl.GL11; 11 | 12 | import java.nio.FloatBuffer; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | public class WorldRenderer { 18 | 19 | public static final int RENDER_DISTANCE = 8; 20 | 21 | private final FloatBuffer colorBuffer = GLAllocation.createDirectFloatBuffer(16); 22 | public final int textureId = TextureManager.loadTexture("/terrain.png", GL11.GL_NEAREST); 23 | 24 | private final World world; 25 | 26 | private final BlockRenderer blockRenderer = new BlockRenderer(); 27 | private final Frustum frustum = new Frustum(); 28 | private final List chunkSectionUpdateQueue = new ArrayList<>(); 29 | 30 | public WorldRenderer(World world) { 31 | this.world = world; 32 | 33 | // Sky color 34 | GL11.glClearColor(0.6222222F - 0.05F, 0.5F + 0.1F, 1.0F, 0.0F); 35 | GL11.glClearDepth(1.0D); 36 | 37 | // Render methods 38 | GL11.glEnable(GL11.GL_TEXTURE_2D); 39 | GL11.glShadeModel(GL11.GL_SMOOTH); 40 | GL11.glEnable(GL11.GL_DEPTH_TEST); 41 | GL11.glDepthFunc(GL11.GL_LEQUAL); 42 | 43 | // Matrix 44 | GL11.glMatrixMode(GL11.GL_PROJECTION); 45 | GL11.glLoadIdentity(); 46 | GL11.glMatrixMode(GL11.GL_MODELVIEW); 47 | } 48 | 49 | public void setupFog(boolean inWater) { 50 | if (inWater) { 51 | GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_EXP); 52 | GL11.glFogf(GL11.GL_FOG_DENSITY, 0.1F); // Fog distance 53 | GL11.glFog(GL11.GL_FOG_COLOR, this.putColor(0.2F, 0.2F, 0.4F, 1.0F)); 54 | } else { 55 | int viewDistance = WorldRenderer.RENDER_DISTANCE * ChunkSection.SIZE; 56 | 57 | GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR); 58 | GL11.glFogf(GL11.GL_FOG_START, viewDistance / 4.0F); // Fog start 59 | GL11.glFogf(GL11.GL_FOG_END, viewDistance); // Fog end 60 | GL11.glFog(GL11.GL_FOG_COLOR, this.putColor(0.6222222F - 0.05F, 0.5F + 0.1F, 1.0F, 1.0F)); 61 | } 62 | } 63 | 64 | public void onTick() { 65 | 66 | } 67 | 68 | public void render(int cameraChunkX, int cameraChunkZ, EnumWorldBlockLayer renderLayer) { 69 | this.frustum.calculateFrustum(); 70 | 71 | for (Chunk chunk : this.world.chunks.values()) { 72 | int distanceX = Math.abs(cameraChunkX - chunk.getX()); 73 | int distanceZ = Math.abs(cameraChunkZ - chunk.getZ()); 74 | 75 | // Is in camera view 76 | if (distanceX < RENDER_DISTANCE && distanceZ < RENDER_DISTANCE && this.frustum.cubeInFrustum(chunk)) { 77 | 78 | // For all chunk sections 79 | for (ChunkSection chunkSection : chunk.getSections()) { 80 | // Render chunk section 81 | chunkSection.render(renderLayer); 82 | 83 | // Queue for rebuild 84 | if (chunkSection.isQueuedForRebuild() && !this.chunkSectionUpdateQueue.contains(chunkSection)) { 85 | this.chunkSectionUpdateQueue.add(chunkSection); 86 | } 87 | } 88 | } 89 | } 90 | 91 | // Sort update queue, chunk sections that are closer to the camera get a higher priority 92 | Collections.sort(this.chunkSectionUpdateQueue, (section1, section2) -> { 93 | int distance1 = (int) (Math.pow(section1.x - cameraChunkX, 2) + Math.pow(section1.z - cameraChunkZ, 2)); 94 | int distance2 = (int) (Math.pow(section2.x - cameraChunkX, 2) + Math.pow(section2.z - cameraChunkZ, 2)); 95 | return Integer.compare(distance1, distance2); 96 | }); 97 | 98 | // Rebuild one chunk per frame 99 | if (!this.chunkSectionUpdateQueue.isEmpty()) { 100 | ChunkSection chunkSection = this.chunkSectionUpdateQueue.remove(0); 101 | if (chunkSection != null) { 102 | chunkSection.rebuild(this); 103 | } 104 | } 105 | } 106 | 107 | private FloatBuffer putColor(float r, float g, float b, float a) { 108 | this.colorBuffer.clear(); 109 | this.colorBuffer.put(r).put(g).put(b).put(a); 110 | this.colorBuffer.flip(); 111 | return this.colorBuffer; 112 | } 113 | 114 | public BlockRenderer getBlockRenderer() { 115 | return this.blockRenderer; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/ChunkSection.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk; 2 | 3 | import de.labystudio.game.render.Tessellator; 4 | import de.labystudio.game.util.EnumWorldBlockLayer; 5 | import de.labystudio.game.world.World; 6 | import de.labystudio.game.world.WorldRenderer; 7 | import de.labystudio.game.world.block.Block; 8 | import org.lwjgl.opengl.GL11; 9 | 10 | public class ChunkSection { 11 | public static final int SIZE = 16; 12 | 13 | public final World world; 14 | 15 | private final byte[] blocks = new byte[(SIZE * SIZE + SIZE) * SIZE + SIZE]; 16 | private final byte[] blockLight = new byte[(SIZE * SIZE + SIZE) * SIZE + SIZE]; 17 | 18 | public int x; 19 | public int y; 20 | public int z; 21 | 22 | private int lists = -1; 23 | private boolean queuedForRebuild = true; 24 | 25 | public ChunkSection(World world, int x, int y, int z) { 26 | this.world = world; 27 | 28 | this.x = x; 29 | this.y = y; 30 | this.z = z; 31 | 32 | this.lists = GL11.glGenLists(EnumWorldBlockLayer.values().length); 33 | 34 | // Fill chunk with light 35 | for (int lightX = 0; lightX < SIZE; lightX++) { 36 | for (int lightY = 0; lightY < SIZE; lightY++) { 37 | for (int lightZ = 0; lightZ < SIZE; lightZ++) { 38 | int index = lightY << 8 | lightZ << 4 | lightX; 39 | this.blockLight[index] = 15; 40 | } 41 | } 42 | } 43 | } 44 | 45 | public void render(EnumWorldBlockLayer renderLayer) { 46 | // Call list with render layer 47 | GL11.glCallList(this.lists + renderLayer.ordinal()); 48 | } 49 | 50 | public void rebuild(WorldRenderer renderer) { 51 | this.queuedForRebuild = false; 52 | 53 | // Rebuild all render layers 54 | for (EnumWorldBlockLayer layer : EnumWorldBlockLayer.values()) { 55 | rebuild(renderer, layer); 56 | } 57 | } 58 | 59 | public void queueForRebuild() { 60 | this.queuedForRebuild = true; 61 | } 62 | 63 | public boolean isQueuedForRebuild() { 64 | return queuedForRebuild; 65 | } 66 | 67 | private void rebuild(WorldRenderer renderer, EnumWorldBlockLayer renderLayer) { 68 | // Create GPU memory list storage 69 | GL11.glNewList(this.lists + renderLayer.ordinal(), GL11.GL_COMPILE); 70 | GL11.glEnable(GL11.GL_TEXTURE_2D); 71 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, renderer.textureId); 72 | 73 | // Start rendering 74 | Tessellator tessellator = Tessellator.instance; 75 | tessellator.startDrawing(7); 76 | 77 | // Render blocks 78 | for (int x = 0; x < SIZE; x++) { 79 | for (int y = 0; y < SIZE; y++) { 80 | for (int z = 0; z < SIZE; z++) { 81 | short typeId = getBlockAt(x, y, z); 82 | 83 | if (typeId != 0) { 84 | int absoluteX = this.x * SIZE + x; 85 | int absoluteY = this.y * SIZE + y; 86 | int absoluteZ = this.z * SIZE + z; 87 | 88 | Block block = Block.getById(typeId); 89 | if (block != null && ((renderLayer == EnumWorldBlockLayer.CUTOUT) == block.isTransparent())) { 90 | block.render(renderer, this.world, absoluteX, absoluteY, absoluteZ); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | // Stop rendering 98 | tessellator.draw(); 99 | 100 | // End storage 101 | GL11.glDisable(GL11.GL_TEXTURE_2D); 102 | GL11.glEndList(); 103 | } 104 | 105 | public boolean isEmpty() { 106 | for (int x = 0; x < SIZE; x++) { 107 | for (int y = 0; y < SIZE; y++) { 108 | for (int z = 0; z < SIZE; z++) { 109 | int index = y << 8 | z << 4 | x; 110 | if (this.blocks[index] != 0) { 111 | return false; 112 | } 113 | } 114 | } 115 | } 116 | 117 | return true; 118 | } 119 | 120 | public boolean isSolidBlockAt(int x, int y, int z) { 121 | return getBlockAt(x, y, z) != 0; 122 | } 123 | 124 | public byte getBlockAt(int x, int y, int z) { 125 | int index = y << 8 | z << 4 | x; 126 | return this.blocks[index]; 127 | } 128 | 129 | public void setLightAt(int x, int y, int z, int lightLevel) { 130 | int index = y << 8 | z << 4 | x; 131 | this.blockLight[index] = (byte) lightLevel; 132 | } 133 | 134 | public void setBlockAt(int x, int y, int z, int type) { 135 | int index = y << 8 | z << 4 | x; 136 | this.blocks[index] = (byte) type; 137 | } 138 | 139 | public int getLightAt(int x, int y, int z) { 140 | int index = y << 8 | z << 4 | x; 141 | return this.blockLight[index]; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/MinecraftWindow.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game; 2 | 3 | import org.lwjgl.LWJGLException; 4 | import org.lwjgl.opengl.Display; 5 | 6 | import java.awt.*; 7 | import java.awt.event.WindowAdapter; 8 | import java.awt.event.WindowEvent; 9 | 10 | public class MinecraftWindow { 11 | 12 | public static final int DEFAULT_WIDTH = 854; 13 | public static final int DEFAULT_HEIGHT = 480; 14 | 15 | private final Minecraft game; 16 | 17 | protected final Canvas canvas; 18 | protected final Frame frame; 19 | 20 | protected boolean fullscreen; 21 | protected boolean enableVsync; 22 | 23 | public int displayWidth = DEFAULT_WIDTH; 24 | public int displayHeight = DEFAULT_HEIGHT; 25 | 26 | public MinecraftWindow(Minecraft game) { 27 | this.game = game; 28 | 29 | // Create canvas 30 | this.canvas = new Canvas(); 31 | this.canvas.setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT)); 32 | 33 | // Create frame 34 | this.frame = new Frame("3DGame"); 35 | this.frame.setLayout(new BorderLayout()); 36 | this.frame.add(this.canvas, "Center"); 37 | this.frame.pack(); 38 | this.frame.setLocationRelativeTo(null); 39 | this.frame.setVisible(true); 40 | 41 | // Close listener 42 | this.frame.addWindowListener(new WindowAdapter() { 43 | @Override 44 | public void windowClosing(WindowEvent e) { 45 | game.shutdown(); 46 | } 47 | }); 48 | } 49 | 50 | public void init() throws LWJGLException { 51 | Graphics g = this.canvas.getGraphics(); 52 | if (g != null) { 53 | g.setColor(Color.BLACK); 54 | g.fillRect(0, 0, this.displayWidth, this.displayHeight); 55 | g.dispose(); 56 | } 57 | Display.setParent(this.canvas); 58 | 59 | // Init 60 | Display.setTitle(this.frame.getTitle()); 61 | 62 | try { 63 | Display.create(); 64 | } catch (LWJGLException lwjglexception) { 65 | lwjglexception.printStackTrace(); 66 | 67 | // Try again in one second 68 | try { 69 | Thread.sleep(1000L); 70 | } catch (InterruptedException ignored) { 71 | } 72 | 73 | Display.create(); 74 | } 75 | 76 | Display.swapBuffers(); 77 | } 78 | 79 | public void toggleFullscreen() { 80 | try { 81 | this.fullscreen = !this.fullscreen; 82 | 83 | System.out.println("Toggle fullscreen!"); 84 | 85 | if (this.fullscreen) { 86 | Display.setDisplayMode(Display.getDesktopDisplayMode()); 87 | 88 | this.displayWidth = Display.getDisplayMode().getWidth(); 89 | this.displayHeight = Display.getDisplayMode().getHeight(); 90 | 91 | if (this.displayWidth <= 0) { 92 | this.displayWidth = 1; 93 | } 94 | if (this.displayHeight <= 0) { 95 | this.displayHeight = 1; 96 | } 97 | } else { 98 | this.displayWidth = this.canvas.getWidth(); 99 | this.displayHeight = this.canvas.getHeight(); 100 | 101 | if (this.displayWidth <= 0) { 102 | this.displayWidth = 1; 103 | } 104 | if (this.displayHeight <= 0) { 105 | this.displayHeight = 1; 106 | } 107 | 108 | Display.setDisplayMode(new org.lwjgl.opengl.DisplayMode(DEFAULT_WIDTH, DEFAULT_HEIGHT)); 109 | } 110 | 111 | Display.setFullscreen(this.fullscreen); 112 | Display.update(); 113 | 114 | Thread.sleep(1000L); 115 | System.out.println("Size: " + this.displayWidth + ", " + this.displayHeight); 116 | } catch (Exception exception) { 117 | exception.printStackTrace(); 118 | } 119 | } 120 | 121 | public void update() { 122 | Display.update(); 123 | 124 | if (!this.fullscreen && (this.canvas.getWidth() != this.displayWidth || this.canvas.getHeight() != this.displayHeight)) { 125 | this.displayWidth = this.canvas.getWidth(); 126 | this.displayHeight = this.canvas.getHeight(); 127 | 128 | if (this.displayWidth <= 0) { 129 | this.displayWidth = 1; 130 | } 131 | 132 | if (this.displayHeight <= 0) { 133 | this.displayHeight = 1; 134 | } 135 | 136 | this.resize(this.displayWidth, this.displayHeight); 137 | } 138 | } 139 | 140 | private void resize(int width, int height) { 141 | if (width <= 0) { 142 | width = 1; 143 | } 144 | if (height <= 0) { 145 | height = 1; 146 | } 147 | 148 | this.displayWidth = width; 149 | this.displayHeight = height; 150 | 151 | this.game.gui.init(this); 152 | } 153 | 154 | public void destroy() { 155 | Display.destroy(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/gui/FontRenderer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render.gui; 2 | 3 | import de.labystudio.game.render.Tessellator; 4 | import de.labystudio.game.util.TextureManager; 5 | import org.lwjgl.opengl.GL11; 6 | 7 | import javax.imageio.ImageIO; 8 | import java.awt.image.BufferedImage; 9 | import java.io.IOException; 10 | 11 | public class FontRenderer { 12 | 13 | private static final int BITMAP_SIZE = 16; 14 | private static final int FIELD_SIZE = 8; 15 | 16 | private static final String COLOR_CODE_INDEX_LOOKUP = "0123456789abcdef"; 17 | 18 | private final GuiRenderer gui; 19 | private final Tessellator tessellator = Tessellator.instance; 20 | 21 | private final int[] charWidths = new int[256]; 22 | private final int fontTextureId; 23 | 24 | public FontRenderer(GuiRenderer gui, String name) throws IOException { 25 | this.gui = gui; 26 | 27 | BufferedImage bitMap = ImageIO.read(TextureManager.class.getResourceAsStream(name)); 28 | 29 | // Calculate character width 30 | for (int i = 0; i < 128; i++) { 31 | this.charWidths[i] = this.calculateCharacterWidthAt(bitMap, i % BITMAP_SIZE, i / BITMAP_SIZE) + 2; 32 | } 33 | 34 | // Load texture 35 | this.fontTextureId = TextureManager.loadTexture(name, GL11.GL_NEAREST); 36 | } 37 | 38 | private int calculateCharacterWidthAt(BufferedImage bitMap, int indexX, int indexY) { 39 | // We scan the bitmap field from right to left 40 | for (int x = indexX * FIELD_SIZE + FIELD_SIZE - 1; x >= indexX * FIELD_SIZE; x--) { 41 | 42 | // Scan this column from top to bottom 43 | for (int y = indexY * FIELD_SIZE; y < indexY * FIELD_SIZE + FIELD_SIZE; y++) { 44 | 45 | // Return width if there is a white pixel 46 | if ((bitMap.getRGB(x, y) & 0xFF) != 0) { 47 | return x - indexX * FIELD_SIZE; 48 | } 49 | } 50 | } 51 | 52 | // Empty field width (Could be a space character) 53 | return 2; 54 | } 55 | 56 | public void drawString(String string, int x, int y) { 57 | this.drawString(string, x, y, -1); 58 | } 59 | 60 | public void drawString(String string, int x, int y, int color) { 61 | this.drawStringRaw(string, x + 1, y + 1, color, true); 62 | this.drawStringRaw(string, x, y, color, false); 63 | } 64 | 65 | public void drawStringWithoutShadow(String string, int x, int y, int color) { 66 | this.drawStringRaw(string, x, y, color, false); 67 | } 68 | 69 | private void drawStringRaw(String string, int x, int y, int color, boolean isShadow) { 70 | // Setup texture 71 | GL11.glEnable(GL11.GL_TEXTURE_2D); 72 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.fontTextureId); 73 | 74 | // Start rendering 75 | this.tessellator.startDrawingQuads(); 76 | this.setColor(color, isShadow); 77 | 78 | char[] chars = string.toCharArray(); 79 | 80 | // For each character 81 | for (int i = 0; i < chars.length; i++) { 82 | char character = chars[i]; 83 | 84 | // Handle color codes if character is & 85 | if (character == '&' && i != chars.length - 1) { 86 | // Get the next character 87 | char nextCharacter = chars[i + 1]; 88 | 89 | // Change color of string 90 | this.setColor(this.getColorOfCharacter(nextCharacter), isShadow); 91 | 92 | // Skip the color code for rendering 93 | i += 1; 94 | continue; 95 | } 96 | 97 | // Get character offset in bitmap 98 | int textureOffsetX = chars[i] % BITMAP_SIZE * FIELD_SIZE; 99 | int textureOffsetY = chars[i] / BITMAP_SIZE * FIELD_SIZE; 100 | 101 | // Draw character 102 | this.gui.drawTexturedModalRect(this.tessellator, x, y, 103 | textureOffsetX, textureOffsetY, 104 | FIELD_SIZE, FIELD_SIZE, 105 | FIELD_SIZE, FIELD_SIZE, 106 | 128, 128); 107 | 108 | // Increase drawing cursor 109 | x += this.charWidths[character]; 110 | } 111 | 112 | // Finish drawing 113 | this.tessellator.draw(); 114 | GL11.glDisable(GL11.GL_TEXTURE_2D); 115 | } 116 | 117 | public int getColorOfCharacter(char character) { 118 | int index = COLOR_CODE_INDEX_LOOKUP.indexOf(character); 119 | int brightness = (index & 0x8) * 8; 120 | 121 | // Convert index to RGB 122 | int b = (index & 0x1) * 191 + brightness; 123 | int g = ((index & 0x2) >> 1) * 191 + brightness; 124 | int r = ((index & 0x4) >> 2) * 191 + brightness; 125 | 126 | return r << 16 | g << 8 | b; 127 | } 128 | 129 | private void setColor(int color, boolean isShadow) { 130 | this.tessellator.setColorOpaque_I(isShadow ? (color & 0xFCFCFC) >> 2 : color); 131 | } 132 | 133 | public int getStringWidth(String string) { 134 | char[] chars = string.toCharArray(); 135 | int length = 0; 136 | 137 | // For each character 138 | for (int i = 0; i < chars.length; i++) { 139 | 140 | // Check for color code 141 | if (chars[i] == '&') { 142 | // Skip the next character 143 | i++; 144 | } else { 145 | // Add the width of the character 146 | length += this.charWidths[chars[i]]; 147 | } 148 | } 149 | 150 | return length; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/format/WorldFormat.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk.format; 2 | 3 | import de.labystudio.game.world.World; 4 | import de.labystudio.game.world.chunk.ChunkSection; 5 | import de.labystudio.game.world.chunk.Chunk; 6 | 7 | import java.io.DataInputStream; 8 | import java.io.DataOutputStream; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class WorldFormat { 15 | 16 | private final World world; 17 | 18 | private final File worldDirectory; 19 | private final File regionDirectory; 20 | 21 | private final String name; 22 | 23 | public WorldFormat(World world, File worldDirectory) { 24 | this.world = world; 25 | this.worldDirectory = worldDirectory; 26 | 27 | this.name = worldDirectory.getName(); 28 | this.regionDirectory = new File(this.worldDirectory, "region"); 29 | } 30 | 31 | public RegionFormat getRegion(int x, int z) { 32 | File file = new File(this.regionDirectory, RegionFormat.getFileName(x, z)); 33 | 34 | File parent = file.getParentFile(); 35 | if (parent != null && !file.exists() && parent.mkdirs()) { 36 | System.out.println("Created new world directory \"" + this.name + "\""); 37 | } 38 | 39 | return new RegionFormat(file); 40 | } 41 | 42 | public void load(WorldLoadingProgress worldLoadingProgress) { 43 | if (!this.regionDirectory.exists()) { 44 | System.out.println("World file not found"); 45 | return; 46 | } 47 | 48 | File[] files = this.regionDirectory.listFiles(); 49 | if (files != null) { 50 | for (File regionFile : files) { 51 | // Skip old region files 52 | if (regionFile.getName().endsWith("~")) 53 | continue; 54 | 55 | RegionFormat region = new RegionFormat(regionFile); 56 | 57 | try { 58 | for (int x = 0; x < 32; x++) { 59 | for (int z = 0; z < 32; z++) { 60 | DataInputStream inputStream = region.getChunkDataInputStream(x, z); 61 | 62 | int chunkX = (region.x << 5) + x; 63 | int chunkZ = (region.z << 5) + z; 64 | 65 | // Read chunk layers 66 | ChunkFormat chunkFormat = new ChunkFormat(this.world, x, z).read(inputStream, chunkX, chunkZ); 67 | if (!chunkFormat.isEmpty()) { 68 | ChunkSection[] layers = chunkFormat.getChunks(); 69 | 70 | // Fill empty chunks with chunk objects 71 | for (int y = 0; y < 16; y++) { 72 | if (layers[y] == null) { 73 | layers[y] = new ChunkSection(this.world, chunkX, y, chunkZ); 74 | } 75 | } 76 | 77 | // Load chunk layers 78 | worldLoadingProgress.onLoad(chunkX, chunkZ, layers); 79 | } 80 | } 81 | } 82 | } catch (IOException exception) { 83 | exception.printStackTrace(); 84 | } 85 | 86 | // Close the region 87 | try { 88 | region.close(); 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | } 94 | } 95 | 96 | 97 | public void saveChunks() throws IOException { 98 | List regionsToSave = new ArrayList(); 99 | 100 | // Get all regions to save 101 | for (Chunk chunk : this.world.chunks.values()) { 102 | if (chunk.isEmpty()) 103 | continue; 104 | 105 | // Get region coordinates of this chunk 106 | int regionX = chunk.getX() >> 5; 107 | int regionZ = chunk.getZ() >> 5; 108 | 109 | // Create an id of this region coordinate 110 | long regionId = ((long) regionX) << 32L | regionZ & 0xFFFFFFFFL; 111 | 112 | // Add to queue 113 | if (!regionsToSave.contains(regionId)) { 114 | regionsToSave.add(regionId); 115 | } 116 | } 117 | 118 | System.out.println("Start saving world in " + regionsToSave.size() + " region files."); 119 | 120 | for (Long regionId : regionsToSave) { 121 | // Extract two ints from long id 122 | int regionX = (int) (regionId >> 32); 123 | int regionZ = (int) (long) regionId; 124 | 125 | // Get region file 126 | RegionFormat region = this.getRegion(regionX, regionZ); 127 | 128 | // Relative offset 129 | for (int x = 0; x < 32; x++) { 130 | for (int z = 0; z < 32; z++) { 131 | 132 | // Absolute chunk coordinates 133 | int chunkX = (region.x << 5) + x; 134 | int chunkZ = (region.z << 5) + z; 135 | 136 | if (this.world.isChunkLoaded(chunkX, chunkZ)) { 137 | // Get chunk 138 | Chunk chunk = this.world.getChunkAt(chunkX, chunkZ); 139 | 140 | // Write 141 | if (!chunk.isEmpty()) { 142 | DataOutputStream outputStream = region.getChunkDataOutputStream(x, z); 143 | ChunkFormat.write(chunk, outputStream); 144 | outputStream.close(); 145 | } 146 | } 147 | } 148 | } 149 | 150 | region.close(); 151 | } 152 | } 153 | 154 | public boolean exists() { 155 | return this.regionDirectory.exists(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/format/ChunkFormat.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk.format; 2 | 3 | 4 | import com.github.steveice10.opennbt.NBTIO; 5 | import com.github.steveice10.opennbt.tag.builtin.*; 6 | import de.labystudio.game.world.World; 7 | import de.labystudio.game.world.block.Block; 8 | import de.labystudio.game.world.chunk.ChunkSection; 9 | import de.labystudio.game.world.chunk.Chunk; 10 | 11 | import java.io.*; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class ChunkFormat { 16 | 17 | private ChunkSection[] chunkSections = new ChunkSection[16]; 18 | 19 | private World world; 20 | private int x; 21 | private int z; 22 | 23 | private boolean empty = true; 24 | 25 | public ChunkFormat(World world, int x, int z) { 26 | this.world = world; 27 | this.x = x; 28 | this.z = z; 29 | } 30 | 31 | public ChunkFormat read(DataInputStream inputStream, int chunkX, int chunkZ) throws IOException { 32 | if (this.empty = inputStream == null) 33 | return this; 34 | 35 | // Get root tag 36 | CompoundTag rootTag = (CompoundTag) NBTIO.readTag((InputStream) inputStream); 37 | 38 | // Load chunk format 39 | CompoundTag level = rootTag.get("Level"); 40 | ListTag sections = level.get("Sections"); 41 | 42 | for (int i = 0; i < sections.size(); i++) { 43 | CompoundTag section = sections.get(i); 44 | 45 | try { 46 | byte y = (byte) section.get("Y").getValue(); 47 | 48 | byte[] blocks = ((ByteArrayTag) section.get("Blocks")).getValue(); 49 | byte[] add = section.contains("Add") ? ((ByteArrayTag) section.get("Add")).getValue() : new byte[blocks.length]; 50 | byte[] blockLight = ((ByteArrayTag) section.get("BlockLight")).getValue(); 51 | byte[] skyLight = ((ByteArrayTag) section.get("SkyLight")).getValue(); 52 | // byte[] data = ((ByteArrayTag) section.get("Data")).getValue(); 53 | 54 | ChunkSection chunkSection = new ChunkSection(this.world, chunkX, y, chunkZ); 55 | 56 | for (int relY = 0; relY < 16; relY++) { 57 | for (int relX = 0; relX < 16; relX++) { 58 | for (int relZ = 0; relZ < 16; relZ++) { 59 | int index = (relY * 16 + relZ) * 16 + relX; 60 | int blockId = ((add[index] & 0xFF) << 4) | (blocks[index] & 0xFF); 61 | //int typeAndData = (add[index] << 8) | blockId | getHalfByte(index, data); 62 | 63 | // Combine sky light and block light 64 | int lightLevel = Math.max(getHalfByte(index, blockLight), getHalfByte(index, skyLight)); 65 | 66 | if (blockId != 0) { 67 | this.empty = false; 68 | } 69 | 70 | // Invalid block, convert to stone 71 | if (blockId != 0 && Block.getById((short) blockId) == null) { 72 | blockId = Block.STONE.getId(); 73 | } 74 | 75 | chunkSection.setBlockAt(relX, relY, relZ, blockId); 76 | chunkSection.setLightAt(relX, relY, relZ, lightLevel); 77 | } 78 | } 79 | } 80 | 81 | 82 | this.chunkSections[y] = chunkSection; 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | 88 | return this; 89 | } 90 | 91 | private static byte getHalfByte(int index, byte[] bytes) { 92 | return (byte) ((bytes[index / 2] >> (index % 2 == 0 ? 0 : 4)) & 0xF); 93 | } 94 | 95 | private static void setHalfByte(int index, byte value, byte[] bytes) { 96 | bytes[index / 2] = (byte) ((bytes[index / 2] &= 0xF << (index % 2 == 0 ? 4 : 0)) | (value & 0xF) << (index % 2 == 0 ? 0 : 4)); 97 | } 98 | 99 | public static void write(Chunk chunk, DataOutputStream dataOutputStream) throws IOException { 100 | List sectionList = new ArrayList(); 101 | for (byte y = 0; y < 16; y++) { 102 | ChunkSection chunkSection = chunk.getSection(y); 103 | 104 | // Skip empty chunks 105 | if (chunkSection == null || chunkSection.isEmpty()) { 106 | continue; 107 | } 108 | 109 | // Section data 110 | CompoundTag section = new CompoundTag(""); 111 | 112 | // Set Y tag 113 | section.put(new ByteTag("Y", y)); 114 | 115 | // Content 116 | ByteArrayTag blocks = new ByteArrayTag("Blocks"); 117 | ByteArrayTag add = new ByteArrayTag("Add"); 118 | ByteArrayTag data = new ByteArrayTag("Data"); 119 | ByteArrayTag blockLight = new ByteArrayTag("BlockLight"); 120 | ByteArrayTag skyLight = new ByteArrayTag("SkyLight"); 121 | 122 | byte[] blockArray = new byte[4096]; 123 | byte[] addArray = new byte[4096]; 124 | byte[] lightArray = new byte[4096]; 125 | byte[] skyLightArray = new byte[4096]; 126 | //byte[] dataArray = new byte[4096]; 127 | 128 | for (int relY = 0; relY < 16; relY++) { 129 | for (int relX = 0; relX < 16; relX++) { 130 | for (int relZ = 0; relZ < 16; relZ++) { 131 | int index = (relY * 16 + relZ) * 16 + relX; 132 | 133 | int blockId = chunkSection.getBlockAt(relX, relY, relZ); 134 | int blockLightShort = chunkSection.getLightAt(relX, relY, relZ); 135 | 136 | blockArray[index] = (byte) (blockId & 0xFF); 137 | addArray[index] = (byte) (blockId >> 4); 138 | setHalfByte(index, (byte) blockLightShort, lightArray); 139 | 140 | // int typeAndData = chunkSection.getBlockAt(relX, relY, relZ); 141 | // blockArray[index] = (byte) ((typeAndData & 0xFF) >> 4); 142 | // addArray[index] = (byte) (typeAndData >> 8); 143 | // setHalfByte(index, (byte) typeAndData, dataArray); 144 | } 145 | } 146 | } 147 | 148 | // Fill content tags 149 | blocks.setValue(blockArray); 150 | add.setValue(addArray); 151 | blockLight.setValue(lightArray); 152 | skyLight.setValue(skyLightArray); 153 | // data.setValue(dataArray); 154 | 155 | // Add to section 156 | section.put(blocks); 157 | section.put(add); 158 | //section.put(data); 159 | section.put(blockLight); 160 | section.put(skyLight); 161 | 162 | // Add section to list 163 | sectionList.add(section); 164 | } 165 | 166 | // Add level tags 167 | CompoundTag level = new CompoundTag("Level"); 168 | level.put(new ListTag("Sections", sectionList)); 169 | 170 | // Create root 171 | CompoundTag root = new CompoundTag(""); 172 | 173 | // Add level to root 174 | root.put(level); 175 | 176 | // Write 177 | NBTIO.writeTag((OutputStream) dataOutputStream, root); 178 | } 179 | 180 | public ChunkSection[] getChunks() { 181 | return chunkSections; 182 | } 183 | 184 | public int getX() { 185 | return x; 186 | } 187 | 188 | public int getZ() { 189 | return z; 190 | } 191 | 192 | public boolean isEmpty() { 193 | return empty; 194 | } 195 | } -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/world/BlockRenderer.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render.world; 2 | 3 | import de.labystudio.game.render.Tessellator; 4 | import de.labystudio.game.util.BoundingBox; 5 | import de.labystudio.game.util.EnumBlockFace; 6 | import de.labystudio.game.world.WorldRenderer; 7 | import de.labystudio.game.world.block.Block; 8 | import org.lwjgl.opengl.GL11; 9 | 10 | public class BlockRenderer { 11 | 12 | public static final boolean CLASSIC_LIGHTNING = false; 13 | 14 | private final Tessellator tessellator = Tessellator.instance; 15 | 16 | public void renderBlock(IWorldAccess world, Block block, int x, int y, int z) { 17 | BoundingBox boundingBox = block.getBoundingBox(world, x, y, z); 18 | 19 | // Render faces 20 | for (EnumBlockFace face : EnumBlockFace.values()) { 21 | if (block.shouldRenderFace(world, x, y, z, face)) { 22 | this.renderFace(world, block, boundingBox, face, x, y, z); 23 | } 24 | } 25 | } 26 | 27 | public void renderFace(IWorldAccess world, Block block, BoundingBox boundingBox, EnumBlockFace face, int x, int y, int z) { 28 | 29 | // Vertex mappings 30 | double minX = x + boundingBox.minX; 31 | double minY = y + boundingBox.minY; 32 | double minZ = z + boundingBox.minZ; 33 | double maxX = x + boundingBox.maxX; 34 | double maxY = y + boundingBox.maxY; 35 | double maxZ = z + boundingBox.maxZ; 36 | 37 | // UV Mapping 38 | int textureIndex = block.getTextureForFace(face); 39 | float minU = (textureIndex % 16) / 16.0F; 40 | float maxU = minU + (16 / 256F); 41 | float minV = (float) (textureIndex / 16); 42 | float maxV = minV + (16 / 256F); 43 | 44 | // Classic lightning 45 | if (CLASSIC_LIGHTNING) { 46 | float brightness = 0.9F / 15.0F * world.getLightAt((int) minX + face.x, (int) minY + face.y, (int) minZ + face.z) + 0.1F; 47 | float color = brightness * face.getShading(); 48 | this.tessellator.setColorRGB_F(color, color, color); 49 | } 50 | 51 | if (face == EnumBlockFace.BOTTOM) { 52 | this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV); 53 | this.addBlockCorner(world, face, minX, minY, minZ, minU, minV); 54 | this.addBlockCorner(world, face, maxX, minY, minZ, maxU, minV); 55 | this.addBlockCorner(world, face, maxX, minY, maxZ, maxU, maxV); 56 | } 57 | if (face == EnumBlockFace.TOP) { 58 | this.addBlockCorner(world, face, maxX, maxY, maxZ, maxU, maxV); 59 | this.addBlockCorner(world, face, maxX, maxY, minZ, maxU, minV); 60 | this.addBlockCorner(world, face, minX, maxY, minZ, minU, minV); 61 | this.addBlockCorner(world, face, minX, maxY, maxZ, minU, maxV); 62 | } 63 | if (face == EnumBlockFace.EAST) { 64 | this.addBlockCorner(world, face, minX, maxY, minZ, maxU, minV); 65 | this.addBlockCorner(world, face, maxX, maxY, minZ, minU, minV); 66 | this.addBlockCorner(world, face, maxX, minY, minZ, minU, maxV); 67 | this.addBlockCorner(world, face, minX, minY, minZ, maxU, maxV); 68 | } 69 | if (face == EnumBlockFace.WEST) { 70 | this.addBlockCorner(world, face, minX, maxY, maxZ, minU, minV); 71 | this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV); 72 | this.addBlockCorner(world, face, maxX, minY, maxZ, maxU, maxV); 73 | this.addBlockCorner(world, face, maxX, maxY, maxZ, maxU, minV); 74 | } 75 | if (face == EnumBlockFace.NORTH) { 76 | this.addBlockCorner(world, face, minX, maxY, maxZ, maxU, minV); 77 | this.addBlockCorner(world, face, minX, maxY, minZ, minU, minV); 78 | this.addBlockCorner(world, face, minX, minY, minZ, minU, maxV); 79 | this.addBlockCorner(world, face, minX, minY, maxZ, maxU, maxV); 80 | } 81 | if (face == EnumBlockFace.SOUTH) { 82 | this.addBlockCorner(world, face, maxX, minY, maxZ, minU, maxV); 83 | this.addBlockCorner(world, face, maxX, minY, minZ, maxU, maxV); 84 | this.addBlockCorner(world, face, maxX, maxY, minZ, maxU, minV); 85 | this.addBlockCorner(world, face, maxX, maxY, maxZ, minU, minV); 86 | } 87 | } 88 | 89 | 90 | private void addBlockCorner(IWorldAccess world, EnumBlockFace face, double x, double y, double z, float u, float v) { 91 | // Smooth lightning 92 | if (!CLASSIC_LIGHTNING) { 93 | this.setAverageColor(world, face, (int) x, (int) y, (int) z); 94 | } 95 | 96 | this.tessellator.addVertexWithUV(x, y, z, u, v); 97 | } 98 | 99 | private void setAverageColor(IWorldAccess world, EnumBlockFace face, int x, int y, int z) { 100 | // Get the average light level of all 4 blocks at this corner 101 | int lightLevelAtThisCorner = this.getAverageLightLevelAt(world, x, y, z); 102 | 103 | // Convert light level from [0 - 15] to [0.1 - 1.0] 104 | float brightness = 0.9F / 15.0F * lightLevelAtThisCorner + 0.1F; 105 | float color = brightness * face.getShading(); 106 | 107 | // Set color with shading 108 | this.tessellator.setColorRGB_F(color, color, color); 109 | } 110 | 111 | private int getAverageLightLevelAt(IWorldAccess world, int x, int y, int z) { 112 | int totalLightLevel = 0; 113 | int totalBlocks = 0; 114 | 115 | // For all blocks around this corner 116 | for (int offsetX = -1; offsetX <= 0; offsetX++) { 117 | for (int offsetY = -1; offsetY <= 0; offsetY++) { 118 | for (int offsetZ = -1; offsetZ <= 0; offsetZ++) { 119 | short typeId = world.getBlockAt(x + offsetX, y + offsetY, z + offsetZ); 120 | 121 | // Does it contain air? 122 | if (typeId == 0 || Block.getById(typeId).isTransparent()) { 123 | 124 | // Sum up the light levels 125 | totalLightLevel += world.getLightAt(x + offsetX, y + offsetY, z + offsetZ); 126 | totalBlocks++; 127 | } 128 | } 129 | } 130 | } 131 | 132 | // Calculate the average light level of all surrounding blocks 133 | return totalBlocks == 0 ? 0 : totalLightLevel / totalBlocks; 134 | } 135 | 136 | public void renderSingleBlock(WorldRenderer worldRenderer, IWorldAccess world, Block block, int x, int y, int z) { 137 | GL11.glEnable(GL11.GL_TEXTURE_2D); 138 | GL11.glBindTexture(GL11.GL_TEXTURE_2D, worldRenderer.textureId); 139 | 140 | this.tessellator.startDrawing(7); 141 | worldRenderer.getBlockRenderer().renderBlock(world, Block.LEAVE, x, y, z); 142 | this.tessellator.draw(); 143 | } 144 | 145 | public void drawBoundingBox(double minX, double minY, double minZ, 146 | double maxX, double maxY, double maxZ) { 147 | 148 | // Bottom 149 | GL11.glBegin(GL11.GL_LINE_LOOP); 150 | GL11.glVertex3d(minX, minY, minZ); 151 | GL11.glVertex3d(minX, minY, maxZ); 152 | GL11.glVertex3d(maxX, minY, maxZ); 153 | GL11.glVertex3d(maxX, minY, minZ); 154 | GL11.glEnd(); 155 | 156 | // Ceiling 157 | GL11.glBegin(GL11.GL_LINE_LOOP); 158 | GL11.glVertex3d(minX, maxY, minZ); 159 | GL11.glVertex3d(minX, maxY, maxZ); 160 | GL11.glVertex3d(maxX, maxY, maxZ); 161 | GL11.glVertex3d(maxX, maxY, minZ); 162 | GL11.glEnd(); 163 | 164 | GL11.glBegin(GL11.GL_LINE_STRIP); 165 | GL11.glVertex3d(minX, minY, minZ); 166 | GL11.glVertex3d(minX, maxY, minZ); 167 | GL11.glEnd(); 168 | 169 | GL11.glBegin(GL11.GL_LINE_STRIP); 170 | GL11.glVertex3d(minX, minY, maxZ); 171 | GL11.glVertex3d(minX, maxY, maxZ); 172 | GL11.glEnd(); 173 | 174 | GL11.glBegin(GL11.GL_LINE_STRIP); 175 | GL11.glVertex3d(maxX, minY, maxZ); 176 | GL11.glVertex3d(maxX, maxY, maxZ); 177 | GL11.glEnd(); 178 | 179 | GL11.glBegin(GL11.GL_LINE_STRIP); 180 | GL11.glVertex3d(maxX, minY, minZ); 181 | GL11.glVertex3d(maxX, maxY, minZ); 182 | GL11.glEnd(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/util/BoundingBox.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.util; 2 | 3 | public class BoundingBox { 4 | 5 | private final double epsilon = 0.0F; 6 | 7 | public double minX; 8 | public double minY; 9 | public double minZ; 10 | public double maxX; 11 | public double maxY; 12 | public double maxZ; 13 | 14 | /** 15 | * Bounding box 16 | * 17 | * @param minX Minimum x side 18 | * @param minY Minimum y side 19 | * @param minZ Minimum z side 20 | * @param maxX Maximum x side 21 | * @param maxY Maximum y side 22 | * @param maxZ Maximum z side 23 | */ 24 | public BoundingBox(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { 25 | this.minX = minX; 26 | this.minY = minY; 27 | this.minZ = minZ; 28 | this.maxX = maxX; 29 | this.maxY = maxY; 30 | this.maxZ = maxZ; 31 | } 32 | 33 | /** 34 | * Copy the current bounding box object 35 | * 36 | * @return Clone of the bounding box 37 | */ 38 | public BoundingBox clone() { 39 | return new BoundingBox(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ); 40 | } 41 | 42 | /** 43 | * Expand the bounding box. Positive and negative numbers controls which side of the box should grow. 44 | * 45 | * @param x Amount to expand the minX or maxX 46 | * @param y Amount to expand the minY or maxY 47 | * @param z Amount to expand the minZ or maxZ 48 | * @return The expanded bounding box 49 | */ 50 | public BoundingBox expand(double x, double y, double z) { 51 | double minX = this.minX; 52 | double minY = this.minY; 53 | double minZ = this.minZ; 54 | double maxX = this.maxX; 55 | double maxY = this.maxY; 56 | double maxZ = this.maxZ; 57 | 58 | // Handle expanding of min/max x 59 | if (x < 0.0F) { 60 | minX += x; 61 | } else { 62 | maxX += x; 63 | } 64 | 65 | // Handle expanding of min/max y 66 | if (y < 0.0F) { 67 | minY += y; 68 | } else { 69 | maxY += y; 70 | } 71 | 72 | // Handle expanding of min/max z 73 | if (z < 0.0F) { 74 | minZ += z; 75 | } else { 76 | maxZ += z; 77 | } 78 | 79 | // Create new bounding box 80 | return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ); 81 | } 82 | 83 | /** 84 | * Expand the bounding box on both sides. 85 | * The center is always fixed when using grow. 86 | * 87 | * @param x 88 | * @param y 89 | * @param z 90 | * @return 91 | */ 92 | public BoundingBox grow(double x, double y, double z) { 93 | return new BoundingBox(this.minX - x, this.minY - y, 94 | this.minZ - z, this.maxX + x, 95 | this.maxY + y, this.maxZ + z); 96 | } 97 | 98 | /** 99 | * Check for collision on the X axis 100 | * 101 | * @param otherBoundingBox The other bounding box that is colliding with the this one. 102 | * @param x Position on the X axis that is colliding 103 | * @return Returns the corrected x position that collided. 104 | */ 105 | public double clipXCollide(BoundingBox otherBoundingBox, double x) { 106 | // Check if the boxes are colliding on the Y axis 107 | if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) { 108 | return x; 109 | } 110 | 111 | // Check if the boxes are colliding on the Z axis 112 | if (otherBoundingBox.maxZ <= this.minZ || otherBoundingBox.minZ >= this.maxZ) { 113 | return x; 114 | } 115 | 116 | // Check for collision if the X axis of the current box is bigger 117 | if (x > 0.0F && otherBoundingBox.maxX <= this.minX) { 118 | double max = this.minX - otherBoundingBox.maxX - this.epsilon; 119 | if (max < x) { 120 | x = max; 121 | } 122 | } 123 | 124 | // Check for collision if the X axis of the current box is smaller 125 | if (x < 0.0F && otherBoundingBox.minX >= this.maxX) { 126 | double max = this.maxX - otherBoundingBox.minX + this.epsilon; 127 | if (max > x) { 128 | x = max; 129 | } 130 | } 131 | 132 | return x; 133 | } 134 | 135 | /** 136 | * Check for collision on the Y axis 137 | * 138 | * @param otherBoundingBox The other bounding box that is colliding with the this one. 139 | * @param y Position on the X axis that is colliding 140 | * @return Returns the corrected x position that collided. 141 | */ 142 | public double clipYCollide(BoundingBox otherBoundingBox, double y) { 143 | // Check if the boxes are colliding on the X axis 144 | if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) { 145 | return y; 146 | } 147 | 148 | // Check if the boxes are colliding on the Z axis 149 | if (otherBoundingBox.maxZ <= this.minZ || otherBoundingBox.minZ >= this.maxZ) { 150 | return y; 151 | } 152 | 153 | // Check for collision if the Y axis of the current box is bigger 154 | if (y > 0.0F && otherBoundingBox.maxY <= this.minY) { 155 | double max = this.minY - otherBoundingBox.maxY - this.epsilon; 156 | if (max < y) { 157 | y = max; 158 | } 159 | } 160 | 161 | // Check for collision if the Y axis of the current box is bigger 162 | if (y < 0.0F && otherBoundingBox.minY >= this.maxY) { 163 | double max = this.maxY - otherBoundingBox.minY + this.epsilon; 164 | if (max > y) { 165 | y = max; 166 | } 167 | } 168 | 169 | return y; 170 | } 171 | 172 | /** 173 | * Check for collision on the Y axis 174 | * 175 | * @param otherBoundingBox The other bounding box that is colliding with the this one. 176 | * @param z Position on the X axis that is colliding 177 | * @return Returns the corrected x position that collided. 178 | */ 179 | public double clipZCollide(BoundingBox otherBoundingBox, double z) { 180 | // Check if the boxes are colliding on the X axis 181 | if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) { 182 | return z; 183 | } 184 | 185 | // Check if the boxes are colliding on the Y axis 186 | if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) { 187 | return z; 188 | } 189 | 190 | // Check for collision if the Z axis of the current box is bigger 191 | if (z > 0.0F && otherBoundingBox.maxZ <= this.minZ) { 192 | double max = this.minZ - otherBoundingBox.maxZ - this.epsilon; 193 | if (max < z) { 194 | z = max; 195 | } 196 | } 197 | 198 | // Check for collision if the Z axis of the current box is bigger 199 | if (z < 0.0F && otherBoundingBox.minZ >= this.maxZ) { 200 | double max = this.maxZ - otherBoundingBox.minZ + this.epsilon; 201 | if (max > z) { 202 | z = max; 203 | } 204 | } 205 | 206 | return z; 207 | } 208 | 209 | /** 210 | * Check if the two boxes are intersecting/overlapping 211 | * 212 | * @param otherBoundingBox The other bounding box that could intersect 213 | * @return The two boxes are overlapping 214 | */ 215 | public boolean intersects(BoundingBox otherBoundingBox) { 216 | // Check on X axis 217 | if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) { 218 | return false; 219 | } 220 | 221 | // Check on Y axis 222 | if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) { 223 | return false; 224 | } 225 | 226 | // Check on Z axis 227 | return (!(otherBoundingBox.maxZ <= this.minZ)) && (!(otherBoundingBox.minZ >= this.maxZ)); 228 | } 229 | 230 | /** 231 | * Move the bounding box relative. 232 | * 233 | * @param x Relative offset x 234 | * @param y Relative offset y 235 | * @param z Relative offset z 236 | */ 237 | public void move(double x, double y, double z) { 238 | this.minX += x; 239 | this.minY += y; 240 | this.minZ += z; 241 | this.maxX += x; 242 | this.maxY += y; 243 | this.maxZ += z; 244 | } 245 | 246 | /** 247 | * Create a new bounding box with the given offset 248 | * 249 | * @param x Relative offset x 250 | * @param y Relative offset x 251 | * @param z Relative offset x 252 | * @return New bounding box with the given offset relative to this bounding box 253 | */ 254 | public BoundingBox offset(double x, double y, double z) { 255 | return new BoundingBox(this.minX + x, this.minY + y, this.minZ + z, this.maxX + x, this.maxY + y, this.maxZ + z); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/generator/WorldGenerator.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.generator; 2 | 3 | import de.labystudio.game.world.World; 4 | import de.labystudio.game.world.block.Block; 5 | import de.labystudio.game.world.chunk.ChunkSection; 6 | import de.labystudio.game.world.generator.noise.NoiseGeneratorCombined; 7 | import de.labystudio.game.world.generator.noise.NoiseGeneratorOctaves; 8 | 9 | import java.util.Random; 10 | 11 | public final class WorldGenerator { 12 | 13 | private final World world; 14 | private final Random random; 15 | 16 | private final NoiseGenerator groundHeightNoise; 17 | private final NoiseGenerator hillNoise; 18 | private final NoiseGenerator sandInWaterNoise; 19 | private final NoiseGenerator forestNoise; 20 | private final NoiseGenerator holeNoise; 21 | private final NoiseGenerator islandNoise; 22 | private final NoiseGenerator caveNoise; 23 | 24 | private final int waterLevel = 64; 25 | 26 | public WorldGenerator(World world, int seed) { 27 | this.world = world; 28 | this.random = new Random(seed); 29 | 30 | // Create noise for the ground height 31 | this.groundHeightNoise = new NoiseGeneratorOctaves(this.random, 8); 32 | this.hillNoise = new NoiseGeneratorCombined(new NoiseGeneratorOctaves(this.random, 4), 33 | new NoiseGeneratorCombined(new NoiseGeneratorOctaves(this.random, 4), 34 | new NoiseGeneratorOctaves(this.random, 4))); 35 | 36 | // Water noise 37 | this.sandInWaterNoise = new NoiseGeneratorOctaves(this.random, 8); 38 | 39 | // Hole in hills and islands 40 | this.holeNoise = new NoiseGeneratorOctaves(this.random, 3); 41 | this.islandNoise = new NoiseGeneratorOctaves(this.random, 3); 42 | 43 | // Caves 44 | this.caveNoise = new NoiseGeneratorOctaves(this.random, 8); 45 | 46 | // Population 47 | this.forestNoise = new NoiseGeneratorOctaves(this.random, 8); 48 | } 49 | 50 | public void generateChunk(int chunkX, int chunkZ) { 51 | // For each block in the chunk 52 | for (int relX = 0; relX < ChunkSection.SIZE; relX++) { 53 | for (int relZ = 0; relZ < ChunkSection.SIZE; relZ++) { 54 | 55 | // Absolute position of the block 56 | int x = chunkX * ChunkSection.SIZE + relX; 57 | int z = chunkZ * ChunkSection.SIZE + relZ; 58 | 59 | // Extract height value of the noise 60 | double heightValue = this.groundHeightNoise.perlin(x, z); 61 | double hillValue = Math.max(0, this.hillNoise.perlin(x / 18d, z / 18d) * 6); 62 | 63 | // Calculate final height for this position 64 | int groundHeightY = (int) (heightValue / 10 + this.waterLevel + hillValue); 65 | 66 | if (groundHeightY < this.waterLevel) { 67 | // Generate water 68 | for (int y = 0; y <= this.waterLevel; y++) { 69 | // Use noise to place sand in water 70 | boolean sandInWater = this.sandInWaterNoise.perlin(x, z) < 0; 71 | Block block = y > groundHeightY ? Block.WATER : groundHeightY - y < 3 && sandInWater ? Block.SAND : Block.STONE; 72 | 73 | // Send water, sand and stone 74 | this.world.setBlockAt(x, y, z, block.getId()); 75 | } 76 | } else { 77 | // Generate height, the highest block is grass 78 | for (int y = 0; y <= groundHeightY; y++) { 79 | // Use the height map to determine the start of the water by shifting it 80 | boolean isBeach = heightValue < 5 && y < this.waterLevel + 2; 81 | Block block = y == groundHeightY ? isBeach ? Block.SAND : Block.GRASS : groundHeightY - y < 3 ? Block.DIRT : Block.STONE; 82 | 83 | // Set sand, grass, dirt and stone 84 | this.world.setBlockAt(x, y, z, block.getId()); 85 | } 86 | } 87 | 88 | /* 89 | int holeY = (int) (this.holeNouse.perlin(-x / 20F, -z / 20F) * 3F + this.waterLevel + 10); 90 | int holeHeight = (int) this.holeNouse.perlin(x / 4F, -z / 4F); 91 | 92 | if (holeHeight > 0) { 93 | for (int y = holeY - holeHeight; y <= holeY + holeHeight; y++) { 94 | this.world.setBlockAt(x, y, z, 1); 95 | } 96 | } 97 | */ 98 | 99 | // Random holes in hills 100 | int holePositionY = (int) (this.holeNoise.perlin(-x / 20F, -z / 20F) * 3F + this.waterLevel + 10); 101 | int holeHeight = (int) this.holeNoise.perlin(x / 4F, -z / 4F); 102 | 103 | if (holeHeight > 0) { 104 | for (int y = holePositionY - holeHeight; y <= holePositionY + holeHeight; y++) { 105 | if (y > this.waterLevel) { 106 | this.world.setBlockAt(x, y, z, 0); 107 | } 108 | } 109 | } 110 | 111 | // Floating islands 112 | int islandPositionY = (int) (this.islandNoise.perlin(-x / 10F, -z / 10F) * 3F + this.waterLevel + 10); 113 | int islandHeight = (int) (this.islandNoise.perlin(x / 4F, -z / 4F) * 4F); 114 | int islandRarity = (int) (this.islandNoise.perlin(x / 40F, z / 40F) * 4F) - 10; 115 | 116 | if (islandHeight > 0 && islandRarity > 0) { 117 | for (int y = islandPositionY - islandHeight; y <= islandPositionY + islandHeight; y++) { 118 | Block block = y == islandPositionY + islandHeight ? Block.GRASS : (islandPositionY + islandHeight) - y < 2 ? Block.DIRT : Block.STONE; 119 | this.world.setBlockAt(x, y, z, block.getId()); 120 | } 121 | } 122 | 123 | // Caves 124 | } 125 | } 126 | } 127 | 128 | public void populateChunk(int chunkX, int chunkZ) { 129 | for (int index = 0; index < 10; index++) { 130 | int x = this.random.nextInt(ChunkSection.SIZE); 131 | int z = this.random.nextInt(ChunkSection.SIZE); 132 | 133 | // Absolute position of the block 134 | int absoluteX = chunkX * ChunkSection.SIZE + x; 135 | int absoluteZ = chunkZ * ChunkSection.SIZE + z; 136 | 137 | // Use noise for a forest pattern 138 | double perlin = this.forestNoise.perlin(absoluteX * 10, absoluteZ * 10); 139 | if (perlin > 0 && this.random.nextInt(2) == 0) { 140 | 141 | // Get highest block at this position 142 | int highestY = this.world.getHighestBlockYAt(absoluteX, absoluteZ); 143 | 144 | // Don't place a tree if there is no grass 145 | if (this.world.getBlockAt(absoluteX, highestY, absoluteZ) == Block.GRASS.getId() 146 | && this.world.getBlockAt(absoluteX, highestY + 1, absoluteZ) == 0) { 147 | int treeHeight = this.random.nextInt(2) + 5; 148 | 149 | // Create tree log 150 | for (int i = 0; i < treeHeight; i++) { 151 | this.world.setBlockAt(absoluteX, highestY + i + 1, absoluteZ, Block.LOG.getId()); 152 | } 153 | 154 | // Create big leave ring 155 | for (int tx = -2; tx <= 2; tx++) { 156 | for (int ty = 0; ty < 2; ty++) { 157 | for (int tz = -2; tz <= 2; tz++) { 158 | boolean isCorner = Math.abs(tx) == 2 && Math.abs(tz) == 2; 159 | if (isCorner && this.random.nextBoolean()) { 160 | continue; 161 | } 162 | 163 | // Place leave if there is no block yet 164 | if (!this.world.isSolidBlockAt(absoluteX + tx, highestY + treeHeight + ty - 2, absoluteZ + tz)) { 165 | this.world.setBlockAt(absoluteX + tx, highestY + treeHeight + ty - 2, absoluteZ + tz, Block.LEAVE.getId()); 166 | } 167 | } 168 | } 169 | } 170 | 171 | // Create small leave ring on top 172 | for (int tx = -1; tx <= 1; tx++) { 173 | for (int ty = 0; ty < 2; ty++) { 174 | for (int tz = -1; tz <= 1; tz++) { 175 | boolean isCorner = Math.abs(tx) == 1 && Math.abs(tz) == 1; 176 | if (isCorner && (ty == 1 || this.random.nextBoolean())) { 177 | continue; 178 | } 179 | 180 | // Place leave if there is no block yet 181 | if (!this.world.isSolidBlockAt(absoluteX + tx, highestY + treeHeight + ty, absoluteZ + tz)) { 182 | this.world.setBlockAt(absoluteX + tx, highestY + treeHeight + ty, absoluteZ + tz, Block.LEAVE.getId()); 183 | } 184 | } 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/Tessellator.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render; 2 | 3 | import org.lwjgl.opengl.ARBVertexBufferObject; 4 | import org.lwjgl.opengl.GL11; 5 | import org.lwjgl.opengl.GLContext; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.FloatBuffer; 9 | import java.nio.IntBuffer; 10 | 11 | public class Tessellator { 12 | 13 | public static final Tessellator instance = new Tessellator(0x200000); 14 | 15 | private static final boolean convertQuadsToTriangles = true; 16 | private static final boolean tryVBO = false; 17 | 18 | private final ByteBuffer byteBuffer; 19 | private final IntBuffer intBuffer; 20 | private final FloatBuffer floatBuffer; 21 | 22 | private final int[] rawBuffer; 23 | private int vertexCount; 24 | 25 | private double textureU; 26 | private double textureV; 27 | 28 | private int color; 29 | private boolean hasColor; 30 | private boolean hasTexture; 31 | private boolean hasNormals; 32 | 33 | private int rawBufferIndex; 34 | private int addedVertices; 35 | private boolean isColorDisabled; 36 | private int drawMode; 37 | 38 | private double xOffset; 39 | private double yOffset; 40 | private double zOffset; 41 | 42 | private int normal; 43 | private boolean isDrawing; 44 | private final boolean useVBO = tryVBO && GLContext.getCapabilities().GL_ARB_vertex_buffer_object; 45 | private IntBuffer vertexBuffers; 46 | private int vboIndex; 47 | private final int vboCount = 10; 48 | private final int bufferSize; 49 | 50 | private Tessellator(int i) { 51 | this.bufferSize = i; 52 | this.byteBuffer = GLAllocation.createDirectByteBuffer(i * 4); 53 | this.rawBuffer = new int[i]; 54 | 55 | this.intBuffer = this.byteBuffer.asIntBuffer(); 56 | this.floatBuffer = this.byteBuffer.asFloatBuffer(); 57 | 58 | if (this.useVBO) { 59 | this.vertexBuffers = GLAllocation.createDirectIntBuffer(this.vboCount); 60 | ARBVertexBufferObject.glGenBuffersARB(this.vertexBuffers); 61 | } 62 | } 63 | 64 | public void draw() { 65 | if (!this.isDrawing) { 66 | throw new IllegalStateException("Not tesselating!"); 67 | } 68 | this.isDrawing = false; 69 | if (this.vertexCount > 0) { 70 | this.intBuffer.clear(); 71 | 72 | this.intBuffer.put(this.rawBuffer, 0, this.rawBufferIndex); 73 | this.byteBuffer.position(0); 74 | this.byteBuffer.limit(this.rawBufferIndex * 4); 75 | 76 | if (this.useVBO) { 77 | this.vboIndex = (this.vboIndex + 1) % this.vboCount; 78 | ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, this.vertexBuffers.get(this.vboIndex)); 79 | ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, this.byteBuffer, 35040); 80 | } 81 | if (this.hasTexture) { 82 | if (this.useVBO) { 83 | GL11.glTexCoordPointer(2, GL11.GL_FLOAT, GL11.GL_PIXEL_MODE_BIT, 12L); 84 | } else { 85 | this.floatBuffer.position(3); 86 | GL11.glTexCoordPointer(2, GL11.GL_PIXEL_MODE_BIT, this.floatBuffer); 87 | } 88 | GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); 89 | } 90 | if (this.hasColor) { 91 | if (this.useVBO) { 92 | GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, GL11.GL_PIXEL_MODE_BIT, 20L); 93 | } else { 94 | this.byteBuffer.position(20); 95 | GL11.glColorPointer(4, true, GL11.GL_PIXEL_MODE_BIT, this.byteBuffer); 96 | } 97 | GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); 98 | } 99 | if (this.hasNormals) { 100 | if (this.useVBO) { 101 | GL11.glNormalPointer(GL11.GL_BYTE, GL11.GL_PIXEL_MODE_BIT, 24L); 102 | } else { 103 | this.byteBuffer.position(24); 104 | GL11.glNormalPointer(GL11.GL_PIXEL_MODE_BIT, this.byteBuffer); 105 | } 106 | GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY); 107 | } 108 | if (this.useVBO) { 109 | GL11.glVertexPointer(GL11.GL_LINE_STRIP, GL11.GL_FLOAT, GL11.GL_PIXEL_MODE_BIT, 0L); 110 | } else { 111 | this.floatBuffer.position(0); 112 | GL11.glVertexPointer(GL11.GL_LINE_STRIP, GL11.GL_PIXEL_MODE_BIT, this.floatBuffer); 113 | } 114 | GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); 115 | if (this.drawMode == 7 && convertQuadsToTriangles) { 116 | GL11.glDrawArrays(4, GL11.GL_POINTS, this.vertexCount); 117 | } else { 118 | GL11.glDrawArrays(this.drawMode, GL11.GL_POINTS, this.vertexCount); 119 | } 120 | GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); 121 | if (this.hasTexture) { 122 | GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY); 123 | } 124 | if (this.hasColor) { 125 | GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); 126 | } 127 | if (this.hasNormals) { 128 | GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY); 129 | } 130 | } 131 | 132 | this.reset(); 133 | } 134 | 135 | private void reset() { 136 | this.vertexCount = 0; 137 | this.byteBuffer.clear(); 138 | this.rawBufferIndex = 0; 139 | this.addedVertices = 0; 140 | } 141 | 142 | public void startDrawingQuads() { 143 | this.startDrawing(7); 144 | } 145 | 146 | public void startDrawing(int i) { 147 | if (this.isDrawing) { 148 | throw new IllegalStateException("Already tessellating!"); 149 | } else { 150 | this.isDrawing = true; 151 | 152 | this.reset(); 153 | 154 | this.drawMode = i; 155 | this.hasNormals = false; 156 | this.hasColor = false; 157 | this.hasTexture = false; 158 | this.isColorDisabled = false; 159 | } 160 | } 161 | 162 | public void setTextureUV(double u, double v) { 163 | this.hasTexture = true; 164 | this.textureU = u; 165 | this.textureV = v; 166 | } 167 | 168 | public void setColorOpaque_F(float r, float g, float b) { 169 | this.setColorOpaque((int) (r * 255F), (int) (g * 255F), (int) (b * 255F)); 170 | } 171 | 172 | public void setColorRGBA_F(float r, float g, float b, float a) { 173 | this.setColorRGBA((int) (r * 255F), (int) (g * 255F), (int) (b * 255F), (int) (a * 255F)); 174 | } 175 | 176 | public void setColorRGB_F(float r, float g, float b) { 177 | this.setColorRGBA((int) (r * 255F), (int) (g * 255F), (int) (b * 255F), 255); 178 | } 179 | 180 | public void setColorOpaque(int r, int g, int b) { 181 | this.setColorRGBA(r, g, b, 255); 182 | } 183 | 184 | public void setColorRGBA(int r, int g, int b, int a) { 185 | if (this.isColorDisabled) { 186 | return; 187 | } 188 | 189 | this.hasColor = true; 190 | this.color = this.ensureColorRange(a) << 24 191 | | this.ensureColorRange(b) << 16 192 | | this.ensureColorRange(g) << 8 193 | | this.ensureColorRange(r); 194 | } 195 | 196 | public void addVertexWithUV(double x, double y, double z, double u, double v) { 197 | this.setTextureUV(u, v); 198 | this.addVertex(x, y, z); 199 | } 200 | 201 | public void addVertex(double x, double y, double z) { 202 | this.addedVertices++; 203 | 204 | if (this.drawMode == 7 && convertQuadsToTriangles && this.addedVertices % 4 == 0) { 205 | for (int i = 0; i < 2; i++) { 206 | int j = 8 * (3 - i); 207 | 208 | if (this.hasTexture) { 209 | this.rawBuffer[this.rawBufferIndex + 3] = this.rawBuffer[(this.rawBufferIndex - j) + 3]; 210 | this.rawBuffer[this.rawBufferIndex + 4] = this.rawBuffer[(this.rawBufferIndex - j) + 4]; 211 | } 212 | 213 | if (this.hasColor) { 214 | this.rawBuffer[this.rawBufferIndex + 5] = this.rawBuffer[(this.rawBufferIndex - j) + 5]; 215 | } 216 | 217 | this.rawBuffer[this.rawBufferIndex] = this.rawBuffer[(this.rawBufferIndex - j)]; 218 | this.rawBuffer[this.rawBufferIndex + 1] = this.rawBuffer[(this.rawBufferIndex - j) + 1]; 219 | this.rawBuffer[this.rawBufferIndex + 2] = this.rawBuffer[(this.rawBufferIndex - j) + 2]; 220 | 221 | this.vertexCount++; 222 | this.rawBufferIndex += 8; 223 | } 224 | } 225 | 226 | if (this.hasTexture) { 227 | this.rawBuffer[this.rawBufferIndex + 3] = Float.floatToRawIntBits((float) this.textureU); 228 | this.rawBuffer[this.rawBufferIndex + 4] = Float.floatToRawIntBits((float) this.textureV); 229 | } 230 | 231 | if (this.hasColor) { 232 | this.rawBuffer[this.rawBufferIndex + 5] = this.color; 233 | } 234 | 235 | if (this.hasNormals) { 236 | this.rawBuffer[this.rawBufferIndex + 6] = this.normal; 237 | } 238 | 239 | this.rawBuffer[this.rawBufferIndex] = Float.floatToRawIntBits((float) (x + this.xOffset)); 240 | this.rawBuffer[this.rawBufferIndex + 1] = Float.floatToRawIntBits((float) (y + this.yOffset)); 241 | this.rawBuffer[this.rawBufferIndex + 2] = Float.floatToRawIntBits((float) (z + this.zOffset)); 242 | 243 | this.rawBufferIndex += 8; 244 | this.vertexCount++; 245 | 246 | if (this.vertexCount % 4 == 0 && this.rawBufferIndex >= this.bufferSize - 32) { 247 | this.draw(); 248 | this.isDrawing = true; 249 | } 250 | } 251 | 252 | public void setColorOpaque_I(int rgb) { 253 | int r = rgb >> 16 & 0xff; 254 | int g = rgb >> 8 & 0xff; 255 | int b = rgb & 0xff; 256 | 257 | this.setColorOpaque(r, g, b); 258 | } 259 | 260 | public void setColorRGBA_I(int rgb, int alpha) { 261 | int k = rgb >> 16 & 0xff; 262 | int l = rgb >> 8 & 0xff; 263 | int i1 = rgb & 0xff; 264 | 265 | this.setColorRGBA(k, l, i1, alpha); 266 | } 267 | 268 | public void disableColor() { 269 | this.isColorDisabled = true; 270 | } 271 | 272 | public void setNormal(float x, float y, float z) { 273 | this.hasNormals = true; 274 | 275 | byte xByte = (byte) (int) (x * 128F); 276 | byte yByte = (byte) (int) (y * 127F); 277 | byte zByte = (byte) (int) (z * 127F); 278 | 279 | this.normal = xByte | yByte << 8 | zByte << 16; 280 | } 281 | 282 | public void setTranslationD(double xOffset, double yOffset, double zOffset) { 283 | this.xOffset = xOffset; 284 | this.yOffset = yOffset; 285 | this.zOffset = zOffset; 286 | } 287 | 288 | public void setTranslationF(float xOffset, float yOffset, float zOffset) { 289 | this.xOffset += xOffset; 290 | this.yOffset += yOffset; 291 | this.zOffset += zOffset; 292 | } 293 | 294 | private int ensureColorRange(int value) { 295 | return Math.min(Math.max(value, 0), 255); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/World.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world; 2 | 3 | import de.labystudio.game.render.world.IWorldAccess; 4 | import de.labystudio.game.util.BoundingBox; 5 | import de.labystudio.game.util.EnumBlockFace; 6 | import de.labystudio.game.util.MathHelper; 7 | import de.labystudio.game.world.block.Block; 8 | import de.labystudio.game.world.chunk.Chunk; 9 | import de.labystudio.game.world.chunk.ChunkSection; 10 | import de.labystudio.game.world.chunk.format.WorldFormat; 11 | import de.labystudio.game.world.generator.WorldGenerator; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.LinkedList; 18 | import java.util.Map; 19 | import java.util.Queue; 20 | 21 | public class World implements IWorldAccess { 22 | 23 | public static final int TOTAL_HEIGHT = ChunkSection.SIZE * 16 - 1; 24 | public Map chunks = new HashMap<>(); 25 | 26 | public boolean updateLightning = false; 27 | private final Queue lightUpdateQueue = new LinkedList<>(); 28 | 29 | private final WorldGenerator generator = new WorldGenerator(this, (int) (System.currentTimeMillis() % 100000)); 30 | public WorldFormat format = new WorldFormat(this, new File("saves/World1")); 31 | 32 | public World() { 33 | this.load(); 34 | } 35 | 36 | public void load() { 37 | if (this.format.exists()) { 38 | // Load chunks 39 | this.format.load((x, z, array) -> { 40 | Chunk chunk = this.getChunkAt(x, z); 41 | chunk.setSections(array); 42 | chunk.queueForRebuild(); 43 | }); 44 | 45 | this.updateLightning = true; 46 | } else { 47 | int radius = 20; 48 | 49 | // Generator new spawn chunks 50 | for (int x = -radius; x <= radius; x++) { 51 | for (int z = -radius; z <= radius; z++) { 52 | this.generator.generateChunk(x, z); 53 | } 54 | } 55 | 56 | // Populate trees 57 | for (int x = -radius; x <= radius; x++) { 58 | for (int z = -radius; z <= radius; z++) { 59 | this.generator.populateChunk(x, z); 60 | } 61 | } 62 | 63 | this.updateLightning = true; 64 | } 65 | } 66 | 67 | public void save() { 68 | try { 69 | this.format.saveChunks(); 70 | } catch (IOException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | 75 | public void onTick() { 76 | // Light updates 77 | if (!this.lightUpdateQueue.isEmpty()) { 78 | 79 | // Handle 128 light updates per tick 80 | for (int i = 0; i < 128; i++) { 81 | 82 | // Get next position to update 83 | Long positionIndex = this.lightUpdateQueue.poll(); 84 | if (positionIndex != null) { 85 | this.updateBlockLightsAtXZ((int) (positionIndex >> 32L), positionIndex.intValue()); 86 | } else { 87 | break; 88 | } 89 | } 90 | } 91 | } 92 | 93 | public ArrayList getCollisionBoxes(BoundingBox aabb) { 94 | ArrayList boundingBoxList = new ArrayList<>(); 95 | 96 | int minX = MathHelper.floor_double(aabb.minX); 97 | int maxX = MathHelper.floor_double(aabb.maxX + 1.0); 98 | int minY = MathHelper.floor_double(aabb.minY); 99 | int maxY = MathHelper.floor_double(aabb.maxY + 1.0); 100 | int minZ = MathHelper.floor_double(aabb.minZ); 101 | int maxZ = MathHelper.floor_double(aabb.maxZ + 1.0); 102 | 103 | for (int x = minX; x < maxX; x++) { 104 | for (int y = minY; y < maxY; y++) { 105 | for (int z = minZ; z < maxZ; z++) { 106 | if (this.isSolidBlockAt(x, y, z)) { 107 | boundingBoxList.add(new BoundingBox(x, y, z, x + 1, y + 1, z + 1)); 108 | } 109 | } 110 | } 111 | } 112 | return boundingBoxList; 113 | } 114 | 115 | public void blockChanged(int x, int y, int z) { 116 | this.setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1); 117 | } 118 | 119 | public void allChunksChanged() { 120 | for (Chunk chunk : this.chunks.values()) { 121 | chunk.queueForRebuild(); 122 | } 123 | } 124 | 125 | public void setDirty(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { 126 | // To chunk coordinates 127 | minX = minX >> 4; 128 | maxX = maxX >> 4; 129 | minY = minY >> 4; 130 | maxY = maxY >> 4; 131 | minZ = minZ >> 4; 132 | maxZ = maxZ >> 4; 133 | 134 | // Minimum and maximum y 135 | minY = Math.max(0, minY); 136 | maxY = Math.min(15, maxY); 137 | 138 | for (int x = minX; x <= maxX; x++) { 139 | for (int y = minY; y <= maxY; y++) { 140 | for (int z = minZ; z <= maxZ; z++) { 141 | this.getChunkAt(x, y, z).queueForRebuild(); 142 | } 143 | } 144 | } 145 | } 146 | 147 | public void setBlockAt(int x, int y, int z, int type) { 148 | ChunkSection chunkSection = this.getChunkAtBlock(x, y, z); 149 | if (chunkSection != null) { 150 | chunkSection.setBlockAt(x & 15, y & 15, z & 15, type); 151 | 152 | if (this.updateLightning) { 153 | this.updateBlockLightAt(x, y, z); 154 | } 155 | 156 | this.blockChanged(x, y, z); 157 | } 158 | } 159 | 160 | public void updateBlockLightAt(int x, int y, int z) { 161 | // Calculate brightness for target block 162 | int lightLevel = this.isHighestBlockAt(x, y, z) ? 15 : this.calculateLightAt(x, y, z); 163 | 164 | // Update target block light 165 | this.getChunkAtBlock(x, y, z).setLightAt(x & 15, y & 15, z & 15, lightLevel); 166 | 167 | // Update block lights below the target block and the surrounding blocks 168 | for (int offsetX = -1; offsetX <= 1; offsetX++) { 169 | for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { 170 | this.updateBlockLightsAtXZ(x + offsetX, z + offsetZ); 171 | } 172 | } 173 | } 174 | 175 | private void updateBlockLightsAtXZ(int x, int z) { 176 | boolean lightChanged = false; 177 | int skyLevel = 15; 178 | 179 | // Scan from the top to the bottom 180 | for (int y = TOTAL_HEIGHT; y >= 0; y--) { 181 | if (!this.isTransparentBlockAt(x, y, z)) { 182 | // Sun is blocked because of solid block 183 | skyLevel = 0; 184 | } else { 185 | // Get opacity of this block 186 | short typeId = this.getBlockAt(x, y, z); 187 | float translucence = typeId == 0 ? 1.0F : 1.0F - Block.getById(typeId).getOpacity(); 188 | 189 | // Decrease strength of the skylight by the opacity of the block 190 | skyLevel *= translucence; 191 | 192 | // Get previous block light 193 | float prevBlockLight = this.getLightAt(x, y, z); 194 | 195 | // Combine skylight with the calculated block light and decrease strength by the opacity of the block 196 | int blockLight = (int) (Math.max(skyLevel, this.calculateLightAt(x, y, z)) * translucence); 197 | 198 | // Did one of the light change inside of the range? 199 | if (prevBlockLight != blockLight) { 200 | lightChanged = true; 201 | } 202 | 203 | // Apply the new light to the block 204 | this.setLightAt(x, y, z, blockLight); 205 | } 206 | } 207 | 208 | // Chain reaction, update next affected blocks 209 | if (lightChanged && this.lightUpdateQueue.size() < 512) { 210 | for (int offsetX = -1; offsetX <= 1; offsetX++) { 211 | for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { 212 | long positionIndex = (long) (x + offsetX) << 32 | (z + offsetZ) & 0xFFFFFFFFL; 213 | 214 | // Add block range to update queue 215 | if (!this.lightUpdateQueue.contains(positionIndex)) { 216 | this.lightUpdateQueue.add(positionIndex); 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | private void setLightAt(int x, int y, int z, int light) { 224 | ChunkSection chunkSection = this.getChunkAtBlock(x, y, z); 225 | if (chunkSection != null) { 226 | chunkSection.setLightAt(x & 15, y & 15, z & 15, light); 227 | chunkSection.queueForRebuild(); 228 | } 229 | } 230 | 231 | @Override 232 | public int getLightAt(int x, int y, int z) { 233 | ChunkSection chunkSection = this.getChunkAtBlock(x, y, z); 234 | return chunkSection == null ? 15 : chunkSection.getLightAt(x & 15, y & 15, z & 15); 235 | } 236 | 237 | private boolean isHighestBlockAt(int x, int y, int z) { 238 | for (int i = y + 1; i < TOTAL_HEIGHT; i++) { 239 | if (this.isSolidBlockAt(x, i, z)) { 240 | return false; 241 | } 242 | } 243 | return true; 244 | } 245 | 246 | public int getHighestBlockYAt(int x, int z) { 247 | for (int y = TOTAL_HEIGHT; y > 0; y--) { 248 | if (this.isSolidBlockAt(x, y, z)) { 249 | return y; 250 | } 251 | } 252 | return 0; 253 | } 254 | 255 | private int calculateLightAt(int x, int y, int z) { 256 | int maxBrightness = 0; 257 | 258 | // Get maximal brightness of surround blocks 259 | for (EnumBlockFace face : EnumBlockFace.values()) { 260 | if (this.isTransparentBlockAt(x + face.x, y + face.y, z + face.z)) { 261 | int brightness = this.getLightAt(x + face.x, y + face.y, z + face.z); 262 | 263 | maxBrightness = Math.max(maxBrightness, brightness); 264 | } 265 | } 266 | 267 | // Decrease maximum brightness by 6% 268 | return Math.max(0, maxBrightness - 1); 269 | } 270 | 271 | public boolean isSolidBlockAt(int x, int y, int z) { 272 | short typeId = this.getBlockAt(x, y, z); 273 | return typeId != 0 && Block.getById(typeId).isSolid(); 274 | } 275 | 276 | public boolean isTransparentBlockAt(int x, int y, int z) { 277 | short typeId = this.getBlockAt(x, y, z); 278 | return typeId == 0 || Block.getById(typeId).isTransparent(); 279 | } 280 | 281 | @Override 282 | public short getBlockAt(int x, int y, int z) { 283 | ChunkSection chunkSection = this.getChunkAtBlock(x, y, z); 284 | return chunkSection == null ? 0 : chunkSection.getBlockAt(x & 15, y & 15, z & 15); 285 | } 286 | 287 | public ChunkSection getChunkAt(int chunkX, int layerY, int chunkZ) { 288 | return this.getChunkAt(chunkX, chunkZ).getSection(layerY); 289 | } 290 | 291 | public Chunk getChunkAt(int x, int z) { 292 | long chunkIndex = Chunk.getIndex(x, z); 293 | Chunk chunk = this.chunks.get(chunkIndex); 294 | if (chunk == null) { 295 | chunk = new Chunk(this, x, z); 296 | 297 | // Copy map because of ConcurrentModificationException 298 | Map chunks = new HashMap<>(this.chunks); 299 | chunks.put(chunkIndex, chunk); 300 | this.chunks = chunks; 301 | } 302 | return chunk; 303 | } 304 | 305 | public boolean isChunkLoaded(int x, int z) { 306 | long chunkIndex = (long) x & 4294967295L | ((long) z & 4294967295L) << 32; 307 | return this.chunks.containsKey(chunkIndex); 308 | } 309 | 310 | public boolean isChunkLoadedAt(int x, int y, int z) { 311 | return this.isChunkLoaded(x >> 4, z >> 4); 312 | } 313 | 314 | public ChunkSection getChunkAtBlock(int x, int y, int z) { 315 | Chunk chunk = this.getChunkAt(x >> 4, z >> 4); 316 | return y < 0 || y > TOTAL_HEIGHT ? null : chunk.getSection(y >> 4); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/Minecraft.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game; 2 | 3 | import de.labystudio.game.player.Player; 4 | import de.labystudio.game.render.gui.FontRenderer; 5 | import de.labystudio.game.render.gui.GuiRenderer; 6 | import de.labystudio.game.util.*; 7 | import de.labystudio.game.world.World; 8 | import de.labystudio.game.world.WorldRenderer; 9 | import de.labystudio.game.world.block.Block; 10 | import de.labystudio.game.world.chunk.ChunkSection; 11 | import org.lwjgl.LWJGLException; 12 | import org.lwjgl.input.Keyboard; 13 | import org.lwjgl.input.Mouse; 14 | import org.lwjgl.opengl.Display; 15 | import org.lwjgl.opengl.GL11; 16 | import org.lwjgl.util.glu.GLU; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | 21 | public class Minecraft implements Runnable { 22 | 23 | private final MinecraftWindow gameWindow = new MinecraftWindow(this); 24 | protected final GuiRenderer gui = new GuiRenderer(); 25 | 26 | // Game 27 | private final Timer timer = new Timer(20.0F); 28 | private World world; 29 | private WorldRenderer worldRenderer; 30 | private FontRenderer fontRenderer; 31 | 32 | // Player 33 | private Player player; 34 | private Block pickedBlock = Block.STONE; 35 | 36 | // States 37 | private boolean paused = false; 38 | private boolean running = true; 39 | private int fps; 40 | 41 | public void init() throws LWJGLException, IOException { 42 | // Setup display 43 | this.gameWindow.init(); 44 | this.gui.init(this.gameWindow); 45 | this.gui.loadTextures(); 46 | 47 | // Setup rendering 48 | this.world = new World(); 49 | this.worldRenderer = new WorldRenderer(this.world); 50 | this.player = new Player(this.world); 51 | this.fontRenderer = new FontRenderer(this.gui, "/font.png"); 52 | 53 | // Setup controls 54 | Keyboard.create(); 55 | Mouse.create(); 56 | Mouse.setGrabbed(true); 57 | } 58 | 59 | public void run() { 60 | try { 61 | this.init(); 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | System.exit(0); 65 | } 66 | 67 | long lastTime = System.currentTimeMillis(); 68 | int frames = 0; 69 | try { 70 | do { 71 | this.timer.advanceTime(); 72 | for (int i = 0; i < this.timer.ticks; i++) { 73 | this.tick(); 74 | } 75 | 76 | // Limit framerate 77 | //Thread.sleep(5L); 78 | 79 | GL11.glViewport(0, 0, this.gameWindow.displayWidth, this.gameWindow.displayHeight); 80 | this.render(this.timer.partialTicks); 81 | this.gameWindow.update(); 82 | checkError(); 83 | 84 | frames++; 85 | while (System.currentTimeMillis() >= lastTime + 1000L) { 86 | this.fps = frames; 87 | 88 | lastTime += 1000L; 89 | frames = 0; 90 | } 91 | 92 | // Escape 93 | if (Keyboard.isKeyDown(1) || !Display.isActive()) { 94 | this.paused = true; 95 | Mouse.setGrabbed(false); 96 | } 97 | 98 | // Toggle fullscreen 99 | if (Keyboard.isKeyDown(87)) { 100 | this.gameWindow.toggleFullscreen(); 101 | } 102 | 103 | } while (!Display.isCloseRequested() && this.running); 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } finally { 107 | // Shutdown 108 | this.world.save(); 109 | this.gameWindow.destroy(); 110 | 111 | Mouse.destroy(); 112 | Keyboard.destroy(); 113 | 114 | System.exit(0); 115 | } 116 | } 117 | 118 | public void shutdown() { 119 | this.running = false; 120 | } 121 | 122 | public void tick() { 123 | this.player.onTick(); 124 | this.world.onTick(); 125 | this.worldRenderer.onTick(); 126 | } 127 | 128 | private void moveCameraToPlayer(float partialTicks) { 129 | GL11.glTranslatef(0.0F, 0.0F, -0.3F); 130 | GL11.glRotatef(this.player.pitch, 1.0F, 0.0F, 0.0F); 131 | GL11.glRotatef(this.player.yaw, 0.0F, 1.0F, 0.0F); 132 | 133 | double x = this.player.prevX + (this.player.x - this.player.prevX) * partialTicks; 134 | double y = this.player.prevY + (this.player.y - this.player.prevY) * partialTicks; 135 | double z = this.player.prevZ + (this.player.z - this.player.prevZ) * partialTicks; 136 | GL11.glTranslated(-x, -y, -z); 137 | 138 | // Eye height 139 | GL11.glTranslatef(0.0F, -this.player.getEyeHeight(), 0.0F); 140 | } 141 | 142 | private void setupCamera(float partialTicks) { 143 | double zFar = Math.pow(WorldRenderer.RENDER_DISTANCE * ChunkSection.SIZE, 2); 144 | 145 | GL11.glMatrixMode(GL11.GL_PROJECTION); 146 | GL11.glLoadIdentity(); 147 | GLU.gluPerspective(85.0F + this.player.getFOVModifier(), 148 | (float) this.gameWindow.displayWidth / (float) this.gameWindow.displayHeight, 0.05F, (float) zFar); 149 | GL11.glMatrixMode(GL11.GL_MODELVIEW); 150 | GL11.glLoadIdentity(); 151 | this.moveCameraToPlayer(partialTicks); 152 | } 153 | 154 | 155 | public void render(float partialTicks) { 156 | float mouseMoveX = Mouse.getDX(); 157 | float mouseMoveY = Mouse.getDY(); 158 | 159 | if (!this.paused) { 160 | this.player.turn(mouseMoveX, mouseMoveY); 161 | } 162 | 163 | // Calculate the target block of the player 164 | HitResult hitResult = this.getTargetBlock(); 165 | 166 | while (Mouse.next()) { 167 | if ((Mouse.getEventButton() == 0) && (Mouse.getEventButtonState())) { 168 | // Resume game if paused 169 | if (this.paused) { 170 | this.paused = false; 171 | 172 | Mouse.setGrabbed(true); 173 | } else { 174 | // Destroy block 175 | if (hitResult != null) { 176 | this.world.setBlockAt(hitResult.x, hitResult.y, hitResult.z, 0); 177 | } 178 | } 179 | } 180 | 181 | // Place block 182 | if ((Mouse.getEventButton() == 1) && (Mouse.getEventButtonState())) { 183 | if (hitResult != null) { 184 | int x = hitResult.x + hitResult.face.x; 185 | int y = hitResult.y + hitResult.face.y; 186 | int z = hitResult.z + hitResult.face.z; 187 | 188 | BoundingBox placedBoundingBox = new BoundingBox(x, y, z, x + 1, y + 1, z + 1); 189 | 190 | // Don't place blocks if the player is standing there 191 | if (!placedBoundingBox.intersects(this.player.boundingBox)) { 192 | this.world.setBlockAt(x, y, z, this.pickedBlock.getId()); 193 | } 194 | 195 | } 196 | } 197 | 198 | // Pick block 199 | if ((Mouse.getEventButton() == 2) && (Mouse.getEventButtonState())) { 200 | if (hitResult != null) { 201 | short typeId = this.world.getBlockAt(hitResult.x, hitResult.y, hitResult.z); 202 | if (typeId != 0) { 203 | this.pickedBlock = Block.getById(typeId); 204 | } 205 | } 206 | } 207 | } 208 | while (Keyboard.next()) { 209 | if ((Keyboard.getEventKey() == 28) && (Keyboard.getEventKeyState())) { 210 | this.world.save(); 211 | } 212 | } 213 | 214 | // Clear color and depth buffer 215 | GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); 216 | 217 | // Camera 218 | this.setupCamera(partialTicks); 219 | 220 | int cameraChunkX = (int) this.player.x >> 4; 221 | int cameraChunkZ = (int) this.player.z >> 4; 222 | 223 | // Fog 224 | GL11.glEnable(GL11.GL_FOG); 225 | this.worldRenderer.setupFog(this.player.isHeadInWater()); 226 | 227 | // Setup rendering for solid blocks 228 | GL11.glDisable(GL11.GL_BLEND); 229 | GL11.glDisable(GL11.GL_ALPHA_TEST); 230 | GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); 231 | GL11.glEnable(GL11.GL_CULL_FACE); 232 | 233 | // Render solid blocks 234 | this.worldRenderer.render(cameraChunkX, cameraChunkZ, EnumWorldBlockLayer.SOLID); 235 | 236 | // Enable alpha and disable face culling 237 | GL11.glEnable(GL11.GL_BLEND); 238 | GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 239 | GL11.glEnable(GL11.GL_ALPHA_TEST); 240 | GL11.glAlphaFunc(519, -1.0F); 241 | GL11.glDisable(GL11.GL_CULL_FACE); 242 | 243 | // Render cutout blocks (Leaves, glass, water..) 244 | this.worldRenderer.render(cameraChunkX, cameraChunkZ, EnumWorldBlockLayer.CUTOUT); 245 | 246 | // Render selection 247 | if (hitResult != null) { 248 | this.renderSelection(hitResult); 249 | } 250 | 251 | GL11.glDisable(GL11.GL_CULL_FACE); 252 | GL11.glDisable(GL11.GL_TEXTURE_2D); 253 | GL11.glDisable(GL11.GL_FOG); 254 | 255 | this.gui.setupCamera(); 256 | this.gui.renderCrosshair(); 257 | 258 | // Enable alpha 259 | GL11.glEnable(GL11.GL_BLEND); 260 | GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 261 | 262 | this.fontRenderer.drawString("FPS: " + this.fps, 2, 2); 263 | } 264 | 265 | public void renderSelection(HitResult hitResult) { 266 | GL11.glColor4f(0.0F, 0.0F, 0.0F, 1.0F); 267 | GL11.glLineWidth(1); 268 | this.worldRenderer.getBlockRenderer().drawBoundingBox(hitResult.x, hitResult.y, hitResult.z, 269 | hitResult.x + 1, hitResult.y + 1, hitResult.z + 1); 270 | } 271 | 272 | private HitResult getTargetBlock() { 273 | double yaw = Math.toRadians(-this.player.yaw + 90); 274 | double pitch = Math.toRadians(-this.player.pitch); 275 | 276 | double xzLen = Math.cos(pitch); 277 | double vectorX = xzLen * Math.cos(yaw); 278 | double vectorY = Math.sin(pitch); 279 | double vectorZ = xzLen * Math.sin(-yaw); 280 | 281 | double targetX = this.player.x - vectorX; 282 | double targetY = this.player.y + this.player.getEyeHeight() - 0.08D - vectorY; 283 | double targetZ = this.player.z - vectorZ; 284 | 285 | int shift = -1; 286 | 287 | int prevAirX = (int) (targetX < 0 ? targetX + shift : targetX); 288 | int prevAirY = (int) (targetY < 0 ? targetY + shift : targetY); 289 | int prevAirZ = (int) (targetZ < 0 ? targetZ + shift : targetZ); 290 | 291 | for (int i = 0; i < 800; i++) { 292 | targetX += vectorX / 10D; 293 | targetY += vectorY / 10D; 294 | targetZ += vectorZ / 10D; 295 | 296 | int hitX = (int) (targetX < 0 ? targetX + shift : targetX); 297 | int hitY = (int) (targetY < 0 ? targetY + shift : targetY); 298 | int hitZ = (int) (targetZ < 0 ? targetZ + shift : targetZ); 299 | 300 | EnumBlockFace targetFace = null; 301 | for (EnumBlockFace type : EnumBlockFace.values()) { 302 | if (prevAirX == hitX + type.x && prevAirY == hitY + type.y && prevAirZ == hitZ + type.z) { 303 | targetFace = type; 304 | break; 305 | } 306 | } 307 | 308 | if (this.world.isSolidBlockAt(hitX, hitY, hitZ)) { 309 | if (targetFace == null) { 310 | return null; 311 | } 312 | return new HitResult(hitX, hitY, hitZ, targetFace); 313 | } else { 314 | prevAirX = (int) (targetX < 0 ? targetX + shift : targetX); 315 | prevAirY = (int) (targetY < 0 ? targetY + shift : targetY); 316 | prevAirZ = (int) (targetZ < 0 ? targetZ + shift : targetZ); 317 | } 318 | } 319 | return null; 320 | } 321 | 322 | public static void checkError() { 323 | int error = GL11.glGetError(); 324 | if (error != 0) { 325 | throw new IllegalStateException(GLU.gluErrorString(error)); 326 | } 327 | } 328 | 329 | public static void main(String[] args) throws LWJGLException { 330 | // Set library path if not available 331 | if (System.getProperty("org.lwjgl.librarypath") == null) { 332 | System.setProperty("org.lwjgl.librarypath", new File("natives").getAbsolutePath()); 333 | } 334 | 335 | new Thread(new Minecraft(), "Game Thread").start(); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/world/chunk/format/RegionFormat.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.world.chunk.format; 2 | 3 | /* 4 | ** 2011 January 5 5 | ** 6 | ** The author disclaims copyright to this source code. In place of 7 | ** a legal notice, here is a blessing: 8 | ** 9 | ** May you do good and not evil. 10 | ** May you find forgiveness for yourself and forgive others. 11 | ** May you share freely, never taking more than you give. 12 | **/ 13 | 14 | /* 15 | * 2011 February 16 16 | * 17 | * This source code is based on the work of Scaevolus (see notice above). 18 | * It has been slightly modified by Mojang AB (constants instead of magic 19 | * numbers, a chunk timestamp header, and auto-formatted according to our 20 | * formatter template). 21 | * 22 | */ 23 | 24 | // Interfaces with region files on the disk 25 | 26 | /* 27 | 28 | Region File Format 29 | 30 | Concept: The minimum unit of storage on hard drives is 4KB. 90% of Minecraft 31 | chunks are smaller than 4KB. 99% are smaller than 8KB. Write a simple 32 | container to store chunks in single files in runs of 4KB sectors. 33 | 34 | Each region file represents a 32x32 group of chunks. The conversion from 35 | chunk number to region number is floor(coord / 32): a chunk at (30, -3) 36 | would be in region (0, -1), and one at (70, -30) would be at (3, -1). 37 | Region files are named "r.x.z.data", where x and z are the region coordinates. 38 | 39 | A region file begins with a 4KB header that describes where chunks are stored 40 | in the file. A 4-byte big-endian integer represents sector offsets and sector 41 | counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the 42 | file. The bottom byte of the chunk offset indicates the number of sectors the 43 | chunk takes up, and the top 3 bytes represent the sector number of the chunk. 44 | Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up 45 | at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk 46 | offset is 0, the corresponding chunk is not stored in the region file. 47 | 48 | Chunk data begins with a 4-byte big-endian integer representing the chunk data 49 | length in bytes, not counting the length field. The length must be smaller than 50 | 4096 times the number of sectors. The next byte is a version field, to allow 51 | backwards-compatible updates to how chunks are encoded. 52 | 53 | A version of 1 represents a gzipped NBT file. The gzipped data is the chunk 54 | length - 1. 55 | 56 | A version of 2 represents a deflated (zlib compressed) NBT file. The deflated 57 | data is the chunk length - 1. 58 | 59 | */ 60 | 61 | import java.io.*; 62 | import java.util.ArrayList; 63 | import java.util.zip.DeflaterOutputStream; 64 | import java.util.zip.GZIPInputStream; 65 | import java.util.zip.InflaterInputStream; 66 | 67 | public class RegionFormat { 68 | 69 | private static final int VERSION_GZIP = 1; 70 | private static final int VERSION_DEFLATE = 2; 71 | 72 | private static final int SECTOR_BYTES = 4096; 73 | private static final int SECTOR_INTS = SECTOR_BYTES / 4; 74 | 75 | static final int CHUNK_HEADER_SIZE = 5; 76 | private static final byte emptySector[] = new byte[4096]; 77 | 78 | private final File fileName; 79 | private RandomAccessFile file; 80 | private final int offsets[]; 81 | private final int chunkTimestamps[]; 82 | private ArrayList sectorFree; 83 | private int sizeDelta; 84 | private long lastModified = 0; 85 | 86 | public int x; 87 | public int z; 88 | 89 | public RegionFormat( File path ) { 90 | offsets = new int[SECTOR_INTS]; 91 | chunkTimestamps = new int[SECTOR_INTS]; 92 | 93 | String[] data = path.getName().split( "\\." ); 94 | this.x = Integer.parseInt( data[1] ); 95 | this.z = Integer.parseInt( data[2] ); 96 | 97 | fileName = path; 98 | debugln( "REGION LOAD " + fileName ); 99 | 100 | sizeDelta = 0; 101 | 102 | try { 103 | if ( path.exists() ) { 104 | lastModified = path.lastModified(); 105 | } 106 | 107 | file = new RandomAccessFile( path, "rw" ); 108 | 109 | if ( file.length() < SECTOR_BYTES ) { 110 | /* we need to write the chunk offset table */ 111 | for ( int i = 0; i < SECTOR_INTS; ++i ) { 112 | file.writeInt( 0 ); 113 | } 114 | // write another sector for the timestamp info 115 | for ( int i = 0; i < SECTOR_INTS; ++i ) { 116 | file.writeInt( 0 ); 117 | } 118 | 119 | sizeDelta += SECTOR_BYTES * 2; 120 | } 121 | 122 | if ( ( file.length() & 0xfff ) != 0 ) { 123 | /* the file size is not a multiple of 4KB, grow it */ 124 | for ( int i = 0; i < ( file.length() & 0xfff ); ++i ) { 125 | file.write( (byte) 0 ); 126 | } 127 | } 128 | 129 | /* set up the available sector map */ 130 | int nSectors = (int) file.length() / SECTOR_BYTES; 131 | sectorFree = new ArrayList( nSectors ); 132 | 133 | for ( int i = 0; i < nSectors; ++i ) { 134 | sectorFree.add( true ); 135 | } 136 | 137 | sectorFree.set( 0, false ); // chunk offset table 138 | sectorFree.set( 1, false ); // for the last modified info 139 | 140 | file.seek( 0 ); 141 | for ( int i = 0; i < SECTOR_INTS; ++i ) { 142 | int offset = file.readInt(); 143 | offsets[i] = offset; 144 | if ( offset != 0 && ( offset >> 8 ) + ( offset & 0xFF ) <= sectorFree.size() ) { 145 | for ( int sectorNum = 0; sectorNum < ( offset & 0xFF ); ++sectorNum ) { 146 | sectorFree.set( ( offset >> 8 ) + sectorNum, false ); 147 | } 148 | } 149 | } 150 | for ( int i = 0; i < SECTOR_INTS; ++i ) { 151 | int lastModValue = file.readInt(); 152 | chunkTimestamps[i] = lastModValue; 153 | } 154 | } catch ( IOException e ) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | 159 | /* the modification date of the region file when it was first opened */ 160 | public long lastModified( ) { 161 | return lastModified; 162 | } 163 | 164 | /* gets how much the region file has grown since it was last checked */ 165 | public synchronized int getSizeDelta( ) { 166 | int ret = sizeDelta; 167 | sizeDelta = 0; 168 | return ret; 169 | } 170 | 171 | // various small debug printing helpers 172 | private void debug( String in ) { 173 | // System.out.print(in); 174 | } 175 | 176 | private void debugln( String in ) { 177 | debug( in + "\n" ); 178 | } 179 | 180 | private void debug( String mode, int x, int z, String in ) { 181 | debug( "REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] = " + in ); 182 | } 183 | 184 | private void debug( String mode, int x, int z, int count, String in ) { 185 | debug( "REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] " + count + "B = " + in ); 186 | } 187 | 188 | private void debugln( String mode, int x, int z, String in ) { 189 | debug( mode, x, z, in + "\n" ); 190 | } 191 | 192 | /* 193 | * gets an (uncompressed) stream representing the chunk data returns null if 194 | * the chunk is not found or an error occurs 195 | */ 196 | public synchronized DataInputStream getChunkDataInputStream( int x, int z ) { 197 | if ( outOfBounds( x, z ) ) { 198 | debugln( "READ", x, z, "out of bounds" ); 199 | return null; 200 | } 201 | 202 | try { 203 | int offset = getOffset( x, z ); 204 | if ( offset == 0 ) { 205 | // debugln("READ", x, z, "miss"); 206 | return null; 207 | } 208 | 209 | int sectorNumber = offset >> 8; 210 | int numSectors = offset & 0xFF; 211 | 212 | if ( sectorNumber + numSectors > sectorFree.size() ) { 213 | debugln( "READ", x, z, "invalid sector" ); 214 | return null; 215 | } 216 | 217 | file.seek( sectorNumber * SECTOR_BYTES ); 218 | int length = file.readInt(); 219 | 220 | if ( length > SECTOR_BYTES * numSectors ) { 221 | debugln( "READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors ); 222 | return null; 223 | } 224 | 225 | byte version = file.readByte(); 226 | if ( version == VERSION_GZIP ) { 227 | byte[] data = new byte[length - 1]; 228 | file.read( data ); 229 | DataInputStream ret = new DataInputStream( new GZIPInputStream( new ByteArrayInputStream( data ) ) ); 230 | // debug("READ", x, z, " = found"); 231 | return ret; 232 | } else if ( version == VERSION_DEFLATE ) { 233 | byte[] data = new byte[length - 1]; 234 | file.read( data ); 235 | DataInputStream ret = new DataInputStream( new InflaterInputStream( new ByteArrayInputStream( data ) ) ); 236 | // debug("READ", x, z, " = found"); 237 | return ret; 238 | } 239 | 240 | debugln( "READ", x, z, "unknown version " + version ); 241 | return null; 242 | } catch ( IOException e ) { 243 | debugln( "READ", x, z, "exception" ); 244 | return null; 245 | } 246 | } 247 | 248 | public DataOutputStream getChunkDataOutputStream( int x, int z ) { 249 | if ( outOfBounds( x, z ) ) return null; 250 | 251 | return new DataOutputStream( new DeflaterOutputStream( new ChunkBuffer( x, z ) ) ); 252 | } 253 | 254 | /* 255 | * lets chunk writing be multithreaded by not locking the whole file as a 256 | * chunk is serializing -- only writes when serialization is over 257 | */ 258 | class ChunkBuffer extends ByteArrayOutputStream { 259 | private int x, z; 260 | 261 | public ChunkBuffer( int x, int z ) { 262 | super(); 263 | this.x = x; 264 | this.z = z; 265 | } 266 | 267 | public void close( ) { 268 | RegionFormat.this.write( x, z, buf, buf.length ); 269 | } 270 | } 271 | 272 | /* write a chunk at (x,z) with length bytes of data to disk */ 273 | protected synchronized void write( int x, int z, byte[] data, int length ) { 274 | try { 275 | int offset = getOffset( x, z ); 276 | int sectorNumber = offset >> 8; 277 | int sectorsAllocated = offset & 0xFF; 278 | int sectorsNeeded = ( length + CHUNK_HEADER_SIZE ) / SECTOR_BYTES + 1; 279 | 280 | // maximum chunk size is 1MB 281 | if ( sectorsNeeded >= 256 ) { 282 | return; 283 | } 284 | 285 | if ( sectorNumber != 0 && sectorsAllocated == sectorsNeeded ) { 286 | /* we can simply overwrite the old sectors */ 287 | debug( "SAVE", x, z, length, "rewrite" ); 288 | write( sectorNumber, data, length ); 289 | } else { 290 | /* we need to allocate new sectors */ 291 | 292 | /* mark the sectors previously used for this chunk as free */ 293 | for ( int i = 0; i < sectorsAllocated; ++i ) { 294 | sectorFree.set( sectorNumber + i, true ); 295 | } 296 | 297 | /* scan for a free space large enough to store this chunk */ 298 | int runStart = sectorFree.indexOf( true ); 299 | int runLength = 0; 300 | if ( runStart != -1 ) { 301 | for ( int i = runStart; i < sectorFree.size(); ++i ) { 302 | if ( runLength != 0 ) { 303 | if ( sectorFree.get( i ) ) runLength++; 304 | else runLength = 0; 305 | } else if ( sectorFree.get( i ) ) { 306 | runStart = i; 307 | runLength = 1; 308 | } 309 | if ( runLength >= sectorsNeeded ) { 310 | break; 311 | } 312 | } 313 | } 314 | 315 | if ( runLength >= sectorsNeeded ) { 316 | /* we found a free space large enough */ 317 | debug( "SAVE", x, z, length, "reuse" ); 318 | sectorNumber = runStart; 319 | setOffset( x, z, ( sectorNumber << 8 ) | sectorsNeeded ); 320 | for ( int i = 0; i < sectorsNeeded; ++i ) { 321 | sectorFree.set( sectorNumber + i, false ); 322 | } 323 | write( sectorNumber, data, length ); 324 | } else { 325 | /* 326 | * no free space large enough found -- we need to grow the 327 | * file 328 | */ 329 | debug( "SAVE", x, z, length, "grow" ); 330 | file.seek( file.length() ); 331 | sectorNumber = sectorFree.size(); 332 | for ( int i = 0; i < sectorsNeeded; ++i ) { 333 | file.write( emptySector ); 334 | sectorFree.add( false ); 335 | } 336 | sizeDelta += SECTOR_BYTES * sectorsNeeded; 337 | 338 | write( sectorNumber, data, length ); 339 | setOffset( x, z, ( sectorNumber << 8 ) | sectorsNeeded ); 340 | } 341 | } 342 | setTimestamp( x, z, (int) ( System.currentTimeMillis() / 1000L ) ); 343 | } catch ( IOException e ) { 344 | e.printStackTrace(); 345 | } 346 | } 347 | 348 | /* write a chunk data to the region file at specified sector number */ 349 | private void write( int sectorNumber, byte[] data, int length ) throws IOException { 350 | debugln( " " + sectorNumber ); 351 | file.seek( sectorNumber * SECTOR_BYTES ); 352 | file.writeInt( length + 1 ); // chunk length 353 | file.writeByte( VERSION_DEFLATE ); // chunk version number 354 | file.write( data, 0, length ); // chunk data 355 | } 356 | 357 | /* is this an invalid chunk coordinate? */ 358 | private boolean outOfBounds( int x, int z ) { 359 | return x < 0 || x >= 32 || z < 0 || z >= 32; 360 | } 361 | 362 | private int getOffset( int x, int z ) { 363 | return offsets[x + z * 32]; 364 | } 365 | 366 | public boolean hasChunk( int x, int z ) { 367 | return getOffset( x, z ) != 0; 368 | } 369 | 370 | private void setOffset( int x, int z, int offset ) throws IOException { 371 | offsets[x + z * 32] = offset; 372 | file.seek( ( x + z * 32 ) * 4 ); 373 | file.writeInt( offset ); 374 | } 375 | 376 | private void setTimestamp( int x, int z, int value ) throws IOException { 377 | chunkTimestamps[x + z * 32] = value; 378 | file.seek( SECTOR_BYTES + ( x + z * 32 ) * 4 ); 379 | file.writeInt( value ); 380 | } 381 | 382 | public void close( ) throws IOException { 383 | file.close(); 384 | } 385 | 386 | public static String getFileName( int x, int z ) { 387 | return "/r." + x + "." + z + ".mca"; 388 | } 389 | } -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/render/Frustum.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.render; 2 | 3 | import de.labystudio.game.util.BoundingBox; 4 | import de.labystudio.game.world.World; 5 | import de.labystudio.game.world.chunk.Chunk; 6 | import de.labystudio.game.world.chunk.ChunkSection; 7 | import org.lwjgl.BufferUtils; 8 | 9 | import java.nio.FloatBuffer; 10 | 11 | import static org.lwjgl.opengl.GL11.*; 12 | 13 | /* 14 | * Author: Ben Humphrey (DigiBen) 15 | * E-mail: digiben@gametutorials.com 16 | */ 17 | public class Frustum { 18 | 19 | // We create an enum of the sides so we don't have to call each side 0 or 1. 20 | // This way it makes it more understandable and readable when dealing with frustum sides. 21 | public static final int RIGHT = 0; // The RIGHT side of the frustum 22 | public static final int LEFT = 1; // The LEFT side of the frustum 23 | public static final int BOTTOM = 2; // The BOTTOM side of the frustum 24 | public static final int TOP = 3; // The TOP side of the frustum 25 | public static final int BACK = 4; // The BACK side of the frustum 26 | public static final int FRONT = 5; // The FRONT side of the frustum 27 | 28 | // Like above, instead of saying a number for the ABC and D of the plane, we 29 | // want to be more descriptive. 30 | public static final int A = 0; // The X value of the plane's normal 31 | public static final int B = 1; // The Y value of the plane's normal 32 | public static final int C = 2; // The Z value of the plane's normal 33 | public static final int D = 3; // The distance the plane is from the origin 34 | 35 | // This holds the A B C and D values for each side of our frustum. 36 | float[][] m_Frustum = new float[6][4]; 37 | 38 | /** 39 | * FloatBuffer to get ModelView matrix. 40 | **/ 41 | FloatBuffer modl_b; 42 | 43 | /** 44 | * FloatBuffer to get Projection matrix. 45 | **/ 46 | FloatBuffer proj_b; 47 | 48 | 49 | /** 50 | * Frustum constructor. 51 | */ 52 | public Frustum() { 53 | this.modl_b = BufferUtils.createFloatBuffer(16); 54 | this.proj_b = BufferUtils.createFloatBuffer(16); 55 | } 56 | 57 | ///////////////////////////////// NORMALIZE PLANE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 58 | ///// 59 | ///// This normalizes a plane (A side) from a given frustum. 60 | ///// 61 | ///////////////////////////////// NORMALIZE PLANE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 62 | public void normalizePlane(float[][] frustum, int side) { 63 | // Here we calculate the magnitude of the normal to the plane (point A B C) 64 | // Remember that (A, B, C) is that same thing as the normal's (X, Y, Z). 65 | // To calculate magnitude you use the equation: magnitude = sqrt( x^2 + y^2 + z^2) 66 | float magnitude = (float) Math.sqrt(frustum[side][A] * frustum[side][A] + 67 | frustum[side][B] * frustum[side][B] + frustum[side][C] * frustum[side][C]); 68 | 69 | // Then we divide the plane's values by it's magnitude. 70 | // This makes it easier to work with. 71 | frustum[side][A] /= magnitude; 72 | frustum[side][B] /= magnitude; 73 | frustum[side][C] /= magnitude; 74 | frustum[side][D] /= magnitude; 75 | } 76 | 77 | 78 | ///////////////////////////////// CALCULATE FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 79 | ///// 80 | ///// This extracts our frustum from the projection and modelview matrix. 81 | ///// 82 | ///////////////////////////////// CALCULATE FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 83 | public void calculateFrustum() { 84 | float[] proj = new float[16]; // This will hold our projection matrix 85 | float[] modl = new float[16]; // This will hold our modelview matrix 86 | float[] clip = new float[16]; // This will hold the clipping planes 87 | 88 | 89 | // glGetFloat() is used to extract information about our OpenGL world. 90 | // Below, we pass in GL_PROJECTION_MATRIX to abstract our projection matrix. 91 | // It then stores the matrix into an array of [16]. 92 | this.proj_b.rewind(); 93 | glGetFloat(GL_PROJECTION_MATRIX, this.proj_b); 94 | this.proj_b.rewind(); 95 | this.proj_b.get(proj); 96 | 97 | // By passing in GL_MODELVIEW_MATRIX, we can abstract our model view matrix. 98 | // This also stores it in an array of [16]. 99 | this.modl_b.rewind(); 100 | glGetFloat(GL_MODELVIEW_MATRIX, this.modl_b); 101 | this.modl_b.rewind(); 102 | this.modl_b.get(modl); 103 | 104 | // Now that we have our modelview and projection matrix, if we combine these 2 matrices, 105 | // it will give us our clipping planes. To combine 2 matrices, we multiply them. 106 | 107 | clip[0] = modl[0] * proj[0] + modl[1] * proj[4] + modl[2] * proj[8] + modl[3] * proj[12]; 108 | clip[1] = modl[0] * proj[1] + modl[1] * proj[5] + modl[2] * proj[9] + modl[3] * proj[13]; 109 | clip[2] = modl[0] * proj[2] + modl[1] * proj[6] + modl[2] * proj[10] + modl[3] * proj[14]; 110 | clip[3] = modl[0] * proj[3] + modl[1] * proj[7] + modl[2] * proj[11] + modl[3] * proj[15]; 111 | 112 | clip[4] = modl[4] * proj[0] + modl[5] * proj[4] + modl[6] * proj[8] + modl[7] * proj[12]; 113 | clip[5] = modl[4] * proj[1] + modl[5] * proj[5] + modl[6] * proj[9] + modl[7] * proj[13]; 114 | clip[6] = modl[4] * proj[2] + modl[5] * proj[6] + modl[6] * proj[10] + modl[7] * proj[14]; 115 | clip[7] = modl[4] * proj[3] + modl[5] * proj[7] + modl[6] * proj[11] + modl[7] * proj[15]; 116 | 117 | clip[8] = modl[8] * proj[0] + modl[9] * proj[4] + modl[10] * proj[8] + modl[11] * proj[12]; 118 | clip[9] = modl[8] * proj[1] + modl[9] * proj[5] + modl[10] * proj[9] + modl[11] * proj[13]; 119 | clip[10] = modl[8] * proj[2] + modl[9] * proj[6] + modl[10] * proj[10] + modl[11] * proj[14]; 120 | clip[11] = modl[8] * proj[3] + modl[9] * proj[7] + modl[10] * proj[11] + modl[11] * proj[15]; 121 | 122 | clip[12] = modl[12] * proj[0] + modl[13] * proj[4] + modl[14] * proj[8] + modl[15] * proj[12]; 123 | clip[13] = modl[12] * proj[1] + modl[13] * proj[5] + modl[14] * proj[9] + modl[15] * proj[13]; 124 | clip[14] = modl[12] * proj[2] + modl[13] * proj[6] + modl[14] * proj[10] + modl[15] * proj[14]; 125 | clip[15] = modl[12] * proj[3] + modl[13] * proj[7] + modl[14] * proj[11] + modl[15] * proj[15]; 126 | 127 | // Now we actually want to get the sides of the frustum. To do this we take 128 | // the clipping planes we received above and extract the sides from them. 129 | 130 | // This will extract the RIGHT side of the frustum 131 | this.m_Frustum[RIGHT][A] = clip[3] - clip[0]; 132 | this.m_Frustum[RIGHT][B] = clip[7] - clip[4]; 133 | this.m_Frustum[RIGHT][C] = clip[11] - clip[8]; 134 | this.m_Frustum[RIGHT][D] = clip[15] - clip[12]; 135 | 136 | // Now that we have a normal (A,B,C) and a distance (D) to the plane, 137 | // we want to normalize that normal and distance. 138 | 139 | // Normalize the RIGHT side 140 | this.normalizePlane(this.m_Frustum, RIGHT); 141 | 142 | // This will extract the LEFT side of the frustum 143 | this.m_Frustum[LEFT][A] = clip[3] + clip[0]; 144 | this.m_Frustum[LEFT][B] = clip[7] + clip[4]; 145 | this.m_Frustum[LEFT][C] = clip[11] + clip[8]; 146 | this.m_Frustum[LEFT][D] = clip[15] + clip[12]; 147 | 148 | // Normalize the LEFT side 149 | this.normalizePlane(this.m_Frustum, LEFT); 150 | 151 | // This will extract the BOTTOM side of the frustum 152 | this.m_Frustum[BOTTOM][A] = clip[3] + clip[1]; 153 | this.m_Frustum[BOTTOM][B] = clip[7] + clip[5]; 154 | this.m_Frustum[BOTTOM][C] = clip[11] + clip[9]; 155 | this.m_Frustum[BOTTOM][D] = clip[15] + clip[13]; 156 | 157 | // Normalize the BOTTOM side 158 | this.normalizePlane(this.m_Frustum, BOTTOM); 159 | 160 | // This will extract the TOP side of the frustum 161 | this.m_Frustum[TOP][A] = clip[3] - clip[1]; 162 | this.m_Frustum[TOP][B] = clip[7] - clip[5]; 163 | this.m_Frustum[TOP][C] = clip[11] - clip[9]; 164 | this.m_Frustum[TOP][D] = clip[15] - clip[13]; 165 | 166 | // Normalize the TOP side 167 | this.normalizePlane(this.m_Frustum, TOP); 168 | 169 | // This will extract the BACK side of the frustum 170 | this.m_Frustum[BACK][A] = clip[3] - clip[2]; 171 | this.m_Frustum[BACK][B] = clip[7] - clip[6]; 172 | this.m_Frustum[BACK][C] = clip[11] - clip[10]; 173 | this.m_Frustum[BACK][D] = clip[15] - clip[14]; 174 | 175 | // Normalize the BACK side 176 | this.normalizePlane(this.m_Frustum, BACK); 177 | 178 | // This will extract the FRONT side of the frustum 179 | this.m_Frustum[FRONT][A] = clip[3] + clip[2]; 180 | this.m_Frustum[FRONT][B] = clip[7] + clip[6]; 181 | this.m_Frustum[FRONT][C] = clip[11] + clip[10]; 182 | this.m_Frustum[FRONT][D] = clip[15] + clip[14]; 183 | 184 | // Normalize the FRONT side 185 | this.normalizePlane(this.m_Frustum, FRONT); 186 | } 187 | 188 | // The code below will allow us to make checks within the frustum. For example, 189 | // if we want to see if a point, a sphere, or a cube lies inside of the frustum. 190 | // Because all of our planes point INWARDS (The normals are all pointing inside the frustum) 191 | // we then can assume that if a point is in FRONT of all of the planes, it's inside. 192 | 193 | ///////////////////////////////// POINT IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 194 | ///// 195 | ///// This determines if a point is inside of the frustum 196 | ///// 197 | ///////////////////////////////// POINT IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 198 | public boolean pointInFrustum(float x, float y, float z) { 199 | // Go through all the sides of the frustum 200 | for (int i = 0; i < 6; i++) { 201 | // Calculate the plane equation and check if the point is behind a side of the frustum 202 | if (this.m_Frustum[i][A] * x + this.m_Frustum[i][B] * y + this.m_Frustum[i][C] * z + this.m_Frustum[i][D] <= 0) { 203 | // The point was behind a side, so it ISN'T in the frustum 204 | return false; 205 | } 206 | } 207 | 208 | // The point was inside of the frustum (In front of ALL the sides of the frustum) 209 | return true; 210 | } 211 | 212 | 213 | ///////////////////////////////// SPHERE IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 214 | ///// 215 | ///// This determines if a sphere is inside of our frustum by it's center and radius. 216 | ///// 217 | ///////////////////////////////// SPHERE IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 218 | public boolean sphereInFrustum(float x, float y, float z, float radius) { 219 | // Go through all the sides of the frustum 220 | for (int i = 0; i < 6; i++) { 221 | // If the center of the sphere is farther away from the plane than the radius 222 | if (this.m_Frustum[i][A] * x + this.m_Frustum[i][B] * y + this.m_Frustum[i][C] * z + this.m_Frustum[i][D] <= -radius) { 223 | // The distance was greater than the radius so the sphere is outside of the frustum 224 | return false; 225 | } 226 | } 227 | 228 | // The sphere was inside of the frustum! 229 | return true; 230 | } 231 | 232 | ///////////////////////////////// CUBE IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 233 | ///// 234 | ///// This determines if a cube is in or around our frustum by it's center and 1/2 it's length 235 | ///// 236 | ///////////////////////////////// CUBE IN FRUSTUM \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* 237 | public boolean cubeInFrustum(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { 238 | // This test is a bit more work, but not too much more complicated. 239 | // Basically, what is going on is, that we are given the center of the cube, 240 | // and half the length. Think of it like a radius. Then we checking each point 241 | // in the cube and seeing if it is inside the frustum. If a point is found in front 242 | // of a side, then we skip to the next side. If we get to a plane that does NOT have 243 | // a point in front of it, then it will return false. 244 | 245 | // *Note* - This will sometimes say that a cube is inside the frustum when it isn't. 246 | // This happens when all the corners of the bounding box are not behind any one plane. 247 | // This is rare and shouldn't effect the overall rendering speed. 248 | 249 | for (int i = 0; i < 6; i++) { 250 | if (this.m_Frustum[i][A] * minX + this.m_Frustum[i][B] * minY + this.m_Frustum[i][C] * minZ + this.m_Frustum[i][D] > 0) 251 | continue; 252 | if (this.m_Frustum[i][A] * maxX + this.m_Frustum[i][B] * minY + this.m_Frustum[i][C] * minZ + this.m_Frustum[i][D] > 0) 253 | continue; 254 | if (this.m_Frustum[i][A] * minX + this.m_Frustum[i][B] * maxY + this.m_Frustum[i][C] * minZ + this.m_Frustum[i][D] > 0) 255 | continue; 256 | if (this.m_Frustum[i][A] * maxX + this.m_Frustum[i][B] * maxY + this.m_Frustum[i][C] * minZ + this.m_Frustum[i][D] > 0) 257 | continue; 258 | if (this.m_Frustum[i][A] * minX + this.m_Frustum[i][B] * minY + this.m_Frustum[i][C] * maxZ + this.m_Frustum[i][D] > 0) 259 | continue; 260 | if (this.m_Frustum[i][A] * maxX + this.m_Frustum[i][B] * minY + this.m_Frustum[i][C] * maxZ + this.m_Frustum[i][D] > 0) 261 | continue; 262 | if (this.m_Frustum[i][A] * minX + this.m_Frustum[i][B] * maxY + this.m_Frustum[i][C] * maxZ + this.m_Frustum[i][D] > 0) 263 | continue; 264 | if (this.m_Frustum[i][A] * maxX + this.m_Frustum[i][B] * maxY + this.m_Frustum[i][C] * maxZ + this.m_Frustum[i][D] > 0) 265 | continue; 266 | 267 | // If we get here, it isn't in the frustum 268 | return false; 269 | } 270 | 271 | return true; 272 | } 273 | 274 | public boolean cubeInFrustum(BoundingBox aabb) { 275 | return this.cubeInFrustum( 276 | (float) aabb.minX, (float) aabb.minY, (float) aabb.minZ, 277 | (float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ 278 | ); 279 | } 280 | 281 | public boolean cubeInFrustum(ChunkSection chunkSection) { 282 | return this.cubeInFrustum( 283 | chunkSection.x * ChunkSection.SIZE, 284 | chunkSection.y * ChunkSection.SIZE, 285 | chunkSection.z * ChunkSection.SIZE, 286 | chunkSection.x * ChunkSection.SIZE + ChunkSection.SIZE, 287 | chunkSection.y * ChunkSection.SIZE + ChunkSection.SIZE, 288 | chunkSection.z * ChunkSection.SIZE + ChunkSection.SIZE 289 | ); 290 | } 291 | 292 | public boolean cubeInFrustum(Chunk chunk) { 293 | return this.cubeInFrustum( 294 | chunk.getX() * ChunkSection.SIZE, 295 | 0, 296 | chunk.getZ() * ChunkSection.SIZE, 297 | chunk.getX() * ChunkSection.SIZE + ChunkSection.SIZE, 298 | World.TOTAL_HEIGHT, 299 | chunk.getZ() * ChunkSection.SIZE + ChunkSection.SIZE 300 | ); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/de/labystudio/game/player/Player.java: -------------------------------------------------------------------------------- 1 | package de.labystudio.game.player; 2 | 3 | import de.labystudio.game.util.BoundingBox; 4 | import de.labystudio.game.world.World; 5 | import de.labystudio.game.world.block.Block; 6 | import org.lwjgl.input.Keyboard; 7 | 8 | import java.util.List; 9 | 10 | public class Player { 11 | 12 | private final World world; 13 | 14 | public double prevX; 15 | public double prevY; 16 | public double prevZ; 17 | 18 | public double x; 19 | public double y; 20 | public double z; 21 | 22 | public double motionX; 23 | public double motionY; 24 | public double motionZ; 25 | 26 | public float yaw; 27 | public float pitch; 28 | 29 | public BoundingBox boundingBox; 30 | public boolean onGround = false; 31 | public boolean collision = false; 32 | 33 | public float jumpMovementFactor = 0.02F; 34 | protected float speedInAir = 0.02F; 35 | private final float flySpeed = 0.05F; 36 | private final float stepHeight = 0.5F; 37 | 38 | float moveForward; 39 | float moveStrafing; 40 | 41 | private int jumpTicks; 42 | private int flyToggleTimer; 43 | private int sprintToggleTimer; 44 | 45 | public boolean jumping; 46 | public boolean sprinting; 47 | public boolean sneaking; 48 | public boolean flying; 49 | 50 | public float prevFovModifier = 0; 51 | public float fovModifier = 0; 52 | public long timeFovChanged = 0; 53 | 54 | public Player(World world) { 55 | this.world = world; 56 | this.resetPos(); 57 | } 58 | 59 | private void resetPos() { 60 | this.setPos(0, 76, 0); 61 | } 62 | 63 | private void setPos(float x, float y, float z) { 64 | this.x = x; 65 | this.y = y; 66 | this.z = z; 67 | float w = 0.3F; 68 | float h = 0.9F; 69 | this.boundingBox = new BoundingBox(x - w, y - h, z - w, x + w, y + h, z + w); 70 | } 71 | 72 | public void turn(float xo, float yo) { 73 | this.yaw = ((float) (this.yaw + xo * 0.15D)); 74 | this.pitch = ((float) (this.pitch - yo * 0.15D)); 75 | if (this.pitch < -90.0F) { 76 | this.pitch = -90.0F; 77 | } 78 | if (this.pitch > 90.0F) { 79 | this.pitch = 90.0F; 80 | } 81 | } 82 | 83 | public void onTick() { 84 | float prevMoveForward = this.moveForward; 85 | boolean prevJumping = this.jumping; 86 | 87 | this.updateKeyboardInput(); 88 | 89 | // Toggle jumping 90 | if (!prevJumping && this.jumping) { 91 | if (this.flyToggleTimer == 0) { 92 | this.flyToggleTimer = 7; 93 | } else { 94 | this.flying = !this.flying; 95 | this.flyToggleTimer = 0; 96 | 97 | this.updateFOVModifier(); 98 | } 99 | } 100 | 101 | // Toggle sprint 102 | if (prevMoveForward == 0 && this.moveForward > 0) { 103 | if (this.sprintToggleTimer == 0) { 104 | this.sprintToggleTimer = 7; 105 | } else { 106 | this.sprinting = true; 107 | this.sprintToggleTimer = 0; 108 | 109 | this.updateFOVModifier(); 110 | } 111 | } 112 | 113 | if (this.jumpTicks > 0) { 114 | --this.jumpTicks; 115 | } 116 | 117 | if (this.flyToggleTimer > 0) { 118 | --this.flyToggleTimer; 119 | } 120 | 121 | if (this.sprintToggleTimer > 0) { 122 | --this.sprintToggleTimer; 123 | } 124 | 125 | this.prevX = this.x; 126 | this.prevY = this.y; 127 | this.prevZ = this.z; 128 | 129 | // Stop if too slow 130 | if (Math.abs(this.motionX) < 0.003D) { 131 | this.motionX = 0.0D; 132 | } 133 | if (Math.abs(this.motionY) < 0.003D) { 134 | this.motionY = 0.0D; 135 | } 136 | if (Math.abs(this.motionZ) < 0.003D) { 137 | this.motionZ = 0.0D; 138 | } 139 | 140 | // Jump 141 | if (this.jumping) { 142 | if (this.isInWater()) { 143 | this.motionY += 0.03999999910593033D; 144 | } else if (this.onGround && this.jumpTicks == 0) { 145 | this.jump(); 146 | this.jumpTicks = 10; 147 | } 148 | } else { 149 | this.jumpTicks = 0; 150 | } 151 | 152 | this.moveStrafing *= 0.98F; 153 | this.moveForward *= 0.98F; 154 | 155 | if (this.flying) { 156 | this.travelFlying(this.moveForward, 0, this.moveStrafing); 157 | } else { 158 | if (this.isInWater()) { 159 | // Is inside of water 160 | this.travelInWater(this.moveForward, 0, this.moveStrafing); 161 | } else { 162 | // Is on land 163 | this.travel(this.moveForward, 0, this.moveStrafing); 164 | } 165 | } 166 | 167 | this.jumpMovementFactor = this.speedInAir; 168 | 169 | if (this.sprinting) { 170 | this.jumpMovementFactor = (float) ((double) this.jumpMovementFactor + (double) this.speedInAir * 0.3D); 171 | 172 | if (this.moveForward <= 0 || this.collision || this.sneaking) { 173 | this.sprinting = false; 174 | 175 | this.updateFOVModifier(); 176 | } 177 | } 178 | } 179 | 180 | public boolean isInWater() { 181 | return this.world.getBlockAt(this.getBlockPosX(), this.getBlockPosY(), this.getBlockPosZ()) == Block.WATER.getId(); 182 | } 183 | 184 | public boolean isHeadInWater() { 185 | return this.world.getBlockAt(this.getBlockPosX(), (int) (this.y + this.getEyeHeight() + 0.12), this.getBlockPosZ()) == Block.WATER.getId(); 186 | } 187 | 188 | protected void jump() { 189 | this.motionY = 0.42D; 190 | 191 | if (this.sprinting) { 192 | float radiansYaw = (float) Math.toRadians(this.yaw); 193 | this.motionX -= Math.sin(radiansYaw) * 0.2F; 194 | this.motionZ += Math.cos(radiansYaw) * 0.2F; 195 | } 196 | } 197 | 198 | private void travelFlying(float forward, float vertical, float strafe) { 199 | // Fly move up and down 200 | if (this.sneaking) { 201 | this.moveStrafing = strafe / 0.3F; 202 | this.moveForward = forward / 0.3F; 203 | this.motionY -= this.flySpeed * 3.0F; 204 | } 205 | 206 | if (this.jumping) { 207 | this.motionY += this.flySpeed * 3.0F; 208 | } 209 | 210 | double prevMotionY = this.motionY; 211 | float prevJumpMovementFactor = this.jumpMovementFactor; 212 | this.jumpMovementFactor = this.flySpeed * (this.sprinting ? 2 : 1); 213 | 214 | this.travel(forward, vertical, strafe); 215 | 216 | this.motionY = prevMotionY * 0.6D; 217 | this.jumpMovementFactor = prevJumpMovementFactor; 218 | 219 | if (this.onGround) { 220 | this.flying = false; 221 | } 222 | } 223 | 224 | private void travelInWater(float forward, float vertical, float strafe) { 225 | float slipperiness = 0.8F; 226 | float friction = 0.02F; 227 | 228 | this.moveRelative(forward, vertical, strafe, friction); 229 | this.collision = this.moveCollide(-this.motionX, this.motionY, -this.motionZ); 230 | 231 | this.motionX *= slipperiness; 232 | this.motionY *= 0.800000011920929D; 233 | this.motionZ *= slipperiness; 234 | this.motionY -= 0.02D; 235 | } 236 | 237 | public void travel(float forward, float vertical, float strafe) { 238 | float prevSlipperiness = this.getBlockSlipperiness() * 0.91F; 239 | 240 | float value = 0.16277136F / (prevSlipperiness * prevSlipperiness * prevSlipperiness); 241 | float friction; 242 | 243 | if (this.onGround) { 244 | friction = this.getAIMoveSpeed() * value; 245 | } else { 246 | friction = this.jumpMovementFactor; 247 | } 248 | 249 | this.moveRelative(forward, vertical, strafe, friction); 250 | 251 | // Get new speed 252 | float slipperiness = this.getBlockSlipperiness() * 0.91F; 253 | 254 | // Move 255 | this.collision = this.moveCollide(-this.motionX, this.motionY, -this.motionZ); 256 | 257 | // Gravity 258 | if (!this.flying) { 259 | this.motionY -= 0.08D; 260 | } 261 | 262 | // Decrease motion 263 | this.motionX *= slipperiness; 264 | this.motionY *= 0.9800000190734863D; 265 | this.motionZ *= slipperiness; 266 | } 267 | 268 | private float getBlockSlipperiness() { 269 | return this.onGround ? 0.6F : 1.0F; 270 | } 271 | 272 | private float getAIMoveSpeed() { 273 | return this.sprinting ? 0.13000001F : 0.10000000149011612F; 274 | } 275 | 276 | public void moveRelative(double forward, double up, double strafe, double friction) { 277 | double distance = strafe * strafe + up * up + forward * forward; 278 | 279 | if (distance >= 1.0E-4F) { 280 | distance = Math.sqrt(distance); 281 | 282 | if (distance < 1.0F) { 283 | distance = 1.0F; 284 | } 285 | 286 | distance = friction / distance; 287 | strafe = strafe * distance; 288 | up = up * distance; 289 | forward = forward * distance; 290 | 291 | double yawRadians = Math.toRadians(this.yaw); 292 | double sin = Math.sin(yawRadians); 293 | double cos = Math.cos(yawRadians); 294 | 295 | this.motionX += strafe * cos - forward * sin; 296 | this.motionY += up; 297 | this.motionZ += forward * cos + strafe * sin; 298 | } 299 | } 300 | 301 | public void updateKeyboardInput() { 302 | float moveForward = 0.0F; 303 | float moveStrafe = 0.0F; 304 | 305 | boolean jumping = false; 306 | boolean sneaking = false; 307 | 308 | if (Keyboard.isKeyDown(19)) { // R 309 | this.resetPos(); 310 | } 311 | if ((Keyboard.isKeyDown(200)) || (Keyboard.isKeyDown(17))) { // W 312 | moveForward++; 313 | } 314 | if ((Keyboard.isKeyDown(208)) || (Keyboard.isKeyDown(31))) { // S 315 | moveForward--; 316 | } 317 | if ((Keyboard.isKeyDown(203)) || (Keyboard.isKeyDown(30))) { // A 318 | moveStrafe++; 319 | } 320 | if ((Keyboard.isKeyDown(205)) || (Keyboard.isKeyDown(32))) { // D 321 | moveStrafe--; 322 | } 323 | if ((Keyboard.isKeyDown(57)) || (Keyboard.isKeyDown(219))) { // Space 324 | jumping = true; 325 | } 326 | if (Keyboard.isKeyDown(42)) { // Shift 327 | if (this.moveForward > 0 && !this.sneaking && !this.sprinting && this.motionX != 0 && this.motionZ != 0) { 328 | this.sprinting = true; 329 | 330 | this.updateFOVModifier(); 331 | } 332 | } 333 | if (Keyboard.isKeyDown(16)) { // Q 334 | sneaking = true; 335 | } 336 | 337 | if (sneaking) { 338 | moveStrafe = (float) ((double) moveStrafe * 0.3D); 339 | moveForward = (float) ((double) moveForward * 0.3D); 340 | } 341 | 342 | this.moveForward = moveForward; 343 | this.moveStrafing = moveStrafe; 344 | 345 | this.jumping = jumping; 346 | this.sneaking = sneaking; 347 | } 348 | 349 | public boolean moveCollide(double targetX, double targetY, double targetZ) { 350 | 351 | // Target position 352 | double originalTargetX = targetX; 353 | double originalTargetY = targetY; 354 | double originalTargetZ = targetZ; 355 | 356 | if (this.onGround && this.sneaking) { 357 | for (; targetX != 0.0D && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, 0.0D)).isEmpty(); originalTargetX = targetX) { 358 | if (targetX < 0.05D && targetX >= -0.05D) { 359 | targetX = 0.0D; 360 | } else if (targetX > 0.0D) { 361 | targetX -= 0.05D; 362 | } else { 363 | targetX += 0.05D; 364 | } 365 | } 366 | 367 | for (; targetZ != 0.0D && this.world.getCollisionBoxes(this.boundingBox.offset(0.0D, -this.stepHeight, targetZ)).isEmpty(); originalTargetZ = targetZ) { 368 | if (targetZ < 0.05D && targetZ >= -0.05D) { 369 | targetZ = 0.0D; 370 | } else if (targetZ > 0.0D) { 371 | targetZ -= 0.05D; 372 | } else { 373 | targetZ += 0.05D; 374 | } 375 | } 376 | 377 | for (; targetX != 0.0D && targetZ != 0.0D && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, targetZ)).isEmpty(); originalTargetZ = targetZ) { 378 | if (targetX < 0.05D && targetX >= -0.05D) { 379 | targetX = 0.0D; 380 | } else if (targetX > 0.0D) { 381 | targetX -= 0.05D; 382 | } else { 383 | targetX += 0.05D; 384 | } 385 | 386 | originalTargetX = targetX; 387 | 388 | if (targetZ < 0.05D && targetZ >= -0.05D) { 389 | targetZ = 0.0D; 390 | } else if (targetZ > 0.0D) { 391 | targetZ -= 0.05D; 392 | } else { 393 | targetZ += 0.05D; 394 | } 395 | } 396 | } 397 | 398 | // Get level tiles as bounding boxes 399 | List boundingBoxList = this.world.getCollisionBoxes(this.boundingBox.expand(targetX, targetY, targetZ)); 400 | 401 | // Move bounding box 402 | for (BoundingBox aABB : boundingBoxList) { 403 | targetY = aABB.clipYCollide(this.boundingBox, targetY); 404 | } 405 | this.boundingBox.move(0.0F, targetY, 0.0F); 406 | 407 | for (BoundingBox aABB : boundingBoxList) { 408 | targetX = aABB.clipXCollide(this.boundingBox, targetX); 409 | } 410 | this.boundingBox.move(targetX, 0.0F, 0.0F); 411 | 412 | for (BoundingBox aABB : boundingBoxList) { 413 | targetZ = aABB.clipZCollide(this.boundingBox, targetZ); 414 | } 415 | this.boundingBox.move(0.0F, 0.0F, targetZ); 416 | 417 | this.onGround = originalTargetY != targetY && originalTargetY < 0.0F; 418 | 419 | // Stop motion on collision 420 | if (originalTargetX != targetX) { 421 | this.motionX = 0.0F; 422 | } 423 | if (originalTargetY != targetY) { 424 | this.motionY = 0.0F; 425 | } 426 | if (originalTargetZ != targetZ) { 427 | this.motionZ = 0.0F; 428 | } 429 | 430 | // Update position 431 | this.x = ((this.boundingBox.minX + this.boundingBox.maxX) / 2.0F); 432 | this.y = this.boundingBox.minY; 433 | this.z = ((this.boundingBox.minZ + this.boundingBox.maxZ) / 2.0F); 434 | 435 | // Horizontal collision? 436 | return originalTargetX != targetX || originalTargetZ != targetZ; 437 | } 438 | 439 | public float getEyeHeight() { 440 | return this.sneaking ? 1.50F : 1.62F; 441 | } 442 | 443 | public void updateFOVModifier() { 444 | float value = 1.0F; 445 | 446 | if (this.sprinting) { 447 | value += 1; 448 | } 449 | 450 | if (this.flying) { 451 | value *= 1.1F; 452 | } 453 | 454 | this.setFOVModifier((value - 1.0F) * 10F); 455 | } 456 | 457 | public void setFOVModifier(float fov) { 458 | this.prevFovModifier = this.fovModifier; 459 | this.fovModifier = fov; 460 | this.timeFovChanged = System.currentTimeMillis(); 461 | } 462 | 463 | public float getFOVModifier() { 464 | long timePassed = System.currentTimeMillis() - this.timeFovChanged; 465 | float distance = this.prevFovModifier - this.fovModifier; 466 | long duration = 100; 467 | float progress = distance / (float) duration * timePassed; 468 | return timePassed > duration ? this.fovModifier : this.prevFovModifier - progress; 469 | } 470 | 471 | public int getBlockPosX() { 472 | return (int) this.x - (this.x < 0 ? 1 : 0); 473 | } 474 | 475 | public int getBlockPosY() { 476 | return (int) this.y - (this.y < 0 ? 1 : 0); 477 | } 478 | 479 | public int getBlockPosZ() { 480 | return (int) this.z - (this.z < 0 ? 1 : 0); 481 | } 482 | } 483 | --------------------------------------------------------------------------------