feat: Phase 2 Bausystem (Raycast, platzieren/abreißen, Block-Auswahl, Fadenkreuz) + LWJGL arm64-Fix + Gitea CI
All checks were successful
build / build (push) Successful in 1m25s
All checks were successful
build / build (push) Successful in 1m25s
- LWJGL natives fuer macOS aarch64 (arm64) ergaenzt - GL.createCapabilities() nach Kontext-Bindung (LWJGL 3 Pflicht) - VoxelWorld.raycast (DDA), public set() - PlayerController.overlapsCell (verhindert Platzieren in Spieler) - Game: Linksklick abbauen, Rechtsklick platzieren, 1-6 Auswahl, Mesh-Rebuild - Crosshair-Overlay (NDC Screen-Space) - .gitea/workflows/build.yml (build-on-push, ubuntu-latest)
This commit is contained in:
parent
55d2fad396
commit
a4a5cbbf2a
21
.gitea/workflows/build.yml
Normal file
21
.gitea/workflows/build.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, dev]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
- name: Make gradlew executable
|
||||||
|
run: chmod +x gradlew
|
||||||
|
- name: Build (compile + jar)
|
||||||
|
run: ./gradlew build --no-daemon
|
||||||
@ -19,10 +19,13 @@ Hintergrund weiter — auch wenn es geschlossen ist.
|
|||||||
./gradlew build # kompiliert + erstellt Jar
|
./gradlew build # kompiliert + erstellt Jar
|
||||||
```
|
```
|
||||||
|
|
||||||
## Steuerung (Phase 1)
|
## Steuerung (Phase 1 + 2)
|
||||||
- **W/A/S/D** — Laufen
|
- **W/A/S/D** — Laufen
|
||||||
- **Maus** — Umsehen (Pointer ist gefangen)
|
- **Maus** — Umsehen (Pointer ist gefangen)
|
||||||
- **Leertaste** — Springen
|
- **Leertaste** — Springen
|
||||||
|
- **Linksklick** — Block abbauen (vor der Kamera)
|
||||||
|
- **Rechtsklick** — Block platzieren (leere Zelle davor)
|
||||||
|
- **1–6** — Block-Auswahl: 1=Gras, 2=Erde, 3=Stein, 4=Holz, 5=Blaetter, 6=Wasser
|
||||||
- **Esc** — Fenster schließen (beendet)
|
- **Esc** — Fenster schließen (beendet)
|
||||||
|
|
||||||
## Projektstruktur
|
## Projektstruktur
|
||||||
|
|||||||
@ -22,10 +22,14 @@ dependencies {
|
|||||||
implementation 'org.lwjgl:lwjgl-glfw:3.3.3'
|
implementation 'org.lwjgl:lwjgl-glfw:3.3.3'
|
||||||
implementation 'org.lwjgl:lwjgl-opengl:3.3.3'
|
implementation 'org.lwjgl:lwjgl-opengl:3.3.3'
|
||||||
|
|
||||||
// Native Libs für macOS (universal: arm64 + x86_64)
|
// Native Libs für macOS — BEIDE Architekturen (x64 + arm64/Apple Silicon).
|
||||||
|
// LWJGL klassifiziert 'natives-macos' als x64; auf aarch64 JVM braucht es zwingend natives-macos-arm64.
|
||||||
runtimeOnly 'org.lwjgl:lwjgl:3.3.3:natives-macos'
|
runtimeOnly 'org.lwjgl:lwjgl:3.3.3:natives-macos'
|
||||||
|
runtimeOnly 'org.lwjgl:lwjgl:3.3.3:natives-macos-arm64'
|
||||||
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.3:natives-macos'
|
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.3:natives-macos'
|
||||||
|
runtimeOnly 'org.lwjgl:lwjgl-glfw:3.3.3:natives-macos-arm64'
|
||||||
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.3:natives-macos'
|
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.3:natives-macos'
|
||||||
|
runtimeOnly 'org.lwjgl:lwjgl-opengl:3.3.3:natives-macos-arm64'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@ -2,17 +2,23 @@ package de.jfritzsche.homegarden.core;
|
|||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
import static org.lwjgl.opengl.GL.createCapabilities;
|
||||||
|
|
||||||
import de.jfritzsche.homegarden.gfx.Camera;
|
import de.jfritzsche.homegarden.gfx.Camera;
|
||||||
|
import de.jfritzsche.homegarden.gfx.Crosshair;
|
||||||
import de.jfritzsche.homegarden.gfx.Mesh;
|
import de.jfritzsche.homegarden.gfx.Mesh;
|
||||||
import de.jfritzsche.homegarden.gfx.Shader;
|
import de.jfritzsche.homegarden.gfx.Shader;
|
||||||
import de.jfritzsche.homegarden.math.Mat4;
|
import de.jfritzsche.homegarden.math.Mat4;
|
||||||
|
import de.jfritzsche.homegarden.math.Vec3;
|
||||||
import de.jfritzsche.homegarden.player.PlayerController;
|
import de.jfritzsche.homegarden.player.PlayerController;
|
||||||
|
import de.jfritzsche.homegarden.world.Block;
|
||||||
import de.jfritzsche.homegarden.world.VoxelWorld;
|
import de.jfritzsche.homegarden.world.VoxelWorld;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielschleife + Rendering. Phase 1: statische Voxelwelt + FP-Laufen.
|
* Spielschleife + Rendering.
|
||||||
* Spawn: Mitte der Welt, Fuesse auf der Grasoberflaeche.
|
* Phase 1: statische Voxelwelt + FP-Laufen.
|
||||||
|
* Phase 2: Bausystem — Raycast, Linksklick abbauen, Rechtsklick platzieren,
|
||||||
|
* Block-Auswahl 1..6, Mesh-Rebuild, Fadenkreuz.
|
||||||
*/
|
*/
|
||||||
public final class Game {
|
public final class Game {
|
||||||
private final Window window = new Window();
|
private final Window window = new Window();
|
||||||
@ -23,17 +29,23 @@ public final class Game {
|
|||||||
|
|
||||||
private Shader shader;
|
private Shader shader;
|
||||||
private Mesh worldMesh;
|
private Mesh worldMesh;
|
||||||
|
private Crosshair crosshair;
|
||||||
|
|
||||||
|
private int selectedBlock = Block.STONE;
|
||||||
|
private final int[] placeable = {Block.GRASS, Block.DIRT, Block.STONE, Block.WOOD, Block.LEAVES, Block.WATER};
|
||||||
|
|
||||||
private static final double SENS = 0.0022;
|
private static final double SENS = 0.0022;
|
||||||
private static final float FOV = (float) Math.toRadians(70f);
|
private static final float FOV = (float) Math.toRadians(70f);
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
window.create(1280, 720, "HomeGarden");
|
window.create(1280, 720, "HomeGarden");
|
||||||
|
createCapabilities(); // LWJGL 3: laedt native GL-Funktionen fuer den Kontext
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glDisable(GL_CULL_FACE); // einfache Wuerfelflaechen brauchen kein Culling
|
glDisable(GL_CULL_FACE); // einfache Wuerfelflaechen brauchen kein Culling
|
||||||
|
|
||||||
shader = new Shader(VS, FS);
|
shader = new Shader(VS, FS);
|
||||||
worldMesh = world.buildMesh();
|
worldMesh = world.buildMesh();
|
||||||
|
crosshair = new Crosshair();
|
||||||
|
|
||||||
player.update(0f, camera, new boolean[5], world); // Kamera an Spawn syncen
|
player.update(0f, camera, new boolean[5], world); // Kamera an Spawn syncen
|
||||||
|
|
||||||
@ -45,6 +57,7 @@ public final class Game {
|
|||||||
|
|
||||||
applyMouseLook();
|
applyMouseLook();
|
||||||
player.update(dt, camera, window.keysForMovement(), world);
|
player.update(dt, camera, window.keysForMovement(), world);
|
||||||
|
handleBuild();
|
||||||
render();
|
render();
|
||||||
|
|
||||||
window.pollEvents();
|
window.pollEvents();
|
||||||
@ -63,6 +76,53 @@ public final class Game {
|
|||||||
if (camera.pitch < -lim) camera.pitch = -lim;
|
if (camera.pitch < -lim) camera.pitch = -lim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleBuild() {
|
||||||
|
int digit = window.consumeDigit();
|
||||||
|
if (digit >= 1 && digit <= placeable.length) {
|
||||||
|
selectedBlock = placeable[digit - 1];
|
||||||
|
System.out.println("[HomeGarden] Block ausgewaehlt: " + blockName(selectedBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] click = window.consumeClicks();
|
||||||
|
if (!click[0] && !click[1]) return;
|
||||||
|
|
||||||
|
int[] hit = new int[3], prev = new int[3];
|
||||||
|
Vec3 o = new Vec3(camera.position.x, camera.position.y, camera.position.z);
|
||||||
|
Vec3 d = camera.forward();
|
||||||
|
if (!world.raycast(o, d, 6f, hit, prev)) return; // ins Leere geschaut
|
||||||
|
|
||||||
|
if (click[0]) { // Linksklick: abbauen
|
||||||
|
world.set(hit[0], hit[1], hit[2], Block.AIR);
|
||||||
|
rebuildMesh();
|
||||||
|
} else { // Rechtsklick: platzieren (in der leeren Zelle vor dem Treffer)
|
||||||
|
int bx = prev[0], by = prev[1], bz = prev[2];
|
||||||
|
if (!world.isSolid(bx, by, bz)
|
||||||
|
&& !player.overlapsCell(bx, by, bz)
|
||||||
|
&& bx >= 0 && by >= 0 && bz >= 0
|
||||||
|
&& bx < world.sx && by < world.sy && bz < world.sz) {
|
||||||
|
world.set(bx, by, bz, selectedBlock);
|
||||||
|
rebuildMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildMesh() {
|
||||||
|
worldMesh.dispose();
|
||||||
|
worldMesh = world.buildMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String blockName(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case Block.GRASS: return "Gras";
|
||||||
|
case Block.DIRT: return "Erde";
|
||||||
|
case Block.STONE: return "Stein";
|
||||||
|
case Block.WOOD: return "Holz";
|
||||||
|
case Block.LEAVES: return "Blaetter";
|
||||||
|
case Block.WATER: return "Wasser";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void render() {
|
private void render() {
|
||||||
glClearColor(0.53f, 0.81f, 0.98f, 1f); // Himmelblau
|
glClearColor(0.53f, 0.81f, 0.98f, 1f); // Himmelblau
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
@ -73,10 +133,16 @@ public final class Game {
|
|||||||
shader.setMat4("u_proj", proj.toBuffer());
|
shader.setMat4("u_proj", proj.toBuffer());
|
||||||
shader.setMat4("u_view", view.toBuffer());
|
shader.setMat4("u_view", view.toBuffer());
|
||||||
worldMesh.draw();
|
worldMesh.draw();
|
||||||
|
|
||||||
|
// Fadenkreuz: ohne Tiefentest, immer oben
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
crosshair.draw();
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
worldMesh.dispose();
|
worldMesh.dispose();
|
||||||
|
crosshair.dispose();
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import org.lwjgl.glfw.GLFWErrorCallback;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* GLFW-Fenster + Input. Pointer ist gefangen (FP-Look).
|
* GLFW-Fenster + Input. Pointer ist gefangen (FP-Look).
|
||||||
* Tastenstatus und Maus-Delta werden fuer den Game-Loop gepuffert.
|
* Tastenstatus, Maus-Delta, Klick-Events und ausgewaehlte Ziffer werden
|
||||||
|
* fuer den Game-Loop gepuffert.
|
||||||
*/
|
*/
|
||||||
public final class Window {
|
public final class Window {
|
||||||
private long handle;
|
private long handle;
|
||||||
@ -17,6 +18,9 @@ public final class Window {
|
|||||||
private boolean firstMouse = true;
|
private boolean firstMouse = true;
|
||||||
private int width = 1280, height = 720;
|
private int width = 1280, height = 720;
|
||||||
|
|
||||||
|
private final boolean[] clicked = new boolean[2]; // [0]=links, [1]=rechts
|
||||||
|
private int pendingDigit = -1;
|
||||||
|
|
||||||
public void create(int w, int h, String title) {
|
public void create(int w, int h, String title) {
|
||||||
GLFWErrorCallback.createPrint(System.err).set();
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
if (!glfwInit()) throw new IllegalStateException("glfwInit failed");
|
if (!glfwInit()) throw new IllegalStateException("glfwInit failed");
|
||||||
@ -42,6 +46,9 @@ public final class Window {
|
|||||||
if (key >= 0 && key < keys.length) {
|
if (key >= 0 && key < keys.length) {
|
||||||
keys[key] = (action == GLFW_PRESS || action == GLFW_REPEAT);
|
keys[key] = (action == GLFW_PRESS || action == GLFW_REPEAT);
|
||||||
}
|
}
|
||||||
|
if (key >= GLFW_KEY_1 && key <= GLFW_KEY_6 && action == GLFW_PRESS) {
|
||||||
|
pendingDigit = key - GLFW_KEY_1 + 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
glfwSetCursorPosCallback(handle, (win, x, y) -> {
|
glfwSetCursorPosCallback(handle, (win, x, y) -> {
|
||||||
@ -51,6 +58,12 @@ public final class Window {
|
|||||||
lastX = x; lastY = y;
|
lastX = x; lastY = y;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
glfwSetMouseButtonCallback(handle, (win, button, action, mods) -> {
|
||||||
|
if (action != GLFW_PRESS) return;
|
||||||
|
if (button == GLFW_MOUSE_BUTTON_LEFT) clicked[0] = true;
|
||||||
|
else if (button == GLFW_MOUSE_BUTTON_RIGHT) clicked[1] = true;
|
||||||
|
});
|
||||||
|
|
||||||
glfwSetFramebufferSizeCallback(handle, (win, ww, hh) -> {
|
glfwSetFramebufferSizeCallback(handle, (win, ww, hh) -> {
|
||||||
glViewport(0, 0, ww, hh);
|
glViewport(0, 0, ww, hh);
|
||||||
width = ww; height = hh;
|
width = ww; height = hh;
|
||||||
@ -75,4 +88,16 @@ public final class Window {
|
|||||||
out[0] = mouseDX; out[1] = mouseDY;
|
out[0] = mouseDX; out[1] = mouseDY;
|
||||||
mouseDX = 0; mouseDY = 0;
|
mouseDX = 0; mouseDY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Liefert Links/Rechts-Klick zurueck (edge-triggered, einmalig). */
|
||||||
|
public boolean[] consumeClicks() {
|
||||||
|
boolean[] r = new boolean[] {clicked[0], clicked[1]};
|
||||||
|
clicked[0] = false; clicked[1] = false;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Liefert zuletzt gedrueckte Ziffer 1..6 zurueck (oder -1). */
|
||||||
|
public int consumeDigit() {
|
||||||
|
int d = pendingDigit; pendingDigit = -1; return d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/main/java/de/jfritzsche/homegarden/gfx/Crosshair.java
Normal file
76
src/main/java/de/jfritzsche/homegarden/gfx/Crosshair.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package de.jfritzsche.homegarden.gfx;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
import static org.lwjgl.opengl.GL15.*;
|
||||||
|
import static org.lwjgl.opengl.GL20.*;
|
||||||
|
import static org.lwjgl.opengl.GL30.*;
|
||||||
|
|
||||||
|
/** Fadenkreuz als Screen-Space-Overlay (NDC, kein Tiefentest). */
|
||||||
|
public final class Crosshair {
|
||||||
|
private final int vao, vbo, program;
|
||||||
|
|
||||||
|
private static final String VS = """
|
||||||
|
#version 330 core
|
||||||
|
layout(location = 0) in vec2 aPos;
|
||||||
|
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
|
||||||
|
""";
|
||||||
|
|
||||||
|
private static final String FS = """
|
||||||
|
#version 330 core
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() { fragColor = vec4(0.95f, 0.95f, 0.95f, 1.0); }
|
||||||
|
""";
|
||||||
|
|
||||||
|
public Crosshair() {
|
||||||
|
float t = 0.004f, L = 0.012f; // dicke / laenge
|
||||||
|
float[] verts = {
|
||||||
|
-L, -t, L, -t, L, t,
|
||||||
|
-L, -t, L, t, -L, t,
|
||||||
|
-t, -L, t, -L, t, L,
|
||||||
|
-t, -L, t, L, -t, L,
|
||||||
|
};
|
||||||
|
program = glCreateProgram();
|
||||||
|
int vs = compile(GL_VERTEX_SHADER, VS);
|
||||||
|
int fs = compile(GL_FRAGMENT_SHADER, FS);
|
||||||
|
glAttachShader(program, vs);
|
||||||
|
glAttachShader(program, fs);
|
||||||
|
glLinkProgram(program);
|
||||||
|
if (glGetProgrami(program, GL_LINK_STATUS) == 0) {
|
||||||
|
throw new RuntimeException("Crosshair link failed: " + glGetProgramInfoLog(program));
|
||||||
|
}
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
vao = glGenVertexArrays();
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
vbo = glGenBuffers();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, verts, GL_STATIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int compile(int type, String src) {
|
||||||
|
int id = glCreateShader(type);
|
||||||
|
glShaderSource(id, src);
|
||||||
|
glCompileShader(id);
|
||||||
|
if (glGetShaderi(id, GL_COMPILE_STATUS) == 0) {
|
||||||
|
throw new RuntimeException("Crosshair shader failed: " + glGetShaderInfoLog(id));
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw() {
|
||||||
|
glUseProgram(program);
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 12);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
glDeleteVertexArrays(vao);
|
||||||
|
glDeleteBuffers(vbo);
|
||||||
|
glDeleteProgram(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -85,4 +85,14 @@ public final class PlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Vec3 position() { return pos; }
|
public Vec3 position() { return pos; }
|
||||||
|
|
||||||
|
/** Prueft, ob die Spieler-AABB die Voxelzelle (bx,by,bz) ueberschneidet. */
|
||||||
|
public boolean overlapsCell(int bx, int by, int bz) {
|
||||||
|
float minX = pos.x - halfW, maxX = pos.x + halfW;
|
||||||
|
float minY = pos.y, maxY = pos.y + height;
|
||||||
|
float minZ = pos.z - halfW, maxZ = pos.z + halfW;
|
||||||
|
return maxX > bx && minX < bx + 1
|
||||||
|
&& maxY > by && minY < by + 1
|
||||||
|
&& maxZ > bz && minZ < bz + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.jfritzsche.homegarden.world;
|
package de.jfritzsche.homegarden.world;
|
||||||
|
|
||||||
import de.jfritzsche.homegarden.gfx.Mesh;
|
import de.jfritzsche.homegarden.gfx.Mesh;
|
||||||
|
import de.jfritzsche.homegarden.math.Vec3;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -49,7 +50,56 @@ public final class VoxelWorld {
|
|||||||
return get(x, y, z) != Block.AIR;
|
return get(x, y, z) != Block.AIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(int x, int y, int z, int id) {
|
/**
|
||||||
|
* Voxel-Raycast (Amanat/DDA). Start = Kamera, dir = normierte Blickrichtung.
|
||||||
|
* Liefert bei Treffer die getroffene Zelle in 'hit' und die Zelle davor in
|
||||||
|
* 'prev' (zum Platzieren). maxDist in Block-Einheiten.
|
||||||
|
*/
|
||||||
|
public boolean raycast(Vec3 origin, Vec3 dir, float maxDist, int[] hit, int[] prev) {
|
||||||
|
int ix = (int) Math.floor(origin.x);
|
||||||
|
int iy = (int) Math.floor(origin.y);
|
||||||
|
int iz = (int) Math.floor(origin.z);
|
||||||
|
|
||||||
|
float stepX = dir.x > 0 ? 1 : (dir.x < 0 ? -1 : 0);
|
||||||
|
float stepY = dir.y > 0 ? 1 : (dir.y < 0 ? -1 : 0);
|
||||||
|
float stepZ = dir.z > 0 ? 1 : (dir.z < 0 ? -1 : 0);
|
||||||
|
|
||||||
|
float tDeltaX = dir.x != 0 ? Math.abs(1f / dir.x) : Float.POSITIVE_INFINITY;
|
||||||
|
float tDeltaY = dir.y != 0 ? Math.abs(1f / dir.y) : Float.POSITIVE_INFINITY;
|
||||||
|
float tDeltaZ = dir.z != 0 ? Math.abs(1f / dir.z) : Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
float tMaxX = nextT(origin.x, ix, stepX, tDeltaX);
|
||||||
|
float tMaxY = nextT(origin.y, iy, stepY, tDeltaY);
|
||||||
|
float tMaxZ = nextT(origin.z, iz, stepZ, tDeltaZ);
|
||||||
|
|
||||||
|
int px = ix, py = iy, pz = iz;
|
||||||
|
float t = 0f;
|
||||||
|
while (t <= maxDist) {
|
||||||
|
if (isSolid(ix, iy, iz)) {
|
||||||
|
hit[0] = ix; hit[1] = iy; hit[2] = iz;
|
||||||
|
prev[0] = px; prev[1] = py; prev[2] = pz;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
px = ix; py = iy; pz = iz;
|
||||||
|
if (tMaxX < tMaxY && tMaxX < tMaxZ) {
|
||||||
|
t = tMaxX; tMaxX += tDeltaX; ix += stepX;
|
||||||
|
} else if (tMaxY < tMaxZ) {
|
||||||
|
t = tMaxY; tMaxY += tDeltaY; iy += stepY;
|
||||||
|
} else {
|
||||||
|
t = tMaxZ; tMaxZ += tDeltaZ; iz += stepZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float nextT(float o, int i, float step, float tDelta) {
|
||||||
|
if (step > 0) return (i + 1 - o) * tDelta;
|
||||||
|
if (step < 0) return (o - i) * tDelta;
|
||||||
|
return Float.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setzt einen Block (oeffentlich — Bausystem/Platzieren). */
|
||||||
|
public void set(int x, int y, int z, int id) {
|
||||||
if (x < 0 || y < 0 || z < 0 || x >= sx || y >= sy || z >= sz) return;
|
if (x < 0 || y < 0 || z < 0 || x >= sx || y >= sy || z >= sz) return;
|
||||||
blocks[idx(x, y, z)] = (byte) id;
|
blocks[idx(x, y, z)] = (byte) id;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user