commit b647cc32d8cf38ca2b443bad96986323fe52a9e2 Author: itycodes Date: Tue Oct 1 15:55:59 2024 +0200 Initial commit diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f36aa2e --- /dev/null +++ b/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' +} + +version = "0.0.1" + +sourceCompatibility = 17 +targetCompatibility = 17 + + +repositories { + mavenCentral() +} + +//applicationDefaultJvmArgs = ["-javaagent:lwjglx-debug-1.0.0.jar"] + +//project.ext.lwjglNatives = "natives-linux-arm64" +project.ext.lwjglNatives = "natives-linux" + +dependencies { + implementation platform("org.lwjgl:lwjgl-bom:3.3.1") + + implementation "org.lwjgl:lwjgl" + implementation "org.lwjgl:lwjgl-glfw" + implementation "org.lwjgl:lwjgl-opengl" + implementation "org.lwjgl:lwjgl-stb" + runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" + implementation "org.joml:joml:1.10.4" + implementation 'org.reflections:reflections:0.10.2' + implementation 'org.slf4j:slf4j-nop:1.7.32' +} + +application { + mainClass = 'ity.opencraft.Main' +} + +jar { + manifest { + attributes 'Main-Class': 'ity.opencraft.Main' + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1fb9e63 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.2/userguide/multi_project_builds.html + */ + +rootProject.name = 'OpenCraft' +include('app') diff --git a/src/main/java/ity/opencraft/Main.java b/src/main/java/ity/opencraft/Main.java new file mode 100644 index 0000000..e4279ac --- /dev/null +++ b/src/main/java/ity/opencraft/Main.java @@ -0,0 +1,41 @@ +package ity.opencraft; + +import ity.opencraft.io.Screen; +import ity.opencraft.io.Window; +import ity.opencraft.physics.PhysicsManager; +import ity.opencraft.render.Renderer; +import ity.opencraft.util.TextureLoader; +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendScanner; +import ity.opencraft.backends.CurrentBackend; +import ity.opencraft.io.Windows; +import org.lwjgl.glfw.GLFW; + +public class Main { + public static void main(String[] args) { + BackendScanner.scan(); + CurrentBackend.backend = Backend.GL; + + Screen.init(); + Window win = new Window(); + win.init(1000, 1000, "OpenCraft v0.0.1"); + win.select(); + win.setCursor(GLFW.GLFW_CURSOR_DISABLED); + Screen.put(Windows.MAIN.toString(), win); + TextureLoader.init(); + Renderer.init(); + + long mOld = System.currentTimeMillis(); + while(!win.shouldClose()) { + long delta = System.currentTimeMillis() - mOld; + mOld = System.currentTimeMillis(); + PhysicsManager.update(delta); + Renderer.render(delta); + win.swap(); + GLFW.glfwPollEvents(); + } + for(Thread worker : Renderer.worldGenWorkers) { + worker.stop(); + } + } +} diff --git a/src/main/java/ity/opencraft/backends/Backend.java b/src/main/java/ity/opencraft/backends/Backend.java new file mode 100644 index 0000000..dae2f97 --- /dev/null +++ b/src/main/java/ity/opencraft/backends/Backend.java @@ -0,0 +1,6 @@ +package ity.opencraft.backends; + +public enum Backend { + GL, + GLES +} diff --git a/src/main/java/ity/opencraft/backends/BackendImpl.java b/src/main/java/ity/opencraft/backends/BackendImpl.java new file mode 100644 index 0000000..882b388 --- /dev/null +++ b/src/main/java/ity/opencraft/backends/BackendImpl.java @@ -0,0 +1,8 @@ +package ity.opencraft.backends; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +public @interface BackendImpl { + Backend value(); +} diff --git a/src/main/java/ity/opencraft/backends/BackendScanner.java b/src/main/java/ity/opencraft/backends/BackendScanner.java new file mode 100644 index 0000000..3c422da --- /dev/null +++ b/src/main/java/ity/opencraft/backends/BackendScanner.java @@ -0,0 +1,52 @@ +package ity.opencraft.backends; + +import org.reflections.Reflections; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class BackendScanner { + public static Map, Map>> backends = new HashMap<>(); + public static void scan() { + ArrayList> subT = new ArrayList<>(); + for(Package p : Package.getPackages()) { + Set> subs = new Reflections(p.getName()).getSubTypesOf(Backended.class); + for(Class sub : subs) { + if(!subT.contains(sub)) subT.add(sub); + } + } + for(Class cls : subT) { + if(cls.getSuperclass() == Backended.class) { + for(Class clsB : subT) { + if(clsB.getSuperclass() == cls) { + if(clsB.isAnnotationPresent(BackendImpl.class)) { + Map> bck = null; + Backend backend = clsB.getAnnotation(BackendImpl.class).value(); + Supplier backendImpl = (() -> { + try { + return clsB.getConstructor().newInstance(); + } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + }); + if(!backends.containsKey(cls)) { + bck = new HashMap<>(); + bck.put(backend, backendImpl); + backends.put(cls, bck); + } + else { + bck = backends.get(cls); + bck.put(backend, backendImpl); + } + } + } + } + } + } + } +} diff --git a/src/main/java/ity/opencraft/backends/Backended.java b/src/main/java/ity/opencraft/backends/Backended.java new file mode 100644 index 0000000..fe27bf7 --- /dev/null +++ b/src/main/java/ity/opencraft/backends/Backended.java @@ -0,0 +1,14 @@ +package ity.opencraft.backends; + +public class Backended { + protected Backended backend; + + public Backended() { + if(this.getClass().getSuperclass() == Backended.class) { + this.backend = BackendScanner.backends.get(this.getClass()).get(CurrentBackend.backend).get(); + if(this.backend == null) { + throw new IllegalStateException("Backend not implemented: "+CurrentBackend.backend); + } + } + } +} diff --git a/src/main/java/ity/opencraft/backends/CurrentBackend.java b/src/main/java/ity/opencraft/backends/CurrentBackend.java new file mode 100644 index 0000000..fd28815 --- /dev/null +++ b/src/main/java/ity/opencraft/backends/CurrentBackend.java @@ -0,0 +1,5 @@ +package ity.opencraft.backends; + +public class CurrentBackend { + public static Backend backend; +} diff --git a/src/main/java/ity/opencraft/client/ViewportCamera.java b/src/main/java/ity/opencraft/client/ViewportCamera.java new file mode 100644 index 0000000..c49a8ae --- /dev/null +++ b/src/main/java/ity/opencraft/client/ViewportCamera.java @@ -0,0 +1,30 @@ +package ity.opencraft.client; + +import ity.opencraft.util.MatrixUtils; +import org.joml.Matrix3f; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class ViewportCamera { + public static Vector3f pos = new Vector3f(0,0,0); + + // This is an angle... + public static Vector3f rot = new Vector3f(0,0,180); + + public static Matrix4f getViewportMatrix() { + return MatrixUtils.viewMatrix(ViewportCamera.pos, ViewportCamera.rot); + } + + public static Vector3f transformVector(Vector3f vec) { + Vector3f rotation = new Vector3f(rot).mul((float) (Math.PI / 180.0f), new Vector3f()); + return new Vector3f(vec).mul(new Matrix3f().rotateXYZ(rotation).invert()); + } + + public static Vector3f forwardVector() { + return transformVector(new Vector3f(0, 0,-1)); + } + + public static Vector3f rightVector() { + return transformVector(new Vector3f(1, 0,0)); + } +} diff --git a/src/main/java/ity/opencraft/data/BlockMesh.java b/src/main/java/ity/opencraft/data/BlockMesh.java new file mode 100644 index 0000000..9299a56 --- /dev/null +++ b/src/main/java/ity/opencraft/data/BlockMesh.java @@ -0,0 +1,42 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backended; +import org.joml.Vector2f; +import org.joml.Vector3f; + +import java.util.List; + +public class BlockMesh extends Backended { + public void init() { + ((BlockMesh)this.backend).init(); + } + + public void upload(float[] pos, float[] norm, float[] texIds, float[] light, int[] inds) { + ((BlockMesh)this.backend).upload(pos, norm, texIds, light, inds); + } + + public void upload(List pos, List norm, List texIds, List light, List inds) { + ((BlockMesh)this.backend).upload(pos, norm, texIds, light, inds); + } + + public void bind() { + ((BlockMesh)this.backend).bind(); + } + + public void render() { + ((BlockMesh)this.backend).render(); + } + + public void unbind() { + ((BlockMesh)this.backend).unbind(); + } + + public void destroy(){ + ((BlockMesh)this.backend).destroy(); + } + + // Used for debug purposes. + public int getId() { + return ((BlockMesh)this.backend).getId(); + } +} diff --git a/src/main/java/ity/opencraft/data/BlockMeshData.java b/src/main/java/ity/opencraft/data/BlockMeshData.java new file mode 100644 index 0000000..1571e49 --- /dev/null +++ b/src/main/java/ity/opencraft/data/BlockMeshData.java @@ -0,0 +1,39 @@ +package ity.opencraft.data; + +import org.joml.Vector2f; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; + +public class BlockMeshData { + public ArrayList pos; + public ArrayList norms; + public ArrayList texIds; + public ArrayList light; + + public ArrayList inds; + + public BlockMeshData(ArrayList pos, ArrayList norms, ArrayList texIds, ArrayList light, ArrayList inds) { + this.pos = pos; + this.norms = norms; + this.texIds = texIds; + this.light = light; + + this.inds = inds; + } + + public void free() { + if(pos == null) return; + pos.clear(); + pos = null; + norms.clear(); + norms = null; + texIds.clear(); + texIds = null; + light.clear(); + light = null; + inds.clear(); + inds = null; + } +} diff --git a/src/main/java/ity/opencraft/data/BlockMeshGL.java b/src/main/java/ity/opencraft/data/BlockMeshGL.java new file mode 100644 index 0000000..7fc9424 --- /dev/null +++ b/src/main/java/ity/opencraft/data/BlockMeshGL.java @@ -0,0 +1,161 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.joml.Vector2f; +import org.joml.Vector3f; + +import java.util.List; + +import static org.lwjgl.opengl.GL30.*; + +@BackendImpl(Backend.GL) +public class BlockMeshGL extends BlockMesh { + private int vaoID; + + private int pboID; + private int nboID; + private int tboID; + + private int lboID; + + private int iboID; + + private int length; + + @Override + public void init() { + vaoID = glGenVertexArrays(); + glBindVertexArray(vaoID); + + pboID = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, pboID); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + nboID = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, nboID); + glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + tboID = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, tboID); + glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + lboID = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, lboID); + glVertexAttribPointer(3, 1, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + iboID = glGenBuffers(); + + glBindVertexArray(0); + } + + @Override + public void upload(float[] pos, float[] norm, float[] texIds, float[] light, int[] inds) { + this.length = inds.length; + + glBindVertexArray(vaoID); + + glBindBuffer(GL_ARRAY_BUFFER, pboID); + glBufferData(GL_ARRAY_BUFFER, pos, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ARRAY_BUFFER, nboID); + glBufferData(GL_ARRAY_BUFFER, norm, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ARRAY_BUFFER, tboID); + glBufferData(GL_ARRAY_BUFFER, texIds, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ARRAY_BUFFER, lboID); + glBufferData(GL_ARRAY_BUFFER, light, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, inds, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glBindVertexArray(0); + } + + @Override + public void upload(List pos, List norm, List texIds, List light, List inds) { + float[] pBuf = new float[pos.size() * 3]; + for(int i = 0; i < pos.size(); i++) { + pBuf[i*3] = pos.get(i).x; + pBuf[i*3+1] = pos.get(i).y; + pBuf[i*3+2] = pos.get(i).z; + } + + float[] nBuf = new float[norm.size() * 3]; + for(int i = 0; i < norm.size(); i++) { + nBuf[i*3] = norm.get(i).x; + nBuf[i*3+1] = norm.get(i).y; + nBuf[i*3+2] = norm.get(i).z; + } + + float[] tBuf = new float[texIds.size() * 2]; + for(int i = 0; i < texIds.size(); i++) { + tBuf[i*2] = texIds.get(i).x; + tBuf[i*2+1] = texIds.get(i).y; + } + + float[] lBuf = new float[light.size()]; + for(int i = 0; i < light.size(); i++) { + lBuf[i] = light.get(i); + } + + int[] iBuf = new int[inds.size()]; + for(int i = 0; i < inds.size(); i++) { + iBuf[i] = inds.get(i); + } + + upload(pBuf, nBuf, tBuf, lBuf, iBuf); + } + + @Override + public void bind() { + glBindVertexArray(vaoID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + } + + @Override + public void render() { + // TODO this is a bit hacky, idk... + if(this.length > 0) + glDrawElements(GL_TRIANGLES, this.length, GL_UNSIGNED_INT, 0); + } + + @Override + public void unbind() { + glBindVertexArray(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + } + + @Override + public void destroy() { + glDeleteVertexArrays(vaoID); + glDeleteBuffers(pboID); + glDeleteBuffers(nboID); + glDeleteBuffers(tboID); + glDeleteBuffers(lboID); + glDeleteBuffers(iboID); + } + + @Override + public int getId() { + return vaoID; + } +} diff --git a/src/main/java/ity/opencraft/data/BlockMeshHandle.java b/src/main/java/ity/opencraft/data/BlockMeshHandle.java new file mode 100644 index 0000000..0ae5e31 --- /dev/null +++ b/src/main/java/ity/opencraft/data/BlockMeshHandle.java @@ -0,0 +1,28 @@ +package ity.opencraft.data; + +public class BlockMeshHandle { + public float timer; + public BlockMesh mesh; + private BlockMeshPool pool; + public BlockMeshData data; + + public BlockMeshHandle(BlockMeshPool pool, BlockMeshData data) { + this.pool = pool; + this.data = data; + } + + public void init() { + mesh = new BlockMesh(); + mesh.init(); + } + + public void upload() { + mesh.upload(data.pos, data.norms, data.texIds, data.light, data.inds); + } + + public void free() { + pool.free.add(this); + data.free(); + data = null; + } +} diff --git a/src/main/java/ity/opencraft/data/BlockMeshPool.java b/src/main/java/ity/opencraft/data/BlockMeshPool.java new file mode 100644 index 0000000..07227fd --- /dev/null +++ b/src/main/java/ity/opencraft/data/BlockMeshPool.java @@ -0,0 +1,26 @@ +package ity.opencraft.data; + +import java.util.ArrayList; + +public class BlockMeshPool { + public final ArrayList free = new ArrayList<>(); + + public BlockMeshHandle make(BlockMeshData data) { + if(free.size() > 0) { + BlockMeshHandle handle = free.remove(free.size()-1); + handle.data = data; + handle.upload(); + return handle; + } else { + BlockMeshHandle handle = new BlockMeshHandle(this, data); + handle.init(); + handle.upload(); + return handle; + } + } + + // TODO implement pool GC + public void garbageCollectUpdate(float delta) { + + } +} diff --git a/src/main/java/ity/opencraft/data/IndexedMesh.java b/src/main/java/ity/opencraft/data/IndexedMesh.java new file mode 100644 index 0000000..229d8f3 --- /dev/null +++ b/src/main/java/ity/opencraft/data/IndexedMesh.java @@ -0,0 +1,32 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backended; +import org.joml.Vector3f; + +import java.util.List; + +public class IndexedMesh extends Backended { + public void init() { + ((IndexedMesh)this.backend).init(); + } + + public void upload(float[] pos, int[] inds) { + ((IndexedMesh)this.backend).upload(pos, inds); + } + + public void upload(List pos, List inds) { + ((IndexedMesh)this.backend).upload(pos, inds); + } + + public void bind() { + ((IndexedMesh)this.backend).bind(); + } + + public void render() { + ((IndexedMesh)this.backend).render(); + } + + public void unbind() { + ((IndexedMesh)this.backend).unbind(); + } +} diff --git a/src/main/java/ity/opencraft/data/IndexedMeshGL.java b/src/main/java/ity/opencraft/data/IndexedMeshGL.java new file mode 100644 index 0000000..21eb618 --- /dev/null +++ b/src/main/java/ity/opencraft/data/IndexedMeshGL.java @@ -0,0 +1,82 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.joml.Vector3f; +import static org.lwjgl.opengl.GL30.*; + +import java.util.List; + +@BackendImpl(Backend.GL) +public class IndexedMeshGL extends IndexedMesh { + private int vaoID; + + private int pboID; + + private int iboID; + + private int length; + + public void init() { + vaoID = glGenVertexArrays(); + glBindVertexArray(vaoID); + + pboID = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, pboID); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + iboID = glGenBuffers(); + + glBindVertexArray(0); + } + + public void upload(float[] pos, int[] inds) { + this.length = inds.length; + + glBindVertexArray(vaoID); + + glBindBuffer(GL_ARRAY_BUFFER, pboID); + glBufferData(GL_ARRAY_BUFFER, pos, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, inds, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glBindVertexArray(0); + } + + public void upload(List pos, List inds) { + float[] pBuf = new float[pos.size() * 3]; + for(int i = 0; i < pos.size(); i++) { + pBuf[i*3] = pos.get(i).x; + pBuf[i*3+1] = pos.get(i).y; + pBuf[i*3+2] = pos.get(i).z; + } + + int[] iBuf = new int[inds.size()]; + for(int i = 0; i < inds.size(); i++) { + iBuf[i] = inds.get(i); + } + + upload(pBuf, iBuf); + } + + public void bind() { + glBindVertexArray(vaoID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID); + glEnableVertexAttribArray(0); + } + + public void render() { + glDrawElements(GL_TRIANGLES, this.length, GL_UNSIGNED_INT, 0); + } + + public void unbind() { + glBindVertexArray(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDisableVertexAttribArray(0); + } + +} diff --git a/src/main/java/ity/opencraft/data/Texture.java b/src/main/java/ity/opencraft/data/Texture.java new file mode 100644 index 0000000..7ce4446 --- /dev/null +++ b/src/main/java/ity/opencraft/data/Texture.java @@ -0,0 +1,19 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backended; + +public class Texture extends Backended { + public int id; + public int w; + public int h; + public int c; + + public void bind() { + ((Texture)this.backend).id = this.id; + ((Texture)this.backend).bind(); + } + + public void unbind() { + ((Texture)this.backend).unbind(); + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/data/TextureGL.java b/src/main/java/ity/opencraft/data/TextureGL.java new file mode 100644 index 0000000..db7afc1 --- /dev/null +++ b/src/main/java/ity/opencraft/data/TextureGL.java @@ -0,0 +1,18 @@ +package ity.opencraft.data; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.lwjgl.opengl.GL11; + +@BackendImpl(Backend.GL) +public class TextureGL extends Texture { + @Override + public void bind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + } + + @Override + public void unbind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } +} diff --git a/src/main/java/ity/opencraft/io/BackendCapabilities.java b/src/main/java/ity/opencraft/io/BackendCapabilities.java new file mode 100644 index 0000000..53f15b4 --- /dev/null +++ b/src/main/java/ity/opencraft/io/BackendCapabilities.java @@ -0,0 +1,9 @@ +package ity.opencraft.io; + +import ity.opencraft.backends.Backended; + +public class BackendCapabilities extends Backended { + void initCaps() { + ((BackendCapabilities)this.backend).initCaps(); + } +} diff --git a/src/main/java/ity/opencraft/io/BackendCapabilitiesGL.java b/src/main/java/ity/opencraft/io/BackendCapabilitiesGL.java new file mode 100644 index 0000000..b6f6b2d --- /dev/null +++ b/src/main/java/ity/opencraft/io/BackendCapabilitiesGL.java @@ -0,0 +1,13 @@ +package ity.opencraft.io; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.lwjgl.opengl.GL; + +@BackendImpl(Backend.GL) +public class BackendCapabilitiesGL extends BackendCapabilities { + @Override + void initCaps() { + GL.createCapabilities(); + } +} diff --git a/src/main/java/ity/opencraft/io/Keyboard.java b/src/main/java/ity/opencraft/io/Keyboard.java new file mode 100644 index 0000000..49bd42d --- /dev/null +++ b/src/main/java/ity/opencraft/io/Keyboard.java @@ -0,0 +1,18 @@ +package ity.opencraft.io; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWKeyCallback; + +public class Keyboard { + private final boolean[] keys = new boolean[GLFW.GLFW_KEY_LAST]; + public void init(long wID) { + GLFW.glfwSetKeyCallback(wID, new GLFWKeyCallback() { + public void invoke(long window, int key, int scancode, int action, int mods) { + keys[key] = (action != GLFW.GLFW_RELEASE); + } + }); + } + public boolean isKeyDown(int key) { + return keys[key]; + } +} diff --git a/src/main/java/ity/opencraft/io/Mouse.java b/src/main/java/ity/opencraft/io/Mouse.java new file mode 100644 index 0000000..28c8ef8 --- /dev/null +++ b/src/main/java/ity/opencraft/io/Mouse.java @@ -0,0 +1,32 @@ +package ity.opencraft.io; + +import org.joml.Vector2f; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWCursorPosCallback; +import org.lwjgl.glfw.GLFWKeyCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; + +public class Mouse { + private final Vector2f cursorPos = new Vector2f(); + private final boolean[] buttons = new boolean[GLFW.GLFW_MOUSE_BUTTON_LAST]; + public void init(long wID) { + GLFW.glfwSetCursorPosCallback(wID, new GLFWCursorPosCallback() { + public void invoke(long window, double xPos, double yPos) { + cursorPos.set(xPos, yPos); + } + }); + GLFW.glfwSetMouseButtonCallback(wID, new GLFWMouseButtonCallback() { + public void invoke(long window, int button, int action, int mods) { + buttons[button] = (action != GLFW.GLFW_RELEASE); + } + }); + } + + public Vector2f getCursorPos() { + return new Vector2f(cursorPos); + } + + public boolean isButtonPressed(int button) { + return buttons[button]; + } +} diff --git a/src/main/java/ity/opencraft/io/Screen.java b/src/main/java/ity/opencraft/io/Screen.java new file mode 100644 index 0000000..2f515ee --- /dev/null +++ b/src/main/java/ity/opencraft/io/Screen.java @@ -0,0 +1,19 @@ +package ity.opencraft.io; + +import org.lwjgl.glfw.GLFW; + +import java.util.HashMap; + +public class Screen { + public static HashMap windows = new HashMap<>(); + public static void init() { + GLFW.glfwInit(); + } + public static void put(String name, Window value) { + windows.put(name, value); + } + + public static Window get(String name) { + return windows.get(name); + } +} diff --git a/src/main/java/ity/opencraft/io/Window.java b/src/main/java/ity/opencraft/io/Window.java new file mode 100644 index 0000000..bc5201b --- /dev/null +++ b/src/main/java/ity/opencraft/io/Window.java @@ -0,0 +1,73 @@ +package ity.opencraft.io; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWWindowSizeCallback; + +import static org.lwjgl.opengl.GL11.glViewport; +import static org.lwjgl.system.MemoryUtil.NULL; + +public class Window { + private long wID; + public boolean maximized; + + private Keyboard keyboard; + private Mouse mouse; + + public int width, height; + public void init(int sizeX, int sizeY, String title) { + this.width = sizeX; // Ah java... + this.height = sizeY; + wID = GLFW.glfwCreateWindow(width, height, title, NULL, NULL); + + GLFW.glfwSetWindowSizeCallback(wID, new GLFWWindowSizeCallback() { + @Override + public void invoke(long window, int w, int h) { + width = w; + height = h; + glViewport(0, 0, w, h); + } + }); + this.keyboard = new Keyboard(); + keyboard.init(wID); + this.mouse = new Mouse(); + mouse.init(wID); + } + public void select() { + GLFW.glfwMakeContextCurrent(wID); + GLFW.glfwSwapInterval(1); + new BackendCapabilities().initCaps(); + } + public void swap() { + glViewport(0, 0, width, height); + GLFW.glfwSwapBuffers(wID); + } + + public void fullscreen() { + maximized = true; + GLFW.glfwMaximizeWindow(wID); + } + + public void restore() { + maximized = false; + GLFW.glfwRestoreWindow(wID); + } + + public Keyboard getKeyboard() { + return this.keyboard; + } + + public Mouse getMouse() { + return this.mouse; + } + + public void setCursor(int cursor) { + GLFW.glfwSetInputMode(wID, GLFW.GLFW_CURSOR, cursor); + } + + /** + * @return whether the close button or similar was pressed + */ + public boolean shouldClose() { + return GLFW.glfwWindowShouldClose(wID); + } +} diff --git a/src/main/java/ity/opencraft/io/Windows.java b/src/main/java/ity/opencraft/io/Windows.java new file mode 100644 index 0000000..d74445c --- /dev/null +++ b/src/main/java/ity/opencraft/io/Windows.java @@ -0,0 +1,10 @@ +package ity.opencraft.io; + +public enum Windows { + MAIN; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } +} diff --git a/src/main/java/ity/opencraft/mesh/BlockModelType.java b/src/main/java/ity/opencraft/mesh/BlockModelType.java new file mode 100644 index 0000000..d35b5f8 --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/BlockModelType.java @@ -0,0 +1,8 @@ +package ity.opencraft.mesh; + +public enum BlockModelType { + CUBE, + CROSS, + + WATER +} diff --git a/src/main/java/ity/opencraft/mesh/ChunkMesher.java b/src/main/java/ity/opencraft/mesh/ChunkMesher.java new file mode 100644 index 0000000..6f63d59 --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/ChunkMesher.java @@ -0,0 +1,124 @@ +package ity.opencraft.mesh; + +import ity.opencraft.world.Chunk; +import ity.opencraft.world.Block; +import ity.opencraft.world.Blocks; +import ity.opencraft.world.World; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ChunkMesher { + + private static Block top(int x, int y, int z) { + if(y == World.sizeY* Chunk.size) return Blocks.AIR; + return World.getBlockAtLazy(x, y+1, z); + } + private static Block bottom(int x, int y, int z) { + if(y == 0) return Blocks.AIR; + return World.getBlockAtLazy(x, y-1, z); + } + private static Block left(int x, int y, int z) { + if(x == 0) return Blocks.AIR; + return World.getBlockAtLazy(x-1, y, z); + } + private static Block right(int x, int y, int z) { + if(x == World.sizeXZ*Chunk.size) return Blocks.AIR; + return World.getBlockAtLazy(x+1, y, z); + } + private static Block back(int x, int y, int z) { + if(z == 0) return Blocks.AIR; + return World.getBlockAtLazy(x, y, z-1); + } + private static Block front(int x, int y, int z) { + if(z == World.sizeXZ*Chunk.size) return Blocks.AIR; + return World.getBlockAtLazy(x, y, z+1); + } + + + /** + * Fill the given parameters with the mesh of the chunk at the given position + * @param posA The ArrayList to fill with positions + * @param normA The ArrayList tof ill with normals + * @param inxA The ArrayList to fill with indices + * @param texA The ArrayList to fill with texture ids + * @param loc The location of the chunk, in chunk coordinates. + */ + public static void meshChunkWithSolid(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, ArrayList light, Vector3i loc) { + for(int i = 0; i < Chunk.size; i++) { + for(int j = 0; j < Chunk.size; j++) { + for(int k = 0; k < Chunk.size; k++) { + int wPosX = i + loc.x*Chunk.size, wPosY = j + loc.y*Chunk.size, wPosZ = k + loc.z*Chunk.size; + Block block = World.getBlockAtLazy(wPosX, wPosY, wPosZ); + if(block.visible() && !block.isBlend()) { + switch(block.modelType()) { + case CUBE: + CubeMesher.meshCubeWith(posA, normA, texA, inxA, block, new Vector3f(wPosX, wPosY, wPosZ), new CubeSides( + top(wPosX, wPosY, wPosZ).isTransparent(block), + bottom(wPosX, wPosY, wPosZ).isTransparent(block), + left(wPosX, wPosY, wPosZ).isTransparent(block), + right(wPosX, wPosY, wPosZ).isTransparent(block), + back(wPosX, wPosY, wPosZ).isTransparent(block), + front(wPosX, wPosY, wPosZ).isTransparent(block) + ), light, new Vector3i(wPosX, wPosY, wPosZ)); + break; + case CROSS: + CrossMesher.meshCrossWith(posA, normA, texA, inxA, block, new Vector3f(wPosX, wPosY, wPosZ), light, new Vector3i(wPosX, wPosY, wPosZ)); + } + } + } + } + } + } + public static void meshChunkWithBlend(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, ArrayList light, Vector3i loc) { + for(int i = 0; i < Chunk.size; i++) { + for(int j = 0; j < Chunk.size; j++) { + for(int k = 0; k < Chunk.size; k++) { + int wPosX = i + loc.x*Chunk.size, wPosY = j + loc.y*Chunk.size, wPosZ = k + loc.z*Chunk.size; + Block block3; + if(wPosY-1 < 0) { + block3 = Blocks.AIR; + } else { + block3 = World.getBlockAtLazy(wPosX, wPosY-1, wPosZ); + } + Block block = World.getBlockAtLazy(wPosX, wPosY, wPosZ); + Block block2 = World.getBlockAtLazy(wPosX, wPosY+1, wPosZ); + Block block21 = World.getBlockAtLazy(wPosX+1, wPosY, wPosZ); + Block block22 = World.getBlockAtLazy(wPosX, wPosY, wPosZ+1); + Block block23 = World.getBlockAtLazy(wPosX-1, wPosY, wPosZ-1); + Block block24 = World.getBlockAtLazy(wPosX, wPosY, wPosZ); + + if(block.visible() && block.isBlend()) { + switch(block.modelType()) { + case WATER: + WaterMesher.meshWaterWith(block2 == Blocks.AIR, block3 == Blocks.WATER, posA, normA, texA, inxA, block, new Vector3f(wPosX, wPosY, wPosZ), new CubeSides( + top(wPosX, wPosY, wPosZ).isTransparent(block), + bottom(wPosX, wPosY, wPosZ).isTransparent(block), + left(wPosX, wPosY, wPosZ).isTransparent(block), + right(wPosX, wPosY, wPosZ).isTransparent(block), + back(wPosX, wPosY, wPosZ).isTransparent(block), + front(wPosX, wPosY, wPosZ).isTransparent(block) + ), light, new Vector3i(wPosX, wPosY, wPosZ)); + break; + case CUBE: + CubeMesher.meshCubeWith(posA, normA, texA, inxA, block, new Vector3f(wPosX, wPosY, wPosZ), new CubeSides( + top(wPosX, wPosY, wPosZ).isTransparent(block), + bottom(wPosX, wPosY, wPosZ).isTransparent(block), + left(wPosX, wPosY, wPosZ).isTransparent(block), + right(wPosX, wPosY, wPosZ).isTransparent(block), + back(wPosX, wPosY, wPosZ).isTransparent(block), + front(wPosX, wPosY, wPosZ).isTransparent(block) + ), light, new Vector3i(wPosX, wPosY, wPosZ)); + break; + case CROSS: + CrossMesher.meshCrossWith(posA, normA, texA, inxA, block, new Vector3f(wPosX, wPosY, wPosZ), light, new Vector3i(wPosX, wPosY, wPosZ)); + } + } + } + } + } + } +} diff --git a/src/main/java/ity/opencraft/mesh/CrossMesher.java b/src/main/java/ity/opencraft/mesh/CrossMesher.java new file mode 100644 index 0000000..de0acca --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/CrossMesher.java @@ -0,0 +1,94 @@ +package ity.opencraft.mesh; + +import ity.opencraft.world.BlockSide; +import ity.opencraft.world.Block; +import ity.opencraft.world.World; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class CrossMesher { + public static int atlasSize = 4; + + private static void meshFace(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList facePosA, ArrayList texId, Vector3f faceNorm, ArrayList faceInxA, ArrayList lightA, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + int bInx = posA.size(); + ArrayList pos = new ArrayList<>(facePosA.stream().map(p -> p.add(loc)).collect(Collectors.toList())); + ArrayList inds = new ArrayList<>(faceInxA.stream().map(i -> i+bInx).collect(Collectors.toList())); + ArrayList texs = new ArrayList<>(texId.stream().map(v -> v.add(texOff).div(atlasSize,atlasSize)).collect(Collectors.toList())); + posA.addAll(pos); + inxA.addAll(inds); + texA.addAll(texs); + for(Vector3f p : pos) { + normA.add(faceNorm); + } + } + + /** + * Fill the given parameters with the mesh of a partial cube, translated. + * @param posA The ArrayList to fill with positions + * @param normA The ArrayList tof ill with normals + * @param inxA The ArrayList to fill with indices + * @param loc The offset by which to offset the generated mesh + */ + public static void meshCrossWith(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Block block, Vector3f loc, ArrayList lightA, Vector3i wPos) { + meshFace1(posA, normA, texA, inxA, loc, new Vector2f(block.textureIndex(BlockSide.CROSS_1, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.LEFT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), lightA, wPos); + meshFace2(posA, normA, texA, inxA, loc, new Vector2f(block.textureIndex(BlockSide.CROSS_2, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.RIGHT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), lightA, wPos); + meshFace3(posA, normA, texA, inxA, loc, new Vector2f(block.textureIndex(BlockSide.CROSS_2, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.FRONT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), lightA, wPos); + meshFace4(posA, normA, texA, inxA, loc, new Vector2f(block.textureIndex(BlockSide.CROSS_2, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.BACK, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), lightA, wPos); + } + + private static void meshFace1(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList lightA, Vector3i wPos) { + meshFace(posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, 0.5f, -0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,1), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0)), lightA, wPos); + } + private static void meshFace2(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList lightA, Vector3i wPos) { + meshFace(posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,-1), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0)), lightA, wPos); + } + private static void meshFace3(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList lightA, Vector3i wPos) { + meshFace(posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, 0.5f, -0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,1), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2)), lightA, wPos); + } + private static void meshFace4(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList lightA, Vector3i wPos) { + meshFace(posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,-1), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2)), lightA, wPos); + } +} diff --git a/src/main/java/ity/opencraft/mesh/CubeMesher.java b/src/main/java/ity/opencraft/mesh/CubeMesher.java new file mode 100644 index 0000000..13aff2c --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/CubeMesher.java @@ -0,0 +1,157 @@ +package ity.opencraft.mesh; + +import ity.opencraft.world.BlockSide; +import ity.opencraft.world.Block; +import ity.opencraft.world.World; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; +import java.util.stream.Collectors; + +public class CubeMesher { + public static int atlasSize = 4; + private static ArrayList rotateLeftOnce(ArrayList inp) { + ArrayList out = new ArrayList<>(); + for(int i = 1; i < inp.size(); i++) { + out.add(inp.get(i)); + } + out.add(inp.get(0)); + return out; + } + private static ArrayList rotateLeft(ArrayList inp, int n) { + for(int i = 0; i < n; i++) { + inp = rotateLeftOnce(inp); + } + return inp; + } + + private static void meshFace(boolean randRotate, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList facePosA, ArrayList texId, Vector3f faceNorm, ArrayList faceInxA) { + Random rand = new Random(loc.hashCode()); + int bInx = posA.size(); + ArrayList pos = new ArrayList<>(facePosA.stream().map(p -> p.add(loc)).collect(Collectors.toList())); + ArrayList inds = new ArrayList<>(faceInxA.stream().map(i -> i+bInx).collect(Collectors.toList())); + ArrayList texs = new ArrayList<>(texId.stream().map(v -> v.add(texOff).div(atlasSize,atlasSize)).collect(Collectors.toList())); + posA.addAll(pos); + inxA.addAll(inds); + if(randRotate) { + texA.addAll(rotateLeft(texs, rand.nextInt(4))); + } else { + texA.addAll(texs); + } + for(Vector3f p : pos) { + normA.add(faceNorm); + } + } + + private static void meshFaceFront(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z+1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,1), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceBack(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z-1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, 0.5f, -0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,-1), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceLeft(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x-1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f(-0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(-1,0,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceRight(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x+1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(1,0,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceTop(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y+1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,1,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceBottom(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y-1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,-1,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + + /** + * Fill the given parameters with the mesh of a partial cube, translated. + * @param posA The ArrayList to fill with positions + * @param normA The ArrayList tof ill with normals + * @param inxA The ArrayList to fill with indices + * @param loc The offset by which to offset the generated mesh + * @param sides The sides which to show. True means render, false means do not render. + */ + public static void meshCubeWith(ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Block block, Vector3f loc, CubeSides sides, ArrayList light, Vector3i wPos) { + if(sides.back) + meshFaceBack(block.textureRotate(BlockSide.BACK), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.BACK, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.BACK, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + if(sides.front) + meshFaceFront(block.textureRotate(BlockSide.FRONT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.FRONT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.FRONT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + if(sides.top) + meshFaceTop(block.textureRotate(BlockSide.TOP), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.TOP, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.TOP, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + if(sides.bottom) + meshFaceBottom(block.textureRotate(BlockSide.BOTTOM), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.BOTTOM, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.BOTTOM, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + if(sides.left) + meshFaceLeft(block.textureRotate(BlockSide.LEFT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.LEFT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.LEFT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + if(sides.right) + meshFaceRight(block.textureRotate(BlockSide.RIGHT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.RIGHT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))%atlasSize, block.textureIndex(BlockSide.RIGHT, new Vector3i((int)loc.x, (int)loc.y, (int)loc.z))/atlasSize), wPos); + } +} diff --git a/src/main/java/ity/opencraft/mesh/CubeSides.java b/src/main/java/ity/opencraft/mesh/CubeSides.java new file mode 100644 index 0000000..7241772 --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/CubeSides.java @@ -0,0 +1,19 @@ +package ity.opencraft.mesh; + +public class CubeSides { + public boolean top; + public boolean bottom; + public boolean left; + public boolean right; + public boolean back; + public boolean front; + + public CubeSides(boolean top, boolean bottom, boolean left, boolean right, boolean back, boolean front) { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + this.back = back; + this.front = front; + } +} diff --git a/src/main/java/ity/opencraft/mesh/WaterMesher.java b/src/main/java/ity/opencraft/mesh/WaterMesher.java new file mode 100644 index 0000000..b5234c9 --- /dev/null +++ b/src/main/java/ity/opencraft/mesh/WaterMesher.java @@ -0,0 +1,265 @@ +package ity.opencraft.mesh; + +import ity.opencraft.world.Block; +import ity.opencraft.world.BlockSide; +import ity.opencraft.world.World; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; +import java.util.stream.Collectors; + +public class WaterMesher { + public static int atlasSize = 4; + private static ArrayList rotateLeftOnce(ArrayList inp) { + ArrayList out = new ArrayList<>(); + for(int i = 1; i < inp.size(); i++) { + out.add(inp.get(i)); + } + out.add(inp.get(0)); + return out; + } + private static ArrayList rotateLeft(ArrayList inp, int n) { + for(int i = 0; i < n; i++) { + inp = rotateLeftOnce(inp); + } + return inp; + } + + private static void meshFace(boolean randRotate, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Vector3f loc, Vector2f texOff, ArrayList facePosA, ArrayList texId, Vector3f faceNorm, ArrayList faceInxA) { + Random rand = new Random(loc.hashCode()); + int bInx = posA.size(); + ArrayList pos = new ArrayList<>(facePosA.stream().map(p -> p.add(loc)).collect(Collectors.toList())); + ArrayList inds = new ArrayList<>(faceInxA.stream().map(i -> i+bInx).collect(Collectors.toList())); + ArrayList texs = new ArrayList<>(texId.stream().map(v -> v.add(texOff).div(atlasSize,atlasSize)).collect(Collectors.toList())); + posA.addAll(pos); + inxA.addAll(inds); + if(randRotate) { + texA.addAll(rotateLeft(texs, rand.nextInt(4))); + } else { + texA.addAll(texs); + } + for(Vector3f p : pos) { + normA.add(faceNorm); + } + } + + private static void meshFaceFront(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z+1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.45f, 0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.45f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,1), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceBack(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z-1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.45f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, 0.45f, -0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,-1), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceLeft(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x-1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.45f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f(-0.5f, 0.45f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(-1,0,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceRight(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x+1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.45f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.45f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(1,0,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceTop2(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y+1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,1,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceTop(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y+1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.45f, -0.5f), + new Vector3f(-0.5f, 0.45f, -0.5f), + new Vector3f(-0.5f, 0.45f, 0.5f), + new Vector3f( 0.5f, 0.45f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,1,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceBottom(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y-1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,-1,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceBottom3(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y-1, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, -0.5f-0.0625f, -0.5f), + new Vector3f(-0.5f, -0.5f-0.0625f, -0.5f), + new Vector3f(-0.5f, -0.5f-0.0625f, 0.5f), + new Vector3f( 0.5f, -0.5f-0.0625f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(1f,0f), + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f))), new Vector3f(0,-1,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + + private static void meshFaceFront2(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z+1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, 0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,1), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceBack2(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x, wPos.y, wPos.z-1); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, 0.5f, -0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(0,0,-1), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + private static void meshFaceLeft2(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x-1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f(-0.5f, 0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, -0.5f), + new Vector3f(-0.5f, -0.5f, 0.5f), + new Vector3f(-0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(-1,0,0), new ArrayList<>(Arrays.asList(0, 1, 2, 2, 3, 0))); + } + private static void meshFaceRight2(boolean randFace, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList lightA, ArrayList inxA, Vector3f loc, Vector2f texOff, Vector3i wPos) { + float light = World.getLightAt(wPos.x+1, wPos.y, wPos.z); + lightA.addAll(Arrays.asList(light, light, light, light)); + meshFace(randFace, posA, normA, texA, inxA, loc, texOff, new ArrayList<>(Arrays.asList( + new Vector3f( 0.5f, 0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, -0.5f), + new Vector3f( 0.5f, -0.5f, 0.5f), + new Vector3f( 0.5f, 0.5f, 0.5f))), new ArrayList<>(Arrays.asList( + + new Vector2f(0f,0f), + new Vector2f(0f,1f), + new Vector2f(1f,1f), + new Vector2f(1f,0f))), new Vector3f(1,0,0), new ArrayList<>(Arrays.asList(2, 1, 0, 0, 3, 2))); + } + + /** + * Fill the given parameters with the mesh of a partial cube, translated. + * @param posA The ArrayList to fill with positions + * @param normA The ArrayList tof ill with normals + * @param inxA The ArrayList to fill with indices + * @param loc The offset by which to offset the generated mesh + * @param sides The sides which to show. True means render, false means do not render. + */ + public static void meshWaterWith(boolean isUp, boolean isBot, ArrayList posA, ArrayList normA, ArrayList texA, ArrayList inxA, Block block, Vector3f loc, CubeSides sides, ArrayList light, Vector3i wPos) { + if(sides.back) { + if(isUp) { + meshFaceBack(block.textureRotate(BlockSide.BACK), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.BACK, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.BACK, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } else { + meshFaceBack2(block.textureRotate(BlockSide.BACK), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.BACK, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.BACK, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } + } if(sides.front) { + if(isUp) { + meshFaceFront(block.textureRotate(BlockSide.FRONT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.FRONT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.FRONT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } else { + meshFaceFront2(block.textureRotate(BlockSide.FRONT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.FRONT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.FRONT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } + } if(sides.top) { + if(isUp) { + meshFaceTop(block.textureRotate(BlockSide.TOP), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.TOP, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.TOP, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } else { + meshFaceTop2(block.textureRotate(BlockSide.TOP), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.TOP, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.TOP, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } + } + if(sides.bottom) { + meshFaceBottom(block.textureRotate(BlockSide.BOTTOM), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.BOTTOM, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.BOTTOM, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } if(sides.left) { + if(isUp) { + meshFaceLeft(block.textureRotate(BlockSide.LEFT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.LEFT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.LEFT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } else { + meshFaceLeft2(block.textureRotate(BlockSide.LEFT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.LEFT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.LEFT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + + } + } if(sides.right) { + if(isUp) { + meshFaceRight(block.textureRotate(BlockSide.RIGHT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.RIGHT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.RIGHT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } else { + meshFaceRight2(block.textureRotate(BlockSide.RIGHT), posA, normA, texA, light, inxA, loc, new Vector2f(block.textureIndex(BlockSide.RIGHT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) % atlasSize, block.textureIndex(BlockSide.RIGHT, new Vector3i((int) loc.x, (int) loc.y, (int) loc.z)) / atlasSize), wPos); + } + } + } +} diff --git a/src/main/java/ity/opencraft/physics/Gravity.java b/src/main/java/ity/opencraft/physics/Gravity.java new file mode 100644 index 0000000..e900090 --- /dev/null +++ b/src/main/java/ity/opencraft/physics/Gravity.java @@ -0,0 +1,17 @@ +package ity.opencraft.physics; + +import org.joml.Vector3f; + +public class Gravity { + public static float GRAVITY = 9.8f; + public static float inAir = 0f; + public static void update(float delta) { + if(PlayerMovement.isOnGround()) { + PlayerVelocity.vel.y = 0.0f; + inAir = 0.0f; + } else { + inAir += delta/1000.0f*GRAVITY; + PlayerVelocity.vel.sub(new Vector3f(0f, inAir*1.5f, 0f)); + } + } +} diff --git a/src/main/java/ity/opencraft/physics/PhysicsManager.java b/src/main/java/ity/opencraft/physics/PhysicsManager.java new file mode 100644 index 0000000..4e82590 --- /dev/null +++ b/src/main/java/ity/opencraft/physics/PhysicsManager.java @@ -0,0 +1,14 @@ +package ity.opencraft.physics; + +public class PhysicsManager { + + /** + * Advance all physics calculations + * @param delta The delta time, in seconds. + */ + public static void update(float delta) { + //Gravity.update(delta); + PlayerMovement.update(delta); + PlayerVelocity.update(delta); + } +} diff --git a/src/main/java/ity/opencraft/physics/PlayerMovement.java b/src/main/java/ity/opencraft/physics/PlayerMovement.java new file mode 100644 index 0000000..33c1d43 --- /dev/null +++ b/src/main/java/ity/opencraft/physics/PlayerMovement.java @@ -0,0 +1,166 @@ +package ity.opencraft.physics; + +import ity.opencraft.client.ViewportCamera; +import ity.opencraft.io.Screen; +import ity.opencraft.io.Window; +import ity.opencraft.render.Renderer; +import ity.opencraft.world.Blocks; +import ity.opencraft.world.Chunk; +import ity.opencraft.io.Windows; +import ity.opencraft.world.World; +import org.joml.*; +import org.lwjgl.glfw.GLFW; + +import java.lang.Math; + +// TODO clean up PlayerMovement, it handles too much +public class PlayerMovement { + private static final float movSpeed = 0.8f; + private static final float jumpSpeed = 2.5f; + private static final float rotSpeed = 0.1f; + private static Vector2f lastMouse; + private static Vector3i playerChunk = new Vector3i(); + + private static boolean keyLeftIsDown = false; + private static boolean keyRightIsDown = false; + + public static boolean moveTo(Vector3f dest) { + if(true || !World.getBlockAt((int)(dest.x+0.5), (int)(dest.y-1.0), (int)(dest.z+0.5)).isSolid() && !World.getBlockAt((int)(dest.x+0.5), (int)(dest.y), (int)(dest.z+0.5)).isSolid()) { + ViewportCamera.pos.set(dest); + return true; + } else return false; + } + + public static boolean isOnGround() { + return World.getBlockAt((int)(ViewportCamera.pos.x+0.5), (int)(ViewportCamera.pos.y-1.5), (int)(ViewportCamera.pos.z+0.5)).isSolid(); + } + + static class WTFException extends RuntimeException { + public WTFException() { + super("WTF just happened??? Did the universe break???"); + } + } + + private static Vector3i axisAlign(Vector3d in) { + if(Math.abs(in.x) > Math.abs(in.y) && Math.abs(in.x) > Math.abs(in.z)) { + return new Vector3i((int)Math.signum(in.x), 0, 0); + } else if(Math.abs(in.y) > Math.abs(in.x) && Math.abs(in.y) > Math.abs(in.z)) { + return new Vector3i(0, (int)Math.signum(in.y), 0); + } else if(Math.abs(in.z) > Math.abs(in.x) && Math.abs(in.z) > Math.abs(in.y)) { + return new Vector3i(0, 0, (int)Math.signum(in.z)); + } else return new Vector3i(0, 0, 0); + } + + // TODO: Fix the NaN problem + // Sometimes all three values in pos turn to NaN. rot untested. + /** + * Advance the player movement physics + * @param delta The delta time, in seconds. + */ + public static void update(float delta) { + Vector3i newPlayerChunk = new Vector3i((int)(ViewportCamera.pos.x+(0))/ Chunk.size, (int)(ViewportCamera.pos.y+(0))/Chunk.size, (int)(ViewportCamera.pos.z+(0))/Chunk.size); + if(!newPlayerChunk.equals(playerChunk)) { + Renderer.doChunk(); + } + playerChunk = newPlayerChunk; + Window main = Screen.get(Windows.MAIN.toString()); + if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_F11)) { + if(main.maximized) + main.restore(); + else + main.fullscreen(); + } + + if(lastMouse == null) { + lastMouse = main.getMouse().getCursorPos(); + } else { + Vector2f diff = main.getMouse().getCursorPos().sub(lastMouse); + lastMouse = main.getMouse().getCursorPos(); + float toX = diff.y*rotSpeed; + ViewportCamera.rot.x += diff.y*rotSpeed; + ViewportCamera.rot.y -= diff.x*rotSpeed; + + // Damn bratty rotation! Clamping correction needed!!! + if(ViewportCamera.rot.x < 90.01f) { + ViewportCamera.rot.x = 90.01f; + } else if (ViewportCamera.rot.x > 269.01f) { + ViewportCamera.rot.x = 269.01f; + } + if(ViewportCamera.rot.y > 360) { + ViewportCamera.rot.y = 0; + } else if(ViewportCamera.rot.y < 0) { + ViewportCamera.rot.y = 360; + } + } + Vector3f rightVector = ViewportCamera.rightVector(); + Vector3f forwardVector = ViewportCamera.forwardVector(); + Vector3f posCopy = new Vector3f(); + float mult = 1f; + if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) { + mult = 10f; + } + if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_S)) { + posCopy.add(new Vector3f(forwardVector.x, 0, forwardVector.z).normalize().mul(-delta*movSpeed*mult)); + } else if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_W)) { + posCopy.add(new Vector3f(forwardVector.x, forwardVector.y, forwardVector.z).normalize().mul(delta*movSpeed*mult)); + } if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_A)) { + posCopy.add(new Vector3f(rightVector.x, 0, rightVector.z).normalize().mul(-delta*movSpeed*mult)); + } else if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_D)) { + posCopy.add(new Vector3f(rightVector.x, 0, rightVector.z).normalize().mul(delta*movSpeed*mult)); + } +// if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) { +// posCopy.add(new Vector3f(0, 1, 0).mul(-delta*jumpSpeed)); +// } + + if(main.getMouse().isButtonPressed(GLFW.GLFW_MOUSE_BUTTON_1)) { + if(!keyLeftIsDown) { + keyLeftIsDown = true; + Vector3d tmpPos = new Vector3d(ViewportCamera.pos); + for (int i = 0; i < 1000; i++) { + Vector3i bPos = new Vector3i((int) (tmpPos.x+0.5), (int) (tmpPos.y+0.5), (int) (tmpPos.z+0.5)); + if (World.getBlockAt(bPos.x, bPos.y, bPos.z).isSolid()) { + World.setBlockAt(bPos.x, bPos.y, bPos.z, Blocks.AIR); + break; + } + tmpPos.add(new Vector3f(ViewportCamera.forwardVector()).div(100.0f)); + } + } + } else { + keyLeftIsDown = false; + } + + if(main.getMouse().isButtonPressed(GLFW.GLFW_MOUSE_BUTTON_2)) { + if(!keyRightIsDown) { + keyRightIsDown = true; + Vector3d tmpPos = new Vector3d(ViewportCamera.pos); + for (int i = 0; i < 1000; i++) { + Vector3d cent = new Vector3d(tmpPos.x+0.5, tmpPos.y+0.5, tmpPos.z+0.5); + Vector3i bPos = new Vector3i((int)cent.x, (int)cent.y, (int)cent.z); + if (World.getBlockAt(bPos.x, bPos.y, bPos.z).isSolid()) { + Vector3d dir = new Vector3d(tmpPos).sub(new Vector3d(bPos).add(new Vector3d(0.0))); + Vector3i dirI = axisAlign(dir); + Vector3i bPosO = new Vector3i(bPos).add(dirI); + if(World.getBlockAt(bPosO.x, bPosO.y, bPosO.z).isSolid()) { + System.out.println(dir); + System.out.println(dirI); + } else { + World.setBlockAt(bPosO.x, bPosO.y, bPosO.z, Blocks.STONE); + } + break; + } + tmpPos.add(new Vector3f(ViewportCamera.forwardVector()).div(100.0f)); + } + } + } else { + keyRightIsDown = false; + } + + if(main.getKeyboard().isKeyDown(GLFW.GLFW_KEY_SPACE)) { + //if(isOnGround()) { + posCopy.add(new Vector3f(0, 1, 0).mul(delta*jumpSpeed)); + //} + } + + PlayerVelocity.vel.add(posCopy); + } +} diff --git a/src/main/java/ity/opencraft/physics/PlayerVelocity.java b/src/main/java/ity/opencraft/physics/PlayerVelocity.java new file mode 100644 index 0000000..0ea0d81 --- /dev/null +++ b/src/main/java/ity/opencraft/physics/PlayerVelocity.java @@ -0,0 +1,21 @@ +package ity.opencraft.physics; + +import ity.opencraft.client.ViewportCamera; +import org.joml.Vector3f; + +public class PlayerVelocity { + public static Vector3f vel = new Vector3f(); + public static float DRAG = 15f; + public static void update(float delta) { + Vector3f offsetVec = new Vector3f(vel).div(1000.0f).mul(delta); + Vector3f cameraPosVec = new Vector3f(ViewportCamera.pos); + Vector3f moveToVec = new Vector3f(cameraPosVec).add(offsetVec); + boolean res = PlayerMovement.moveTo(moveToVec); + if(!res) { + PlayerMovement.moveTo(new Vector3f(cameraPosVec).add(new Vector3f(0.0f, offsetVec.y, 0.0f))); + } + + // https://answers.unity.com/questions/652010/how-drag-is-calculated-by-unity-engine.html + vel.mul(1-(delta/1000.0f) * DRAG); + } +} diff --git a/src/main/java/ity/opencraft/render/Renderer.java b/src/main/java/ity/opencraft/render/Renderer.java new file mode 100644 index 0000000..5e526ef --- /dev/null +++ b/src/main/java/ity/opencraft/render/Renderer.java @@ -0,0 +1,426 @@ +package ity.opencraft.render; + +import ity.opencraft.data.*; +import ity.opencraft.io.Screen; +import ity.opencraft.io.Window; +import ity.opencraft.mesh.ChunkMesher; +import ity.opencraft.world.Blocks; +import ity.opencraft.world.Chunk; +import ity.opencraft.world.ChunkReference; +import ity.opencraft.backends.Backended; +import ity.opencraft.client.ViewportCamera; +import ity.opencraft.io.Windows; +import ity.opencraft.shader.Shader; +import ity.opencraft.util.MatrixUtils; +import ity.opencraft.util.TextureLoader; +import ity.opencraft.world.World; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.lwjgl.opengl.GL11; + +import java.lang.reflect.Array; +import java.util.*; + +// TODO clean up Renderer, it handles too much +public class Renderer extends Backended { + private static IndexedMesh crosshair; + private static HashMap chunks = new HashMap<>(); + private static ArrayList toGen = new ArrayList<>(); + private static ArrayList toMesh = new ArrayList<>(); + private static ArrayList toUpload = new ArrayList<>(); + private static ArrayList toRender = new ArrayList<>(); + private static ArrayList toFree = new ArrayList<>(); + private static HashMap toDelete = new HashMap<>(); + private static ArrayList toDeletePos = new ArrayList<>(); + private static Shader colorShader; + private static Shader guiShader; + private static Renderer instance; + private static Texture atlas; + private static final BlockMeshPool pool = new BlockMeshPool(); + + public static ArrayList worldGenWorkers = new ArrayList<>(); + + private static HashMap perfTimes = new HashMap<>(); + private static long frameTime; + + // TODO: Renderer should not be backend-dependant just for clearing the screen. + public void clearScreen() { + ((Renderer)this.backend).clearScreen(); + } + public void depthTest() { + ((Renderer)this.backend).depthTest(); + } + + private static final int renderDistance = 4; + private static final int renderDistanceY = 4; + + public static void remeshChunk(int x, int y, int z) { + Vector3i vPos = new Vector3i(x, y, z); + ChunkReference chunk = chunks.get(vPos); + if(chunk == null || chunk.getMeshHandle() == null) return; + chunk.getMeshHandle().free(); + ArrayList pos = new ArrayList<>(); + ArrayList norms = new ArrayList<>(); + ArrayList texs = new ArrayList<>(); + ArrayList light = new ArrayList<>(); + ArrayList inds = new ArrayList<>(); + ChunkMesher.meshChunkWithSolid(pos, norms, texs, inds, light, vPos); + BlockMeshHandle mesh = pool.make(new BlockMeshData(pos, norms, texs, light, inds)); + chunk.setMeshHandle(mesh); + chunk.setMeshData(mesh.data); + ArrayList pos2 = new ArrayList<>(); + ArrayList norms2 = new ArrayList<>(); + ArrayList texs2 = new ArrayList<>(); + ArrayList light2 = new ArrayList<>(); + ArrayList inds2 = new ArrayList<>(); + ChunkMesher.meshChunkWithBlend(pos2, norms2, texs2, inds2, light2, vPos); + BlockMeshHandle mesh2 = pool.make(new BlockMeshData(pos2, norms2, texs2, light2, inds2)); + chunk.setMeshHandle2(mesh2); + chunk.setMeshData2(mesh2.data); + toDeletePos.remove(vPos); + toDelete.remove(vPos); + for(ChunkReference chk : toRender) { + if(chk.getPos().equals(vPos)) + return; + } + toRender.add(chunk); + } + + // TODO clean up + public static void doChunk() { + ArrayList toGenNew = new ArrayList<>(); + // TODO do in a circle around the player starting from the center, instead of in strips. + for(int i = 0; i <= renderDistance*2; i++) { + for(int j = renderDistanceY*2; j >= 0 ; j--) { + for (int k = 0; k <= renderDistance*2; k++) { + Vector3i vPos = new Vector3i(((int)(ViewportCamera.pos.x+(0))/Chunk.size)+(i*((i%2)*2-1)/2), + ((int)(ViewportCamera.pos.y+(0))/Chunk.size)+(j*((j%2)*2-1)/2), + ((int)(ViewportCamera.pos.z+(0))/Chunk.size)+(k*((k%2)*2-1)/2)); + if(vPos.x < 0 || vPos.y < 0|| vPos.z < 0) continue; + if(new Vector3f(vPos).sub(new Vector3f(ViewportCamera.pos).div(Chunk.size)).length() > renderDistance) continue; + toGenNew.add(vPos); + } + } + } + for(Vector3i vec : toGenNew) { + if(!toGen.contains(vec)) { + toDeletePos.remove(vec); + toDelete.remove(vec); + if(!chunks.containsKey(vec)) toGen.add(vec); + } + } +// for(Vector3i vec : toGen) { +// if(!toGenNew.contains(vec)) { +// toDeletePos.add(vec); +// } +// } + for(Map.Entry chunkEntry : World.chunks.entrySet()) { + if(!toGenNew.contains(chunkEntry.getKey()) && !toDeletePos.contains(chunkEntry.getKey())) { + Chunk chunk = chunkEntry.getValue(); + chunks.put(chunk.pos, World.getChunkAt(chunk.pos)); + toDeletePos.add(chunk.pos); + } + } + } + + /** + * Initialize the Renderer. Requires everything else to be ready. + */ + public static void init() { + instance = new Renderer(); + instance.depthTest(); + for(int i = 0; i < 1; i++) { + Thread worker = new Thread(() -> { + while (true) { + beginPerf("worldgen"); + worldGenUpdate(); + long taken = endPerf("worldgen") / 1000000; + long max = 8; + long toSleep = Math.max(max - taken, 0); + try { + Thread.sleep(toSleep); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }, "WorldGen worker "+i); + worldGenWorkers.add(worker); + } + crosshair = new IndexedMesh(); + crosshair.init(); + Window win = Screen.get(Windows.MAIN.toString()); + float asp = (float)win.height/(float)win.width; + crosshair.upload(new float[] { + 0.05f/asp, 0.05f, 0.0f, // top right + 0.05f/asp, -0.05f, 0.0f, // bottom right + -0.05f/asp, -0.05f, 0.0f, // bottom left + -0.05f/asp, 0.05f, 0.0f // top left + }, new int[] { + 3, 1, 0, // first triangle + 3, 2, 1 // second triangle + }); + atlas = TextureLoader.load("atlas.png"); + Vector3i startPlayerPos = new Vector3i(World.sizeXZ/2,60/Chunk.size,World.sizeXZ/2); + for(int i = startPlayerPos.x; i <= startPlayerPos.x+(renderDistance*2); i++) { + for(int j = startPlayerPos.y; j <= startPlayerPos.y+(renderDistanceY*2); j++) { + for (int k = startPlayerPos.z; k <= startPlayerPos.z+(renderDistance*2); k++) { + if(j < 0) continue; + Vector3i vPos = new Vector3i((i/2*((i%2)*2-1)), (j/2*((j%2)*2-1)), (k/2*((k%2)*2-1))); + if(new Vector3f(startPlayerPos).sub(new Vector3f(ViewportCamera.pos).div(Chunk.size)).length() > renderDistance) continue; + toGen.add(vPos); + } + } + } + + colorShader = new Shader(); + colorShader.init("color.vs", "color.fs"); + guiShader = new Shader(); + guiShader.init("gui.vs", "gui.fs"); + ViewportCamera.pos.set(World.sizeXZ*Chunk.size/2+Chunk.size/2,80+128-70,World.sizeXZ*Chunk.size/2+Chunk.size/2); + + for(Thread worker : worldGenWorkers) { + worker.start(); + } + } + + private static synchronized void worldGenUpdate() { + if(toGen.isEmpty()) { + System.out.println("Possible deadlock"); + return; + } + Vector3i toGenPos = toGen.remove(0); + if(toDelete.remove(toGenPos) != null) return; + if(toDeletePos.remove(toGenPos)) return; + ChunkReference chunk = World.getChunkAt(toGenPos.x, toGenPos.y, toGenPos.z); + if(toRender.contains(chunk)) { + // TODO fix + return; + //throw new IllegalStateException("Trying to generate a chunk that's being rendered"); + } + toMesh.add(chunk); + chunks.put(toGenPos, chunk); + } + + private static void meshUpdate() { + beginPerf("premesh"); + beginPerf("premesh.1"); + if(toMesh.isEmpty()) return; + endPerf("premesh.1"); + beginPerf("premesh.2"); + ChunkReference toMeshChunk = toMesh.remove(0); + if(!toMeshChunk.isChunkGenerated()) + System.out.println("Generating a chunk that should've been generated already."); + endPerf("premesh.2"); + beginPerf("premesh.3"); + if(toRender.contains(toMeshChunk)) { + throw new IllegalStateException("Trying to mesh a chunk that's being rendered"); + } + endPerf("premesh.3"); + //if(toDelete.containsKey(toMeshChunk.pos)) return; + beginPerf("premesh.4"); + BlockMeshData meshData = toMeshChunk.getMeshData(); + endPerf("premesh.4"); + beginPerf("premesh.5"); + if(meshData != null) { + beginPerf("premesh.5.1"); + toUpload.add(toMeshChunk); + endPerf("premesh.5.1"); + return; + } + endPerf("premesh.5"); + endPerf("premesh"); + beginPerf("solidmesh"); + ArrayList pos = new ArrayList<>(); + ArrayList norms = new ArrayList<>(); + ArrayList texs = new ArrayList<>(); + ArrayList light = new ArrayList<>(); + ArrayList inds = new ArrayList<>(); + ChunkMesher.meshChunkWithSolid(pos, norms, texs, inds, light, toMeshChunk.getPos()); + toMeshChunk.setMeshData(new BlockMeshData(pos, norms, texs, light, inds)); + endPerf("solidmesh"); + beginPerf("blendmesh"); + ArrayList pos2 = new ArrayList<>(); + ArrayList norms2 = new ArrayList<>(); + ArrayList texs2 = new ArrayList<>(); + ArrayList light2 = new ArrayList<>(); + ArrayList inds2 = new ArrayList<>(); + ChunkMesher.meshChunkWithBlend(pos2, norms2, texs2, inds2, light2, toMeshChunk.getPos()); + toMeshChunk.setMeshData2(new BlockMeshData(pos2, norms2, texs2, light2, inds2)); + toUpload.add(toMeshChunk); + endPerf("blendmesh"); + } + + private static void uploadUpdate() { + if(toUpload.isEmpty()) return; + ChunkReference toUploadChunk = toUpload.remove(0); + if(toRender.contains(toUploadChunk)) { + throw new IllegalStateException("Trying to upload a chunk mesh of a chunk that's being rendered"); + } + //if(toDelete.containsKey(toUploadChunk.pos)) return; + if(toUploadChunk.getMeshHandle() != null) { + toRender.add(toUploadChunk); + return; + } + toUploadChunk.setMeshHandle(pool.make(toUploadChunk.getMeshData())); + toUploadChunk.getMeshData().free(); + toUploadChunk.setMeshData(null); + toUploadChunk.setMeshHandle2(pool.make(toUploadChunk.getMeshData2())); + toUploadChunk.getMeshData2().free(); + toUploadChunk.setMeshData2(null); + // toUploadChunk.meshData = null; + // toUploadChunk.meshHandle.data = null; + toRender.add(toUploadChunk); + } + + private static void deleteUpdate() { + for(Map.Entry pair : toDelete.entrySet()) { + if(!toDeletePos.contains(pair.getKey())) + toDeletePos.add(pair.getKey()); + } + for(Vector3i vec : toDeletePos) { + ChunkReference chunk = chunks.remove(vec); + if(chunk != null) { + toUpload.remove(chunk); + if(toUpload.remove(chunk)) throw new IllegalStateException(); + toRender.remove(chunk); + if(toRender.remove(chunk)) throw new IllegalStateException(); + toMesh.remove(chunk); + if(toMesh.remove(chunk)) throw new IllegalStateException(); + toGen.remove(vec); + if(toGen.remove(vec)) throw new IllegalStateException(); + toFree.add(chunk); + } + } + } + + private static void freeUpdate() { + if(toDelete.isEmpty()) + return; + ChunkReference ref = toDelete.remove(0); + Vector3i vec = ref.getPos(); + //World.freeChunkAt(vec.x, vec.y, vec.z); + } + + private static void beginPerf(String name) { + perfTimes.put(name, System.nanoTime()); + } + + private static long endPerf(String name) { + long start = perfTimes.get(name); + long end = System.nanoTime(); + long timeElapsed = end - start; + System.out.print(name + ": "); + System.out.println(timeElapsed); + return timeElapsed; + } + + // TODO: Do not hardcode Windows.MAIN + /** + * Render a frame + * @param delta Delta Time, the time in miliseconds between the last and this frame. + */ + public static void render(long delta) { + beginPerf("frame"); + if(delta < 200) delta = 200; + for(int j = 0; j <= 1; j++) { + beginPerf("free"); + freeUpdate(); + endPerf("free"); + beginPerf("delete"); + deleteUpdate(); + endPerf("delete"); + beginPerf("mesh"); + meshUpdate(); + endPerf("mesh"); + beginPerf("upload"); + uploadUpdate(); + endPerf("upload"); + toDelete.clear(); + } +// for(int i = 0; i <= (200-delta)/10; i++) { +// new Thread(() -> { +// +// }).run(); +// } + beginPerf("framerender"); + beginPerf("prepass"); + instance.clearScreen(); + colorShader.bind(); + atlas.bind(); + colorShader.setUniform("iRes", new Vector2f(Screen.get(Windows.MAIN.toString()).width, Screen.get(Windows.MAIN.toString()).height)); + colorShader.setUniform("view", ViewportCamera.getViewportMatrix()); + colorShader.setUniform("proj", MatrixUtils.projectionMatrix(70f, ((float) Screen.get(Windows.MAIN.toString()).width) / ((float) Screen.get(Windows.MAIN.toString()).height), 0.1f, 1024)); + colorShader.setUniform("camPos", ViewportCamera.pos); + if(World.getBlockAt((int) ViewportCamera.pos.x, (int) ViewportCamera.pos.y, (int) ViewportCamera.pos.z) == Blocks.WATER) { + colorShader.setUniform("isWater", new Vector2f(1.0f, 2.5f)); + colorShader.setUniform("fogColor", new Vector3f(0.1f, 0.1f, 1.0f)); + GL11.glClearColor(0.1f, 0.1f, 1.0f, 1.0f); + } else { + colorShader.setUniform("isWater", new Vector2f(5.0f, 1.0f)); + colorShader.setUniform("fogColor", new Vector3f(0.5f, 0.85f, 1.0f)); + GL11.glClearColor(0.5f, 0.85f, 1.0f, 1.0f); + } + // TODO fix vertical fog + colorShader.setUniform("renderDistance", (float)renderDistance); + endPerf("prepass"); + beginPerf("render"); + for(int i = 0; i < 2; i++) { + for (ChunkReference chunk : toRender) { + if(i == 0) { + //beginPerf("pass1"); + chunk.getMeshHandle().mesh.bind(); + chunk.getMeshHandle().mesh.render(); + chunk.getMeshHandle().mesh.unbind(); + //endPerf("pass1"); + } else { + //beginPerf("pass2"); + chunk.getMeshHandle2().mesh.bind(); + chunk.getMeshHandle2().mesh.render(); + chunk.getMeshHandle2().mesh.unbind(); + //endPerf("pass2"); + } + } + } + endPerf("render"); + beginPerf("ui"); + colorShader.unbind(); + atlas.unbind(); + guiShader.bind(); + guiShader.setUniform("iRes", new Vector2f(Screen.get(Windows.MAIN.toString()).width, Screen.get(Windows.MAIN.toString()).height)); + if(World.getBlockAt((int) ViewportCamera.pos.x, (int) ViewportCamera.pos.y, (int) ViewportCamera.pos.z) == Blocks.WATER) { + guiShader.setUniform("blueAlpha", 0.6f); + GL11.glDisable(GL11.GL_CULL_FACE); + } + else { + guiShader.setUniform("blueAlpha", 0.0f); + GL11.glEnable(GL11.GL_CULL_FACE); + } + Window win = Screen.get(Windows.MAIN.toString()); + float asp = (float)win.width/(float)win.height; + crosshair.upload(new float[] { + 5f/asp, 5f, 0.0f, // top right + 5f/asp, -5f, 0.0f, // bottom right + -5f/asp, -5f, 0.0f, // bottom left + -5f/asp, 5f, 0.0f // top left + }, new int[] { + 3, 1, 0, // first triangle + 3, 2, 1 // second triangle + }); + crosshair.bind(); + crosshair.render(); + crosshair.unbind(); + guiShader.unbind(); + endPerf("ui"); + //System.out.println(ViewportCamera.pos.x+", "+ViewportCamera.pos.y+", "+ViewportCamera.pos.z); + endPerf("framerender"); + long thisFrameTime = endPerf("frame"); + if(frameTime == 0) + frameTime = thisFrameTime; + else { + frameTime = (frameTime + thisFrameTime) / 2; + } + System.out.println("frametime: "+frameTime/1000000f); + System.out.println("----------------------"); + } +} diff --git a/src/main/java/ity/opencraft/render/RendererGL.java b/src/main/java/ity/opencraft/render/RendererGL.java new file mode 100644 index 0000000..a879052 --- /dev/null +++ b/src/main/java/ity/opencraft/render/RendererGL.java @@ -0,0 +1,22 @@ +package ity.opencraft.render; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.lwjgl.opengl.GL11; + +@BackendImpl(Backend.GL) +public class RendererGL extends Renderer { + @Override + public void clearScreen() { + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + } + + @Override + public void depthTest() { + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glClearColor(0.5f, 0.85f, 1.0f, 1.0f); + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/shader/Shader.java b/src/main/java/ity/opencraft/shader/Shader.java new file mode 100644 index 0000000..1f0158f --- /dev/null +++ b/src/main/java/ity/opencraft/shader/Shader.java @@ -0,0 +1,86 @@ +package ity.opencraft.shader; + +import ity.opencraft.backends.Backended; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +public class Shader extends Backended { + /** + * Initialize a shader + * @param v The path to the vertex shader + * @param f The path to the fragment shader + * @return self + */ + public Shader init(String v, String f) { + return ((Shader)this.backend).init(v, f); + } + + /** + * Set a Matrix4f uniform + * @param name Name of the uniform + * @param matrix The value + */ + public void setUniform(String name, Matrix4f matrix) { + ((Shader)this.backend).setUniform(name, matrix); + } + + /** + * Set a Vector2f uniform + * @param name Name of the uniform + * @param vector The value + */ + public void setUniform(String name, Vector2f vector) { + ((Shader)this.backend).setUniform(name, vector); + } + + /** + * Set an int uniform + * @param name Name of the uniform + * @param value The value + */ + public void setUniform(String name, int value) { + ((Shader)this.backend).setUniform(name, value); + } + + /** + * Set a float uniform + * @param name Name of the uniform + * @param value The value + */ + public void setUniform(String name, float value) { + ((Shader)this.backend).setUniform(name, value);} + + /** + * Set a Vector3i uniform + * @param name Name of the uniform + * @param vector The value + */ + public void setUniform(String name, Vector3i vector) { + ((Shader)this.backend).setUniform(name, vector); + } + + /** + * Set a Vector3f uniform + * @param name Name of the uniform + * @param value The value + */ + public void setUniform(String name, Vector3f value) { + ((Shader)this.backend).setUniform(name, value); + } + + /** + * Bind/Use the shader + */ + public void bind() { + ((Shader)this.backend).bind(); + } + + /** + * Unbind/Unuse the shader + */ + public void unbind() { + ((Shader)this.backend).unbind(); + } +} diff --git a/src/main/java/ity/opencraft/shader/ShaderGL.java b/src/main/java/ity/opencraft/shader/ShaderGL.java new file mode 100644 index 0000000..5ae1684 --- /dev/null +++ b/src/main/java/ity/opencraft/shader/ShaderGL.java @@ -0,0 +1,92 @@ +package ity.opencraft.shader; + +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import ity.opencraft.util.StringLoader; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; +import org.lwjgl.system.MemoryUtil; + +import java.nio.FloatBuffer; + +import static org.lwjgl.opengl.GL20.*; +import static org.lwjgl.opengl.GL20.glGetUniformLocation; + +@BackendImpl(Backend.GL) +public class ShaderGL extends Shader { + + private int pID; + + public Shader init(String v, String f) { + pID = GL20.glCreateProgram(); + + int VID = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); + + GL20.glShaderSource(VID, StringLoader.loadResourceAsString("shaders/" + v)); + GL20.glCompileShader(VID); + if (GL20.glGetShaderi(VID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + System.err.println("Vertex Shader: " + GL20.glGetShaderInfoLog(VID)); + } + + int FID = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); + + GL20.glShaderSource(FID, StringLoader.loadResourceAsString("shaders/" + f)); + GL20.glCompileShader(FID); + if (GL20.glGetShaderi(FID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + System.err.println("Fragment Shader: " + GL20.glGetShaderInfoLog(FID)); + } + + GL20.glAttachShader(pID, VID); + GL20.glAttachShader(pID, FID); + + GL20.glLinkProgram(pID); + if (GL20.glGetProgrami(pID, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) { + System.err.println("Program Linking: " + GL20.glGetProgramInfoLog(pID)); + } + + GL20.glValidateProgram(pID); + if (GL20.glGetProgrami(pID, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) { + System.err.println("Program Validation: " + GL20.glGetProgramInfoLog(pID)); + } + + return this; + } + + public void setUniform(String name, Matrix4f matrix) { + FloatBuffer matrixB = MemoryUtil.memAllocFloat(16); + matrix.get(matrixB); + glUniformMatrix4fv(glGetUniformLocation(pID, name), false, matrixB); + MemoryUtil.memFree(matrixB); + } + + public void setUniform(String name, Vector2f vector) { + glUniform2fv(glGetUniformLocation(pID, name), new float[]{vector.x, vector.y}); + } + + public void setUniform(String name, int value) { + glUniform1iv(glGetUniformLocation(pID, name), new int[]{value}); + } + + public void setUniform(String name, float value) { + glUniform1fv(glGetUniformLocation(pID, name), new float[]{value}); + } + + public void setUniform(String name, Vector3i vector) { + glUniform3iv(glGetUniformLocation(pID, name), new int[]{vector.x, vector.y, vector.z}); + } + public void setUniform(String name, Vector3f vector) { + glUniform3fv(glGetUniformLocation(pID, name), new float[]{vector.x, vector.y, vector.z}); + } + + public void bind() { + glUseProgram(pID); + } + + public void unbind() { + glUseProgram(0); + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/util/MatrixUtils.java b/src/main/java/ity/opencraft/util/MatrixUtils.java new file mode 100644 index 0000000..5ee5b9f --- /dev/null +++ b/src/main/java/ity/opencraft/util/MatrixUtils.java @@ -0,0 +1,43 @@ +package ity.opencraft.util; + +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class MatrixUtils { + + /** + * make a model matrix + * @param translation the position + * @param rotation the rotation + * @param scale the scale + * @return the matrix + */ + public static Matrix4f transformationMatrix(Vector3f translation, Vector3f rotation, Vector3f scale) { + rotation = rotation.mul((float) (Math.PI / 180.0f), new Vector3f()); + Matrix4f matrix = new Matrix4f(); + return matrix.translate(translation).rotateXYZ(rotation).scale(scale); + } + + /** + * make a projection matrix + * @param fov the fov + * @param aspectRatio the aspect ratio (w/h) + * @param zNear the near plane + * @param zFar the far plane + * @return the matrix + */ + public static Matrix4f projectionMatrix(float fov, float aspectRatio, float zNear, float zFar) { + return new Matrix4f().perspective((float) Math.toRadians(fov), aspectRatio, zNear, zFar); + } + + /** + * make a view matrix + * @param position the position + * @param rotation the rotation + * @return the matrix + */ + public static Matrix4f viewMatrix(Vector3f position, Vector3f rotation) { + rotation = rotation.mul((float) (Math.PI / 180.0f), new Vector3f()); + return new Matrix4f().rotateXYZ(rotation).translate(position.negate(new Vector3f())); + } +} diff --git a/src/main/java/ity/opencraft/util/OctavedNoise.java b/src/main/java/ity/opencraft/util/OctavedNoise.java new file mode 100644 index 0000000..d4a65c1 --- /dev/null +++ b/src/main/java/ity/opencraft/util/OctavedNoise.java @@ -0,0 +1,14 @@ +package ity.opencraft.util; + +public class OctavedNoise { + public static double octavedNoise(double x, double y, double z, int octaves, double strength, double size) { + double result = 0; + double maxVal = 0; + for(int i = 0; i < octaves; i++) { + result += SimplexNoise.noise(x*size, y*size, z*size) * strength; + maxVal += strength; + size*=2; + } + return result/maxVal; + } +} diff --git a/src/main/java/ity/opencraft/util/SimplexNoise.java b/src/main/java/ity/opencraft/util/SimplexNoise.java new file mode 100644 index 0000000..5cbdf09 --- /dev/null +++ b/src/main/java/ity/opencraft/util/SimplexNoise.java @@ -0,0 +1,358 @@ +package ity.opencraft.util; + +/* + * A speed-improved simplex noise algorithm for 2D, 3D and 4D in Java. + * + * Based on example code by Stefan Gustavson (stegu@itn.liu.se). + * Optimisations by Peter Eastman (peastman@drizzle.stanford.edu). + * Better rank ordering method by Stefan Gustavson in 2012. + * + * This could be speeded up even further, but it's useful as it is. + * + * Version 2012-03-09 + * + * This code was placed in the public domain by its original author, + * Stefan Gustavson. You may use it as you see fit, but + * attribution is appreciated. + * + */ + +public class SimplexNoise { // Simplex noise in 2D, 3D and 4D + private static Grad grad3[] = {new Grad(1,1,0),new Grad(-1,1,0),new Grad(1,-1,0),new Grad(-1,-1,0), + new Grad(1,0,1),new Grad(-1,0,1),new Grad(1,0,-1),new Grad(-1,0,-1), + new Grad(0,1,1),new Grad(0,-1,1),new Grad(0,1,-1),new Grad(0,-1,-1)}; + + private static Grad grad4[]= {new Grad(0,1,1,1),new Grad(0,1,1,-1),new Grad(0,1,-1,1),new Grad(0,1,-1,-1), + new Grad(0,-1,1,1),new Grad(0,-1,1,-1),new Grad(0,-1,-1,1),new Grad(0,-1,-1,-1), + new Grad(1,0,1,1),new Grad(1,0,1,-1),new Grad(1,0,-1,1),new Grad(1,0,-1,-1), + new Grad(-1,0,1,1),new Grad(-1,0,1,-1),new Grad(-1,0,-1,1),new Grad(-1,0,-1,-1), + new Grad(1,1,0,1),new Grad(1,1,0,-1),new Grad(1,-1,0,1),new Grad(1,-1,0,-1), + new Grad(-1,1,0,1),new Grad(-1,1,0,-1),new Grad(-1,-1,0,1),new Grad(-1,-1,0,-1), + new Grad(1,1,1,0),new Grad(1,1,-1,0),new Grad(1,-1,1,0),new Grad(1,-1,-1,0), + new Grad(-1,1,1,0),new Grad(-1,1,-1,0),new Grad(-1,-1,1,0),new Grad(-1,-1,-1,0)}; + + private static short p[] = {151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180}; + // To remove the need for index wrapping, double the permutation table length + private static short perm[] = new short[512]; + private static short permMod12[] = new short[512]; + static { + for(int i=0; i<512; i++) + { + perm[i]=p[i & 255]; + permMod12[i] = (short)(perm[i] % 12); + } + } + + // Skewing and unskewing factors for 2, 3, and 4 dimensions + private static final double F2 = 0.5*(Math.sqrt(3.0)-1.0); + private static final double G2 = (3.0-Math.sqrt(3.0))/6.0; + private static final double F3 = 1.0/3.0; + private static final double G3 = 1.0/6.0; + private static final double F4 = (Math.sqrt(5.0)-1.0)/4.0; + private static final double G4 = (5.0-Math.sqrt(5.0))/20.0; + + // This method is a *lot* faster than using (int)Math.floor(x) + private static int fastfloor(double x) { + int xi = (int)x; + return xy0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else {i1=0; j1=1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 - 1.0 + 2.0 * G2; + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + int gi0 = permMod12[ii+perm[jj]]; + int gi1 = permMod12[ii+i1+perm[jj+j1]]; + int gi2 = permMod12[ii+1+perm[jj+1]]; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0*x0-y0*y0; + if(t0<0) n0 = 0.0; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + double t1 = 0.5 - x1*x1-y1*y1; + if(t1<0) n1 = 0.0; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2*x2-y2*y2; + if(t2<0) n2 = 0.0; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + + // 3D simplex noise + public static double noise(double xin, double yin, double zin) { + double n0, n1, n2, n3; // Noise contributions from the four corners + // Skew the input space to determine which simplex cell we're in + double s = (xin+yin+zin)*F3; // Very nice and simple skew factor for 3D + int i = fastfloor(xin+s); + int j = fastfloor(yin+s); + int k = fastfloor(zin+s); + double t = (i+j+k)*G3; + double X0 = i-t; // Unskew the cell origin back to (x,y,z) space + double Y0 = j-t; + double Z0 = k-t; + double x0 = xin-X0; // The x,y,z distances from the cell origin + double y0 = yin-Y0; + double z0 = zin-Z0; + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if(x0>=y0) { + if(y0>=z0) + { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order + else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order + else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order + } + else { // x0 y0) rankx++; else ranky++; + if(x0 > z0) rankx++; else rankz++; + if(x0 > w0) rankx++; else rankw++; + if(y0 > z0) ranky++; else rankz++; + if(y0 > w0) ranky++; else rankw++; + if(z0 > w0) rankz++; else rankw++; + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = ranky >= 3 ? 1 : 0; + k1 = rankz >= 3 ? 1 : 0; + l1 = rankw >= 3 ? 1 : 0; + // Rank 2 denotes the second largest coordinate. + i2 = rankx >= 2 ? 1 : 0; + j2 = ranky >= 2 ? 1 : 0; + k2 = rankz >= 2 ? 1 : 0; + l2 = rankw >= 2 ? 1 : 0; + // Rank 1 denotes the second smallest coordinate. + i3 = rankx >= 1 ? 1 : 0; + j3 = ranky >= 1 ? 1 : 0; + k3 = rankz >= 1 ? 1 : 0; + l3 = rankw >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to compute that. + double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + double x2 = x0 - i2 + 2.0*G4; // Offsets for third corner in (x,y,z,w) coords + double y2 = y0 - j2 + 2.0*G4; + double z2 = z0 - k2 + 2.0*G4; + double w2 = w0 - l2 + 2.0*G4; + double x3 = x0 - i3 + 3.0*G4; // Offsets for fourth corner in (x,y,z,w) coords + double y3 = y0 - j3 + 3.0*G4; + double z3 = z0 - k3 + 3.0*G4; + double w3 = w0 - l3 + 3.0*G4; + double x4 = x0 - 1.0 + 4.0*G4; // Offsets for last corner in (x,y,z,w) coords + double y4 = y0 - 1.0 + 4.0*G4; + double z4 = z0 - 1.0 + 4.0*G4; + double w4 = w0 - 1.0 + 4.0*G4; + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 255; + int jj = j & 255; + int kk = k & 255; + int ll = l & 255; + int gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32; + int gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32; + int gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32; + int gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32; + int gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32; + // Calculate the contribution from the five corners + double t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0; + if(t0<0) n0 = 0.0; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + double t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1; + if(t1<0) n1 = 0.0; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + double t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2; + if(t2<0) n2 = 0.0; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + double t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3; + if(t3<0) n3 = 0.0; + else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + double t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4; + if(t4<0) n4 = 0.0; + else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } + + // Inner class to speed upp gradient computations + // (array access is a lot slower than member access) + private static class Grad + { + double x, y, z, w; + + Grad(double x, double y, double z) + { + this.x = x; + this.y = y; + this.z = z; + } + + Grad(double x, double y, double z, double w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/util/StringLoader.java b/src/main/java/ity/opencraft/util/StringLoader.java new file mode 100644 index 0000000..69e68dd --- /dev/null +++ b/src/main/java/ity/opencraft/util/StringLoader.java @@ -0,0 +1,27 @@ +package ity.opencraft.util; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +public class StringLoader { + + /** + * make a stream out of a path + * @param path the path to the resource + * @return the stream + */ + public static InputStream loadResourceAsStream(String path) { + return StringLoader.class.getClassLoader().getResourceAsStream(path); + } + + /** + * read a text file from a path + * @param path path to the text file + * @return the contents of the text file as a string + */ + public static String loadResourceAsString(String path) { + return new BufferedReader(new InputStreamReader(loadResourceAsStream(path))).lines().collect(Collectors.joining("\n")); + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/util/TextureLoader.java b/src/main/java/ity/opencraft/util/TextureLoader.java new file mode 100644 index 0000000..3ecd9e2 --- /dev/null +++ b/src/main/java/ity/opencraft/util/TextureLoader.java @@ -0,0 +1,47 @@ +package ity.opencraft.util; + +import ity.opencraft.backends.Backended; +import ity.opencraft.data.Texture; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.lwjgl.BufferUtils.createByteBuffer; + +public class TextureLoader extends Backended { + private static TextureLoader instance; + + /** + * Initialize. Has to be called before load can be called. + */ + public static void init() { + instance = new TextureLoader(); + } + + /** + * Load a texture + * @param name The path to the texture, relative to the textures/ directory in resources. + * @return The loaded texture + */ + public static Texture load(String name) { + return instance.loadTexture(name); + } + + + protected Texture loadTexture(String name) { + return ((TextureLoader)backend).loadTexture(name); + } + + /** + * Load a resource into a buffer + * @param resource The path to the resource + * @param bufferSize The size of the buffer + * @return The buffer + */ + public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException { + ByteBuffer buffer = createByteBuffer(bufferSize); + buffer.put(StringLoader.loadResourceAsStream(resource).readAllBytes()); + buffer.flip(); + return buffer; + } +} \ No newline at end of file diff --git a/src/main/java/ity/opencraft/util/TextureLoaderGL.java b/src/main/java/ity/opencraft/util/TextureLoaderGL.java new file mode 100644 index 0000000..75294bb --- /dev/null +++ b/src/main/java/ity/opencraft/util/TextureLoaderGL.java @@ -0,0 +1,83 @@ +package ity.opencraft.util; + +import ity.opencraft.data.Texture; +import ity.opencraft.backends.Backend; +import ity.opencraft.backends.BackendImpl; +import org.lwjgl.system.MemoryStack; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.stb.STBImage.*; +import static org.lwjgl.system.MemoryStack.stackPush; + +@BackendImpl(Backend.GL) +public class TextureLoaderGL extends TextureLoader { + @Override + public Texture loadTexture(String name) { + ByteBuffer image; + + ByteBuffer imageBuffer; + try { + imageBuffer = ioResourceToByteBuffer("textures/" + name, 4*(16*4)*(16*4)*(16*4)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try (MemoryStack stack = stackPush()) { + IntBuffer w = stack.mallocInt(1); + IntBuffer h = stack.mallocInt(1); + IntBuffer comp = stack.mallocInt(1); + + if (!stbi_info_from_memory(imageBuffer, w, h, comp)) { + throw new RuntimeException("Failed to read image information: " + stbi_failure_reason()); + } else { + //System.out.println("OK with reason: " + stbi_failure_reason()); + } + +// System.out.println("Image width: " + w.get(0)); +// System.out.println("Image height: " + h.get(0)); +// System.out.println("Image components: " + comp.get(0)); +// System.out.println("Image HDR: " + stbi_is_hdr_from_memory(imageBuffer)); + + // Decode the image + image = stbi_load_from_memory(imageBuffer, w, h, comp, 0); + if (image == null) { + throw new RuntimeException("Failed to load image: " + stbi_failure_reason()); + } + + Texture tex = new Texture(); + + tex.w = w.get(0); + tex.h = h.get(0); + tex.c = comp.get(0); + + int texID = glGenTextures(); + + glBindTexture(GL_TEXTURE_2D, texID); + + int format = 0; + + if(comp.get(0) == 3) + format = GL_RGB; + else if(comp.get(0) == 4) + format = GL_RGBA; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, 0, format, w.get(0), h.get(0), 0, format, GL_UNSIGNED_BYTE, image); + + glBindTexture(GL_TEXTURE_2D, 0); + + stbi_image_free(image); + + tex.id = texID; + + return tex; + } + } +} diff --git a/src/main/java/ity/opencraft/world/Block.java b/src/main/java/ity/opencraft/world/Block.java new file mode 100644 index 0000000..b236275 --- /dev/null +++ b/src/main/java/ity/opencraft/world/Block.java @@ -0,0 +1,42 @@ +package ity.opencraft.world; + +import ity.opencraft.mesh.BlockModelType; +import org.joml.Vector3i; + +public abstract class Block { + /** + * Check if the block is transparent. Used to determine whether other block faces are visible through this block or not. + * Examples include Air, Glass, Water... + * @return Whether this block is transparent + */ + public abstract boolean isTransparent(Block sideBlock); + + /** + * Get the texture index of the block. Texture indices are counted from left to right and then from top to bottom. + * @return The texture index of the block + */ + public abstract int textureIndex(BlockSide side, Vector3i pos); + + /** + * Check if the block is visible. An invisible block does not have any mesh or model, and is thus not rendered at all. + * If you override this to return false, override isTransparent to return true. + * @return Whether this block is transparent + */ + public boolean visible() { + return true; + } + + public boolean textureRotate(BlockSide side) { + return false; + }; + + public BlockModelType modelType() { + return BlockModelType.CUBE; + } + + public abstract boolean isSolid(); + + public boolean isBlend() { + return false; + } +} diff --git a/src/main/java/ity/opencraft/world/BlockSide.java b/src/main/java/ity/opencraft/world/BlockSide.java new file mode 100644 index 0000000..2378f99 --- /dev/null +++ b/src/main/java/ity/opencraft/world/BlockSide.java @@ -0,0 +1,6 @@ +package ity.opencraft.world; + +public enum BlockSide { + TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK, + CROSS_1, CROSS_2 +} diff --git a/src/main/java/ity/opencraft/world/Blocks.java b/src/main/java/ity/opencraft/world/Blocks.java new file mode 100644 index 0000000..c2982ca --- /dev/null +++ b/src/main/java/ity/opencraft/world/Blocks.java @@ -0,0 +1,19 @@ +package ity.opencraft.world; + +import ity.opencraft.world.blocks.*; + +public class Blocks { + public static final BlockAir AIR = new BlockAir(); + public static final BlockStone STONE = new BlockStone(); + public static final BlockDirt DIRT = new BlockDirt(); + public static final BlockGrass GRASS = new BlockGrass(); + public static final BlockCoalOre COAL_ORE = new BlockCoalOre(); + public static final BlockTallGrass TALL_GRASS = new BlockTallGrass(); + public static final BlockDandelion DANDELION = new BlockDandelion(); + public static final BlockWoodLog WOOD_LOG = new BlockWoodLog(); + public static final BlockLeaves LEAVES = new BlockLeaves(); + + public static final BlockWater WATER = new BlockWater(); + + +} diff --git a/src/main/java/ity/opencraft/world/Chunk.java b/src/main/java/ity/opencraft/world/Chunk.java new file mode 100644 index 0000000..9293f10 --- /dev/null +++ b/src/main/java/ity/opencraft/world/Chunk.java @@ -0,0 +1,56 @@ +package ity.opencraft.world; + +import ity.opencraft.data.BlockMeshData; +import ity.opencraft.data.BlockMeshHandle; +import org.joml.Vector3i; + +public class Chunk { + public static final int size = 32; + private boolean freed = false; + + public Vector3i pos; + public BlockMeshHandle meshHandle; + public BlockMeshData meshData; + public BlockMeshHandle meshHandle2; + public BlockMeshData meshData2; + + private Block[] blocks = new Block[size*size*size]; + //private float[] lights = new float[size*size*size]; + + + public Chunk(Vector3i pos) { + this.pos = pos; + } + + public Block getBlockAt(int x, int y, int z) { + return blocks[x*size*size+y*size+z]; + } + + public void setBlockAt(int x, int y, int z, Block block) { + blocks[x*size*size+y*size+z] = block; + } + + public float getLightAt(int x, int y, int z) { + //return lights[x*size*size+y*size+z]; + return 0; + } + + public void setLightAt(int x, int y, int z, float level) { + //lights[x*size*size+y*size+z] = level; + } + + public void free() { + if(!freed) { + blocks = null; + if(meshHandle != null) + meshHandle.free(); + meshHandle = null; + if(meshHandle2 != null) + meshHandle2.free(); + meshHandle2 = null; + freed = true; + } else { + throw new IllegalStateException("Double free"); + } + } +} diff --git a/src/main/java/ity/opencraft/world/ChunkReference.java b/src/main/java/ity/opencraft/world/ChunkReference.java new file mode 100644 index 0000000..faf182e --- /dev/null +++ b/src/main/java/ity/opencraft/world/ChunkReference.java @@ -0,0 +1,51 @@ +package ity.opencraft.world; + +import ity.opencraft.data.BlockMeshData; +import ity.opencraft.data.BlockMeshHandle; +import org.joml.Vector3i; + +public class ChunkReference { + private Vector3i pos; + public boolean isChunkPresent = true; + // TODO refactor, package-private is ugly + ChunkReference(int x, int y, int z) { + this.pos = new Vector3i(x, y, z); + } + + public BlockMeshHandle getMeshHandle() { + return World.getChunkAtRaw(pos).meshHandle; + } + public BlockMeshHandle getMeshHandle2() { + return World.getChunkAtRaw(pos).meshHandle2; + } + + public void setMeshHandle(BlockMeshHandle handle) { + World.getChunkAtRaw(pos).meshHandle = handle; + } + + public BlockMeshData getMeshData() { + return World.getChunkAtRaw(pos).meshData; + } + + public void setMeshData(BlockMeshData handle) { + World.getChunkAtRaw(pos).meshData = handle; + } + public BlockMeshData getMeshData2() { + return World.getChunkAtRaw(pos).meshData2; + } + + public void setMeshData2(BlockMeshData handle) { + World.getChunkAtRaw(pos).meshData2 = handle; + } + public void setMeshHandle2(BlockMeshHandle handle) { + World.getChunkAtRaw(pos).meshHandle2 = handle; + } + + public boolean isChunkGenerated() { + return World.isChunkAt(pos); + } + + public Vector3i getPos() { + return this.pos; + } +} diff --git a/src/main/java/ity/opencraft/world/World.java b/src/main/java/ity/opencraft/world/World.java new file mode 100644 index 0000000..fde2b4b --- /dev/null +++ b/src/main/java/ity/opencraft/world/World.java @@ -0,0 +1,340 @@ +package ity.opencraft.world; + +import ity.opencraft.render.Renderer; +import ity.opencraft.util.OctavedNoise; +import ity.opencraft.world.blocks.BlockLeaves; +import org.joml.Vector3i; + +import java.util.HashMap; +import java.util.Random; + +// TODO negative coordinates +// As Y grows, it goes downwards instead of upwards. +public class World { + /** + * The size of the world in the X and Z direction; The horizontal size. + */ + public static final int sizeXZ = 1024; + public static final int sizeY = 64; + + private static HashMap perfTimes = new HashMap<>(); + + public static HashMap chunks = new HashMap<>(); + private static HashMap chunkRefs = new HashMap<>(); + + + private static Random rand = new Random(); + + private static final int mult = 128; + private static final double multScale = 64.0; + private static final int height = -128; + + private static int random(int x, int y) { + Random pRand = new Random(((x * 435747689L) & 438) ^ y); + + return pRand.nextInt(10000); + } + + private static void beginPerf(String name) { + perfTimes.put(name, System.nanoTime()); + } + + private static void endPerf(String name) { + long start = perfTimes.get(name); + long end = System.nanoTime(); + long timeElapsed = end - start; + System.out.print(name + ": "); + System.out.println(timeElapsed); + } + + private static boolean isSkyVisible(int x, int y, int z) { + while(isChunkAt(x/Chunk.size,y/Chunk.size,z/Chunk.size)) { + if(!World.getBlockAt(x, y, z).isTransparent(Blocks.AIR)) return false; + y += 1; + } + return true; + } + + private static Block makeBlockAt(int x, int y, int z) { + + double mult1 = (OctavedNoise.octavedNoise((x+63156) / 4096f, 0, (z+6533) / 4096f, 1, 32, 0.4f) + 1) * 0.5f; + double mult = (OctavedNoise.octavedNoise((x+9276) / 256f, 0, (z+4317) / 256f, 1, 32, 0.4f) + 1) * 32f; + + int oY = y; + y += height*mult1; + + //System.out.println(mult); + + double noise = OctavedNoise.octavedNoise(x / multScale, y / multScale, z / multScale, 3, 32, 0.4f) * mult; + double noiseBelow = OctavedNoise.octavedNoise(x / multScale, (y - 1) / multScale, z / multScale, 3, 32, 0.4f) * mult; + + /* + double treeNoise = random(x, z); + if(treeNoise > 9908) { + if (!(noiseBelow < (y - 1) - mult) && noise < (y - 1) - (mult - 1)) return Blocks.WOOD_LOG; + + double noiseBelow2 = OctavedNoise.octavedNoise(x / multScale, (y - 2) / multScale, z / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow2 < (y - 2) - mult) && noiseBelow < (y - 2) - (mult - 2)) return Blocks.WOOD_LOG; + + double noiseBelow3 = OctavedNoise.octavedNoise(x / multScale, (y - 3) / multScale, z / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow3 < (y - 3) - mult) && noiseBelow2 < (y - 3) - (mult - 3)) return Blocks.LEAVES; + } + + double treeNoiseXP = random(x+1, z); + if(treeNoiseXP > 9908) { + double noiseBelow2XP = OctavedNoise.octavedNoise((x + 1) / multScale, (y - 2) / multScale, z / multScale, 3, 32, 0.4f) * mult; + double noiseBelowXP = OctavedNoise.octavedNoise((x + 1) / multScale, (y - 1) / multScale, z / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow2XP < (y - 2) - mult) && noiseBelowXP < (y - 2) - (mult - 2)) return Blocks.LEAVES; + } + + double treeNoiseXN = random(x-1, z); + if(treeNoiseXN > 9908) { + double noiseBelow2XN = OctavedNoise.octavedNoise((x - 1) / multScale, (y - 2) / multScale, z / multScale, 3, 32, 0.4f) * mult; + double noiseBelowXN = OctavedNoise.octavedNoise((x - 1) / multScale, (y - 1) / multScale, z / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow2XN < (y - 2) - mult) && noiseBelowXN < (y - 2) - (mult - 2)) return Blocks.LEAVES; + } + + double treeNoiseZP = random(x, z+1); + if(treeNoiseZP > 9908) { + double noiseBelow2ZP = OctavedNoise.octavedNoise(x / multScale, (y - 2) / multScale, (z + 1) / multScale, 3, 32, 0.4f) * mult; + double noiseBelowZP = OctavedNoise.octavedNoise(x / multScale, (y - 1) / multScale, (z + 1) / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow2ZP < (y - 2) - mult) && noiseBelowZP < (y - 2) - (mult - 2)) return Blocks.LEAVES; + } + + double treeNoiseZN = random(x, z-1); + if(treeNoiseZN > 9908) { + double noiseBelow2ZN = OctavedNoise.octavedNoise(x / multScale, (y - 2) / multScale, (z - 1) / multScale, 3, 32, 0.4f) * mult; + double noiseBelowZN = OctavedNoise.octavedNoise(x / multScale, (y - 1) / multScale, (z - 1) / multScale, 3, 32, 0.4f) * mult; + if (!(noiseBelow2ZN < (y - 2) - mult) && noiseBelowZN < (y - 2) - (mult - 2)) return Blocks.LEAVES; + } + */ + + double noiseAbove = OctavedNoise.octavedNoise(x / multScale, (y + 1) / multScale, z / multScale, 3, 32, 0.4f) * mult; + + int waterLevel = 104; + + if(!(noiseBelow<(y-1)-mult) && noise<(y-1)-(mult-1) && rand.nextInt(5) == 0) { + if(oY <= waterLevel-1) + return Blocks.WATER; + else + return Blocks.TALL_GRASS; + } + if(!(noiseBelow<(y-1)-mult) && noise<(y-1)-(mult-1) && rand.nextInt(20) == 0) { + if(oY <= waterLevel-1) + return Blocks.WATER; + else + return Blocks.DANDELION; + } + if(noise 8; + case LEFT, RIGHT, FRONT, BACK -> 7; + default -> -1; + }; + } + + @Override + public boolean isSolid() { + return true; + } +} diff --git a/src/main/resources/shaders/color.fs b/src/main/resources/shaders/color.fs new file mode 100644 index 0000000..25b8238 --- /dev/null +++ b/src/main/resources/shaders/color.fs @@ -0,0 +1,45 @@ +#version 460 + +in vec3 passNorm; +in vec2 passTexUv; +in vec3 fragPos; +in float passLight; + +out vec4 color; + +uniform sampler2D tex; +uniform vec2 iRes; +uniform vec3 camPos; +uniform float renderDistance; +uniform vec2 isWater; +uniform vec3 fogColor; + +const float eps = 0.001; + +bool eqFlt(float a, float b) { + return abs(a - b) < eps; +} + +bool eqVec(vec3 a, vec3 b) { + return eqFlt(a.x,b.x) && eqFlt(a.y,b.y) && eqFlt(a.z,b.z); +} + +const float fogThick = 1.0; +const float chunkSize = 32; + +void main() { + vec4 smp = texture(tex, passTexUv); + vec3 col = texture(tex, passTexUv).rgb * ((passLight/2.0)+0.5); + if(eqVec(passNorm, vec3(0,-1,0))) + col *= 0.5; + else if(eqVec(passNorm, vec3(1,0,0)) || eqVec(passNorm, vec3(-1,0,0))) + col *= 0.7; + else if(eqVec(passNorm, vec3(0,0,1)) || eqVec(passNorm, vec3(0,0,-1))) + col *= 0.9; + vec3 toObj = fragPos - camPos; + float dist = length(toObj)/((renderDistance/isWater.y-fogThick)*chunkSize); + //col = mix(col, vec3(0.5f, 0.85f, 1.0f), min(1.0, pow(dist, 5))); + col = mix(col, fogColor, min(1.0, pow(dist, isWater.x))); + color = vec4(col, smp.a); + if(smp.a < 0.1f) discard; +} diff --git a/src/main/resources/shaders/color.vs b/src/main/resources/shaders/color.vs new file mode 100644 index 0000000..b3ffa64 --- /dev/null +++ b/src/main/resources/shaders/color.vs @@ -0,0 +1,22 @@ +#version 460 + +in vec3 pos; +in vec3 norm; +in vec2 texUv; +in float light; + +out vec3 passNorm; +out vec2 passTexUv; +out float passLight; +out vec3 fragPos; + +uniform mat4 view; +uniform mat4 proj; + +void main() { + gl_Position = proj * view * vec4(pos, 1); + passNorm = norm; + fragPos = pos; + passTexUv = texUv; + passLight = light; +} \ No newline at end of file diff --git a/src/main/resources/shaders/gui.fs b/src/main/resources/shaders/gui.fs new file mode 100644 index 0000000..3f6226e --- /dev/null +++ b/src/main/resources/shaders/gui.fs @@ -0,0 +1,26 @@ +#version 460 + +in vec3 fragPos; + +uniform vec2 iRes; +uniform float blueAlpha; + +out vec4 color; + +const float innerRadius = 0.0040; +const float outerRadius = 0.0050; + +void main() { + vec2 uv = gl_FragCoord.xy/iRes*vec2(2.0)-vec2(1.0); + uv.x *= iRes.x / iRes.y; + float dist = length(vec2(0,0)-uv); + color = vec4(vec3(1.), 1.); + if(dist > innerRadius) { + if(dist > outerRadius) { + color = vec4(0.1, 0.1, 1.0, blueAlpha); + } + else { + color = vec4(vec3(1.), 1-(dist-innerRadius)*(1/(outerRadius-innerRadius))); + } + } +} \ No newline at end of file diff --git a/src/main/resources/shaders/gui.vs b/src/main/resources/shaders/gui.vs new file mode 100644 index 0000000..b17661e --- /dev/null +++ b/src/main/resources/shaders/gui.vs @@ -0,0 +1,9 @@ +#version 460 + +in vec3 pos; + +out vec3 fragPos; + +void main() { + gl_Position = vec4(pos, 1); +} \ No newline at end of file diff --git a/src/main/resources/textures/atlas.aseprite b/src/main/resources/textures/atlas.aseprite new file mode 100644 index 0000000..2364ba5 Binary files /dev/null and b/src/main/resources/textures/atlas.aseprite differ diff --git a/src/main/resources/textures/atlas.png b/src/main/resources/textures/atlas.png new file mode 100644 index 0000000..1ca1469 Binary files /dev/null and b/src/main/resources/textures/atlas.png differ