├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------