diff --git a/.gitignore b/.gitignore deleted file mode 100644 index aace89d..0000000 --- a/.gitignore +++ /dev/null @@ -1,55 +0,0 @@ -.gradle -gradle/ -build/ - - -### IntelliJ IDEA ### -/.idea/workspace.xml -/.idea/usage.statistics.xml -/.idea/uiDesigner.xml -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### OTHER ### -/wiki -.DS_Store -*.bat -*.conf -*.dll -*.dylib -*.jar -*.jpg -*.mhr -*.ogg -*.sh -*.so -*.ttf -*.wav -*.zip -touch.txt diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 6030a3d..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7bcf8e5..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index fdc392f..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index a4d2007..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.gitattributes b/Engine/.gitattributes similarity index 100% rename from .gitattributes rename to Engine/.gitattributes diff --git a/Engine/.gitignore b/Engine/.gitignore new file mode 100644 index 0000000..331c8ba --- /dev/null +++ b/Engine/.gitignore @@ -0,0 +1,7 @@ +.gradle/ +gradle/ +gradlew +gradlew.bat +build/ + +.idea/ \ No newline at end of file diff --git a/Engine/build.gradle.kts b/Engine/build.gradle.kts new file mode 100644 index 0000000..8a80347 --- /dev/null +++ b/Engine/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + java + id("com.gradleup.shadow") version "8.3.0" +} + +subprojects { + apply(plugin = "java") + apply(plugin = "com.gradleup.shadow") + java { + toolchain { + languageVersion = JavaLanguageVersion.of(22) + } + withSourcesJar() + withJavadocJar() + } +} + +val lwjglVersion = "3.3.4" +val jomlVersion = "1.10.7" +val jomlPrimitivesVersion = "1.10.0" + +val lwjglNatives = Pair( + System.getProperty("os.name")!!, + System.getProperty("os.arch")!! +).let { (name, arch) -> + when { + arrayOf("Linux", "SunOS", "Unit").any { name.startsWith(it) } -> + if (arrayOf("arm", "aarch64").any { arch.startsWith(it) }) + "natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}" + else if (arch.startsWith("ppc")) + "natives-linux-ppc64le" + else if (arch.startsWith("riscv")) + "natives-linux-riscv64" + else + "natives-linux" + + arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } -> + "natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}" + + arrayOf("Windows").any { name.startsWith(it) } -> + if (arch.contains("64")) + "natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}" + else + "natives-windows-x86" + + else -> + throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually") + } +} +extra["lwjglVersion"] = lwjglVersion +extra["jomlVersion"] = jomlVersion +extra["jomlPrimitivesVersion"] = jomlPrimitivesVersion +extra["lwjglNatives"] = lwjglNatives \ No newline at end of file diff --git a/Engine/settings.gradle.kts b/Engine/settings.gradle.kts new file mode 100644 index 0000000..7d8723a --- /dev/null +++ b/Engine/settings.gradle.kts @@ -0,0 +1,7 @@ +rootProject.name = "Engine" +include(":core") +project(":core").projectDir = file("src/core") +include(":opengl") +project(":opengl").projectDir = file("src/opengl") +include(":rendering") +project(":rendering").projectDir = file("src/rendering") \ No newline at end of file diff --git a/Engine/src/core/build.gradle.kts b/Engine/src/core/build.gradle.kts new file mode 100644 index 0000000..94ea372 --- /dev/null +++ b/Engine/src/core/build.gradle.kts @@ -0,0 +1,59 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +repositories { + mavenCentral() +} + +plugins { + java + id("com.gradleup.shadow") version "8.3.0" +} + +val lwjglVersion: String by rootProject.extra +val jomlVersion: String by rootProject.extra +val jomlPrimitivesVersion: String by rootProject.extra +val lwjglNatives: String by rootProject.extra + +sourceSets { + val main by getting { + java.srcDir("java") + } +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:1.18.34") + implementation("org.projectlombok:lombok:1.18.34") + annotationProcessor("javax.annotation:javax.annotation-api:1.3.2") + implementation("javax.annotation:javax.annotation-api:1.3.2") + + implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) + implementation("org.lwjgl", "lwjgl") + runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives) + + implementation("org.lwjgl", "lwjgl-glfw") + runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives) + + implementation("org.lwjgl", "lwjgl-stb") + runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives) + + implementation("org.joml", "joml", jomlVersion) + implementation("org.joml", "joml-primitives", jomlPrimitivesVersion) + + shadow(localGroovy()) + shadow(gradleApi()) + + //implementation("org.lwjgl", "lwjgl-nuklear") + //implementation("org.lwjgl", "lwjgl-openal") + //implementation("org.lwjgl", "lwjgl-remotery") + //runtimeOnly("org.lwjgl", "lwjgl-nuklear", classifier = lwjglNatives) + //runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives) + //runtimeOnly("org.lwjgl", "lwjgl-remotery", classifier = lwjglNatives) +} + +tasks.named("shadowJar") { + archiveFileName.set("dev.euph.engine.core.jar") +} + +configurations { + shadow +} \ No newline at end of file diff --git a/Engine/src/core/java/dev/euph/engine/core/data/Archetype.java b/Engine/src/core/java/dev/euph/engine/core/data/Archetype.java new file mode 100644 index 0000000..e3b841d --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/Archetype.java @@ -0,0 +1,37 @@ +package dev.euph.engine.core.data; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Getter +@EqualsAndHashCode +public class Archetype { + private final Set> componentClasses; + + @SafeVarargs + public Archetype(Class... componentClasses) { + this.componentClasses = new HashSet<>(Arrays.asList(componentClasses)); + } + + public Archetype(Set> componentClasses) { + this.componentClasses = new HashSet<>(componentClasses); + } + + public Archetype(List> componentClasses) { + this.componentClasses = new HashSet<>(componentClasses); + } + + public boolean contains(Class componentClass) { + for (Class _componentClass : componentClasses) { + if (componentClass.isAssignableFrom(_componentClass)) { + return true; + } + } + return false; + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/data/ImageLoader.java b/Engine/src/core/java/dev/euph/engine/core/data/ImageLoader.java new file mode 100644 index 0000000..f463f75 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/ImageLoader.java @@ -0,0 +1,27 @@ +package dev.euph.engine.core.data; + +import org.lwjgl.BufferUtils; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import static org.lwjgl.stb.STBImage.*; + + +public class ImageLoader { + public record Image(ByteBuffer data, int width, int height) { + } + + public static Image loadImage(String path) { + IntBuffer width = BufferUtils.createIntBuffer(1); + IntBuffer height = BufferUtils.createIntBuffer(1); + IntBuffer channels = BufferUtils.createIntBuffer(1); + ByteBuffer imageBuffer = stbi_load(path, width, height, channels, 0); + if(imageBuffer == null) { + throw new RuntimeException("Failed to load image: " + stbi_failure_reason()); + } + Image image = new Image(imageBuffer, width.get(0), height.get(0)); + stbi_image_free(imageBuffer); + return image; + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/data/octree/Octree.java b/Engine/src/core/java/dev/euph/engine/core/data/octree/Octree.java new file mode 100644 index 0000000..062ca6a --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/octree/Octree.java @@ -0,0 +1,20 @@ +package dev.euph.engine.core.data.octree; + +import lombok.AllArgsConstructor; +import org.joml.Vector3f; + +import java.util.List; + +@AllArgsConstructor +public class Octree { + private final OctreeNode root; + + public void insert(Vector3f position, T data) { + root.insert(position, data); + } + + public List query(Vector3f position, float radius) { + return root.query(position, radius); + } +} + diff --git a/Engine/src/core/java/dev/euph/engine/core/data/octree/OctreeNode.java b/Engine/src/core/java/dev/euph/engine/core/data/octree/OctreeNode.java new file mode 100644 index 0000000..af5630c --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/octree/OctreeNode.java @@ -0,0 +1,137 @@ +package dev.euph.engine.core.data.octree; + +import lombok.Getter; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OctreeNode { + private final int maxCapacity; + private final Vector3f center; + private final float halfSize; + private final List positions; + private final Map dataMap; + @Getter + private OctreeNode[] children; + + public OctreeNode(int maxCapacity, Vector3f center, float halfSize) { + this.maxCapacity = maxCapacity; + this.center = center; + this.halfSize = halfSize; + this.positions = new ArrayList<>(); + this.dataMap = new HashMap<>(); + this.children = null; + } + + public void insert(Vector3f position, T data) { + if (isOutOfBounds(position)) { + return; + } + + if (children == null) { + positions.add(position); + dataMap.put(position, data); + if (positions.size() > maxCapacity) { + subdivide(); + } + return; + } + + for (OctreeNode child : children) { + child.insert(position, data); + } + } + + public List query(Vector3f position, float radius) { + List result = new ArrayList<>(); + + if (isOutOfBounds(position)) { + return result; + } + + for (Vector3f itemPosition : positions) { + if (itemPosition.distance(position) <= radius) { + result.add(dataMap.get(itemPosition)); + } + } + + if (children == null) { + return result; + } + for (OctreeNode child : children) { + result.addAll(child.query(position, radius)); + } + return result; + + } + + private boolean isOutOfBounds(Vector3f position) { + float minX = center.x - halfSize; + float maxX = center.x + halfSize; + float minY = center.y - halfSize; + float maxY = center.y + halfSize; + float minZ = center.z - halfSize; + float maxZ = center.z + halfSize; + + return ( + position.x < minX || + position.x > maxX || + position.y < minY || + position.y > maxY || + position.z < minZ || + position.z > maxZ + ); + } + + + private void subdivide() { + OctreeNode[] childNodes = new OctreeNode[8]; + + float quarterSize = halfSize / 2.0f; + + Vector3f[] childCenters = new Vector3f[]{ + new Vector3f(center.x - quarterSize, center.y - quarterSize, center.z - quarterSize), + new Vector3f(center.x + quarterSize, center.y - quarterSize, center.z - quarterSize), + new Vector3f(center.x - quarterSize, center.y + quarterSize, center.z - quarterSize), + new Vector3f(center.x + quarterSize, center.y + quarterSize, center.z - quarterSize), + new Vector3f(center.x - quarterSize, center.y - quarterSize, center.z + quarterSize), + new Vector3f(center.x + quarterSize, center.y - quarterSize, center.z + quarterSize), + new Vector3f(center.x - quarterSize, center.y + quarterSize, center.z + quarterSize), + new Vector3f(center.x + quarterSize, center.y + quarterSize, center.z + quarterSize) + }; + + for (int i = 0; i < 8; i++) { + childNodes[i] = new OctreeNode<>(maxCapacity, childCenters[i], quarterSize); + } + children = childNodes; + + for (int i = positions.size() - 1; i >= 0; i--) { + Vector3f position = positions.get(i); + T data = dataMap.remove(position); + for (OctreeNode child : children) { + if (!child.isOutOfBounds(position)) { + child.insert(position, data); + break; + } + } + + positions.remove(i); + } + } + + public List getPositions() { + List allPositions = new ArrayList<>(positions); + + if (children != null) { + for (OctreeNode child : children) { + allPositions.addAll(child.getPositions()); + } + } + + return allPositions; + } + +} diff --git a/Engine/src/core/java/dev/euph/engine/core/data/pipeline/Pipeline.java b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/Pipeline.java new file mode 100644 index 0000000..4f3d74a --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/Pipeline.java @@ -0,0 +1,45 @@ +package dev.euph.engine.core.data.pipeline; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +public class Pipeline { + private final Collection> pipelineStages; + + public Pipeline() { + this.pipelineStages = new ArrayList<>(); + } + + public Pipeline(PipelineStage pipelineStage) { + pipelineStages = Collections.singletonList(pipelineStage); + } + + private Pipeline(Collection> pipelineStages) { + this.pipelineStages = new ArrayList<>(pipelineStages); + } + + public Pipeline addStage(PipelineStage pipelineStage) { + final ArrayList> newPipelineStages = new ArrayList<>(pipelineStages); + newPipelineStages.add(pipelineStage); + return new Pipeline<>(newPipelineStages); + } + + /** + * @param input the input of the pipeline + * @return the Output of the pipelines execution + * @throws PipelineRuntimeException if there is an error durring execution + */ + @SuppressWarnings("all") + public Output execute(Input input) { + try { + Object output = input; + for (final PipelineStage pipelineStage : pipelineStages) { + output = pipelineStage.execute(output); + } + return (Output) output; + } catch (Exception exception) { + throw new PipelineRuntimeException(exception); + } + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineRuntimeException.java b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineRuntimeException.java new file mode 100644 index 0000000..c27e4c4 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineRuntimeException.java @@ -0,0 +1,19 @@ +package dev.euph.engine.core.data.pipeline; + +public class PipelineRuntimeException extends RuntimeException { + public PipelineRuntimeException(String message) { + super("Pipeline Runtime Exception: " + message); + } + + public PipelineRuntimeException(String message, Throwable cause) { + super("Pipeline Runtime Exception: " + message, cause); + } + + public PipelineRuntimeException(Throwable cause) { + super("Pipeline Runtime Exception: " + cause); + } + + protected PipelineRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super("Pipeline Runtime Exception: " + message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineStage.java b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineStage.java new file mode 100644 index 0000000..2573540 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/data/pipeline/PipelineStage.java @@ -0,0 +1,5 @@ +package dev.euph.engine.core.data.pipeline; + +public interface PipelineStage { + Output execute(Input input); +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/Component.java b/Engine/src/core/java/dev/euph/engine/core/ecs/Component.java new file mode 100644 index 0000000..2ecf0c9 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/Component.java @@ -0,0 +1,28 @@ +package dev.euph.engine.core.ecs; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public abstract class Component { + + @Getter + protected Entity entity = null; + protected List> requiredComponents = new ArrayList<>(); + + public void OnReady() { + } + + public void OnUpdate(float deltaTime) { + } + + public void OnDestroy() { + } + + protected boolean hasRequiredComponents(Entity entity) { + return requiredComponents + .stream() + .allMatch(requiredClass -> entity.getComponent(requiredClass) != null); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/Entity.java b/Engine/src/core/java/dev/euph/engine/core/ecs/Entity.java new file mode 100644 index 0000000..ae4746e --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/Entity.java @@ -0,0 +1,87 @@ +package dev.euph.engine.core.ecs; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class Entity { + @Getter + private final String name; + @Getter + private final List components; + private Scene scene; + + public Entity(String name) { + this.name = name; + components = new ArrayList<>(); + } + + public Entity addComponent(T component) { + + if (!component.hasRequiredComponents(this)) { + throw new IllegalArgumentException("Cannot add Component. Missing required Components."); + } + + this.components.add(component); + if (this.scene != null) { + this.scene.updateEntityComponents(this); + } + + if (component.entity != null) { + component.entity.removeComponent(component.getClass()); + } + component.entity = this; + + return this; + } + + public T getComponent(Class componentClass) { + for (Component c : components) { + if (componentClass.isAssignableFrom(c.getClass())) { + try { + return componentClass.cast(c); + } catch (ClassCastException e) { + e.printStackTrace(); + assert false : "Error: Casting component."; + } + } + } + return null; + } + + public void removeComponent(Class componentClass) { + for (int i = 0; i < components.size(); i++) { + if (componentClass.isAssignableFrom(components.get(i).getClass())) { + components.remove(i); + if (this.scene != null) { + this.scene.updateEntityComponents(this); + } + return; + } + } + } + + public void ready() { + for (Component component : components) { + component.OnReady(); + } + } + + public void update(float deltaTime) { + for (Component component : components) { + component.OnUpdate(deltaTime); + } + } + + public void destroy() { + scene.removeEntity(this); + components.forEach(Component::OnDestroy); + } + + public Entity addToScene(Scene scene) { + scene.addEntity(this); + this.scene = scene; + return this; + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/Requires.java b/Engine/src/core/java/dev/euph/engine/core/ecs/Requires.java new file mode 100644 index 0000000..3e68447 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/Requires.java @@ -0,0 +1,12 @@ +package dev.euph.engine.core.ecs; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Requires { + Class[] value(); +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/Scene.java b/Engine/src/core/java/dev/euph/engine/core/ecs/Scene.java new file mode 100644 index 0000000..263bac7 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/Scene.java @@ -0,0 +1,101 @@ +package dev.euph.engine.core.ecs; + + +import dev.euph.engine.core.data.Archetype; +import dev.euph.engine.core.ecs.storages.ComponentClassesBySuperclassStorage; +import dev.euph.engine.core.ecs.storages.ComponentsByTypeStorage; +import dev.euph.engine.core.ecs.storages.EntitiesByArchetypeStorage; +import dev.euph.engine.core.ecs.storages.EntitiesByNameStorage; +import lombok.Getter; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +public class Scene { + @Getter + private final Set entities = new HashSet<>(); + private final EntitiesByNameStorage entitiesByName = new EntitiesByNameStorage(); + private final EntitiesByArchetypeStorage entitiesByArchetype = new EntitiesByArchetypeStorage(); + private final ComponentClassesBySuperclassStorage componentClassesBySuperclass = new ComponentClassesBySuperclassStorage(); + private final ComponentsByTypeStorage componentsByType = new ComponentsByTypeStorage(); + @Getter + private boolean isRunning = false; + + public void start() { + if (isRunning) return; + isRunning = true; + + for (Entity entity : entities) { + entity.ready(); + } + } + + public void update(float deltaTime) { + entities.forEach(entity -> entity.update(deltaTime)); + } + + protected void removeEntity(Entity entity) { + entities.remove(entity); + entitiesByName.remove(entity); + entitiesByArchetype.remove(entity); + componentClassesBySuperclass.remove(entity); + componentsByType.remove(entity); + } + + protected void updateEntityComponents(Entity entity) { + Set> components = entity.getComponents() + .stream() + .map(Component::getClass) + .collect(Collectors.toSet()); + entitiesByName.update(entity, components); + entitiesByArchetype.update(entity, components); + componentClassesBySuperclass.update(entity, components); + componentsByType.update(entity, components); + } + + protected void addEntity(Entity entity) { + if (entities.contains(entity)) { + return; + } + entities.add(entity); + + Set> components = entity.getComponents() + .stream() + .map(Component::getClass) + .collect(Collectors.toSet()); + + entitiesByName.add(entity, components); + entitiesByArchetype.add(entity, components); + componentClassesBySuperclass.add(entity, components); + componentsByType.add(entity, components); + + if (!isRunning) { + return; + } + entity.ready(); + } + + @SafeVarargs + public final Set getByComponents(boolean exactMatch, Class... componentClasses) { + Set filteredEntities = new HashSet<>(); + + if (exactMatch) { + filteredEntities.addAll(entitiesByArchetype.get(new Archetype<>(componentClasses))); + } else { + Set> componentSet = componentClassesBySuperclass.get(Set.of(componentClasses)); + Set componentInstances = componentsByType.get(componentSet); + if (componentInstances != null) { + filteredEntities.addAll(componentInstances.stream() + .map(Component::getEntity) + .toList()); + } + } + return filteredEntities; + } + + public final Set getByName(String name) { + return entitiesByName.get(name); + } + +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/SceneStorage.java b/Engine/src/core/java/dev/euph/engine/core/ecs/SceneStorage.java new file mode 100644 index 0000000..5c7801e --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/SceneStorage.java @@ -0,0 +1,15 @@ +package dev.euph.engine.core.ecs; + +import java.util.Set; + +public interface SceneStorage { + void add(Entity entity, Set> componentClasses); + + void remove(Entity entity); + + void remove(Set entities); + + void update(Entity entity, Set> newComponentClasses); + + Set get(T identifier); +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentClassesBySuperclassStorage.java b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentClassesBySuperclassStorage.java new file mode 100644 index 0000000..48d910a --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentClassesBySuperclassStorage.java @@ -0,0 +1,59 @@ +package dev.euph.engine.core.ecs.storages; + +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.SceneStorage; + +import java.util.*; + +public class ComponentClassesBySuperclassStorage implements SceneStorage>, Class> { + private final Map, Set>> subclassesByComponent; + + public ComponentClassesBySuperclassStorage() { + this.subclassesByComponent = new HashMap<>(); + } + + @Override + public void add(Entity entity, Set> componentClasses) { + for (Class componentClass : componentClasses) { + Set> subclasses = getSubclasses(componentClass); + subclassesByComponent.put(componentClass, subclasses); + } + } + + @Override + public void remove(Entity entity) { + + } + + @Override + public void remove(Set entities) { + + } + + + @Override + public void update(Entity entity, Set> newComponentClasses) { + remove(entity); + } + + @Override + public Set> get(Set> identifier) { + Set> componentSet = new HashSet<>(); + for (Class componentClass : identifier) { + componentSet.add(componentClass); + componentSet.addAll(subclassesByComponent.getOrDefault(componentClass, Collections.emptySet())); + } + return componentSet; + } + + private Set> getSubclasses(Class componentClass) { + Set> subclasses = new HashSet<>(); + for (Class clazz : subclassesByComponent.keySet()) { + if (componentClass.isAssignableFrom(clazz)) { + subclasses.add(clazz); + } + } + return subclasses; + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentsByTypeStorage.java b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentsByTypeStorage.java new file mode 100644 index 0000000..487e7a3 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/ComponentsByTypeStorage.java @@ -0,0 +1,71 @@ +package dev.euph.engine.core.ecs.storages; + +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.SceneStorage; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ComponentsByTypeStorage implements SceneStorage>, Component> { + + private final Map, Set> componentInstancesByType; + + public ComponentsByTypeStorage() { + this.componentInstancesByType = new HashMap<>(); + } + + @Override + public void add(Entity entity, Set> componentClasses) { + for (Class componentClass : componentClasses) { + Set componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new HashSet<>()); + componentInstances.add(entity.getComponent(componentClass)); // added + + Class superClass = componentClass.getSuperclass(); + while (Component.class.isAssignableFrom(superClass)) { + if (Component.class.isAssignableFrom(superClass)) { + Set superClassInstances = componentInstancesByType.computeIfAbsent(superClass.asSubclass(Component.class), key -> new HashSet<>()); + superClassInstances.add(entity.getComponent(componentClass)); + } + superClass = superClass.getSuperclass(); + } + } + } + + @Override + public void remove(Entity entity) { + for (Component component : entity.getComponents()) { + Class componentClass = component.getClass(); + Set componentInstances = componentInstancesByType.get(componentClass); + if (componentInstances != null) { + componentInstances.remove(component); + } + } + } + + @Override + public void remove(Set entities) { + + } + + @Override + public void update(Entity entity, Set> newComponentClasses) { + for (Component component : entity.getComponents()) { + Class componentClass = component.getClass(); + Set componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new HashSet<>()); + componentInstances.add(component); + } + } + + @Override + public Set get(Set> identifier) { + Set filteredEntities = new HashSet<>(); + for (Class componentClass : identifier) { + filteredEntities.addAll(componentInstancesByType.get(componentClass)); + } + return filteredEntities; + } + +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByArchetypeStorage.java b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByArchetypeStorage.java new file mode 100644 index 0000000..3a370cd --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByArchetypeStorage.java @@ -0,0 +1,56 @@ +package dev.euph.engine.core.ecs.storages; + +import dev.euph.engine.core.data.Archetype; +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.SceneStorage; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class EntitiesByArchetypeStorage implements SceneStorage, Entity> { + private final Map, Set> entitiesByArchetype; + + public EntitiesByArchetypeStorage() { + this.entitiesByArchetype = new HashMap<>(); + } + + @Override + public void add(Entity entity, Set> componentClasses) { + Archetype type = new Archetype<>(componentClasses); + Set entitySet = entitiesByArchetype.computeIfAbsent(type, _ -> new HashSet<>()); + entitySet.add(entity); + this.entitiesByArchetype.put(type, entitySet); + } + + @Override + public void remove(Entity entity) { + for (Map.Entry, Set> entry : entitiesByArchetype.entrySet()) { + Archetype archetype = entry.getKey(); + Set archetypeEntities = entry.getValue(); + if (!archetypeEntities.remove(entity)) { + return; + } + if (archetypeEntities.isEmpty()) { + entitiesByArchetype.remove(archetype); + } + } + } + + @Override + public void remove(Set entities) { + entities.forEach(this::remove); + } + + @Override + public void update(Entity entity, Set> newComponentClasses) { + + } + + @Override + public Set get(Archetype identifier) { + return this.entitiesByArchetype.get(identifier); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByNameStorage.java b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByNameStorage.java new file mode 100644 index 0000000..dd4a7aa --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/ecs/storages/EntitiesByNameStorage.java @@ -0,0 +1,58 @@ +package dev.euph.engine.core.ecs.storages; + +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.SceneStorage; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class EntitiesByNameStorage implements SceneStorage { + private final Map> entitiesByName; + + public EntitiesByNameStorage() { + this.entitiesByName = new HashMap<>(); + } + + @Override + public void add(Entity entity, Set> componentClasses) { + Set entities = this.entitiesByName.computeIfAbsent(entity.getName(), _ -> new HashSet<>()); + if (entities == null) { + entities = new HashSet<>(); + } + entities.add(entity); + this.entitiesByName.put(entity.getName(), entities); + } + + @Override + public void remove(Entity entity) { + Set entities = this.entitiesByName.get(entity.getName()); + if (entities == null) { + return; + } + if (entities.size() == 1 && entities.contains(entity)) { + this.entitiesByName.remove(entity.getName()); + return; + } + entities.remove(entity); + this.entitiesByName.put(entity.getName(), entities); + + } + + @Override + public void remove(Set entities) { + entities.forEach(this::remove); + } + + @Override + public void update(Entity entity, Set> newComponentClasses) { + + } + + @Override + public Set get(String identifier) { + return entitiesByName.get(identifier); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/transform/Transform.java b/Engine/src/core/java/dev/euph/engine/core/transform/Transform.java new file mode 100644 index 0000000..d58cf32 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/transform/Transform.java @@ -0,0 +1,118 @@ +package dev.euph.engine.core.transform; + +import dev.euph.engine.core.ecs.Component; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import javax.annotation.PostConstruct; + +import static java.lang.Math.toRadians; + +@Getter +@NoArgsConstructor +public class Transform extends Component { + + private Vector3f position = new Vector3f(); + private Vector3f rotation = new Vector3f(); + private Vector3f scale = new Vector3f(1); + private TransformationMatrix transformationMatrix; + + + public Transform(Vector3f position, Vector3f rotation, Vector3f scale) { + this.position = position; + this.rotation = rotation; + this.scale = scale; + } + + public Transform(Transform transform) { + this.position = transform.position; + this.rotation = transform.rotation; + this.scale = transform.scale; + } + + public static Transform FromPosition(float x, float y, float z) { + return Transform.FromPosition(new Vector3f(x, y, z)); + } + + public static Transform FromPosition(Vector3f position) { + return new Transform(position, new Vector3f(), new Vector3f(1)); + } + + public static Transform FromRotation(float x, float y, float z) { + return Transform.FromRotation(new Vector3f(x, y, z)); + } + + public static Transform FromRotation(Vector3f rotation) { + return new Transform(new Vector3f(), rotation, new Vector3f(1)); + } + + public static Transform FromScale(float x, float y, float z) { + return Transform.FromScale(new Vector3f(x, y, z)); + } + + public static Transform FromScale(Vector3f scale) { + return new Transform(new Vector3f(), new Vector3f(), scale); + } + + @PostConstruct + private void calculateTransformationMatrix() { + transformationMatrix = new TransformationMatrix(this); + } + + public void setPosition(Vector3f position) { + this.position = position; + calculateTransformationMatrix(); + } + + public void setRotation(Vector3f rotation) { + this.rotation = rotation; + calculateTransformationMatrix(); + } + + public Transform setScale(float scale) { + return setScale(new Vector3f(scale)); + } + + public Transform setScale(Vector3f scale) { + this.scale = scale; + calculateTransformationMatrix(); + return this; + } + + public Vector3f getUp() { + Quaternionf q = new Quaternionf().rotateXYZ( + (float) toRadians(rotation.x), + (float) toRadians(rotation.y), + (float) toRadians(rotation.z) + ); + return q.positiveY(new Vector3f()); + } + + public Vector3f getDown() { + return new Vector3f(getUp()).negate(); + } + + public Vector3f getRight() { + Quaternionf q = new Quaternionf().rotateY((float) toRadians(rotation.y)); + return q.positiveX(new Vector3f()); + } + + public Vector3f getLeft() { + return new Vector3f(getRight()).negate(); + } + + public Vector3f getForward() { + return new Vector3f(getBack()).negate(); + } + + public Vector3f getBack() { + Quaternionf q = new Quaternionf().rotateXYZ( + (float) toRadians(rotation.x), + (float) toRadians(rotation.y), + (float) toRadians(rotation.z) + ); + return q.positiveZ(new Vector3f()); + } +} \ No newline at end of file diff --git a/Engine/src/core/java/dev/euph/engine/core/transform/TransformationMatrix.java b/Engine/src/core/java/dev/euph/engine/core/transform/TransformationMatrix.java new file mode 100644 index 0000000..3e59c51 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/transform/TransformationMatrix.java @@ -0,0 +1,33 @@ +package dev.euph.engine.core.transform; + +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class TransformationMatrix extends Matrix4f { + + public TransformationMatrix(Vector3f translation, Vector3f rotation, Vector3f scale) { + super(); + super.identity(); + super.translate(translation); + super.rotate((float) Math.toRadians(rotation.x % 360), new Vector3f(1, 0, 0)); + super.rotate((float) Math.toRadians(rotation.y % 360), new Vector3f(0, 1, 0)); + super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1)); + super.scale(scale); + } + + public TransformationMatrix(Transform transform) { + super(); + super.identity(); + super.translate(transform.getPosition()); + Vector3f rotation = transform.getRotation(); + super.rotate((float) Math.toRadians(rotation.x % 360), new Vector3f(1, 0, 0)); + super.rotate((float) Math.toRadians(rotation.y % 360), new Vector3f(0, 1, 0)); + super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1)); + super.scale(transform.getScale()); + } + + public TransformationMatrix() { + super(); + super.identity(); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/util/Time.java b/Engine/src/core/java/dev/euph/engine/core/util/Time.java new file mode 100644 index 0000000..e72db18 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/util/Time.java @@ -0,0 +1,24 @@ +package dev.euph.engine.core.util; + +import lombok.Getter; +import org.lwjgl.glfw.GLFW; + +public class Time { + private long lastFrameTime; + @Getter + private float deltaTime; + + public void startDeltaTime() { + lastFrameTime = getCurrentTime(); + } + + public void updateDeltaTime() { + long currentFrameTime = getCurrentTime(); + deltaTime = (currentFrameTime - lastFrameTime) / 1000f; + lastFrameTime = currentFrameTime; + } + + private long getCurrentTime() { + return (long) (GLFW.glfwGetTime() * 1000L); + } +} diff --git a/Engine/src/core/java/dev/euph/engine/core/window/CloseCallback.java b/Engine/src/core/java/dev/euph/engine/core/window/CloseCallback.java new file mode 100644 index 0000000..fdbc2e1 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/window/CloseCallback.java @@ -0,0 +1,5 @@ +package dev.euph.engine.core.window; + +public interface CloseCallback { + void onClose(); +} diff --git a/Engine/src/core/java/dev/euph/engine/core/window/ResizeCallback.java b/Engine/src/core/java/dev/euph/engine/core/window/ResizeCallback.java new file mode 100644 index 0000000..f9d01e1 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/window/ResizeCallback.java @@ -0,0 +1,5 @@ +package dev.euph.engine.core.window; + +public interface ResizeCallback { + void onResize(int width, int height); +} diff --git a/Engine/src/core/java/dev/euph/engine/core/window/Window.java b/Engine/src/core/java/dev/euph/engine/core/window/Window.java new file mode 100644 index 0000000..f4d31b2 --- /dev/null +++ b/Engine/src/core/java/dev/euph/engine/core/window/Window.java @@ -0,0 +1,127 @@ +package dev.euph.engine.core.window; + +import dev.euph.engine.core.util.Time; +import lombok.Getter; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWWindowCloseCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallback; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; +import static org.lwjgl.glfw.GLFW.*; + +@Getter +public abstract class Window { + + //@Getter(AccessLevel.NONE) + protected final long id; + protected final Time time; + + protected final int initialWidth, initialHeight; + private final Set closeCallbacks = new HashSet<>(); + private final Set resizeCallbacks = new HashSet<>(); + protected int width, height; + + public Window(int width, int height, String title) { + this(width, height, title, 0, new Time()); + } + + public Window(int width, int height, String title, int vsyncInterval) { + this(width, height, title, vsyncInterval, new Time()); + } + + private Window(int width, int height, String title, int vsyncInterval, Time time) { + this.time = time; + this.initialWidth = width; + this.width = width; + this.initialHeight = height; + this.height = height; + + GLFWErrorCallback.createPrint(System.err).set(); + if (!glfwInit()) { + throw new IllegalStateException("Unable to initialize GLFW"); + } + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + this.id = glfwCreateWindow(width, height, title, 0, 0); + if (id == 0) { + throw new RuntimeException("Failed to create the GLFW window"); + } + setupCallbacks(); + glfwMakeContextCurrent(id); + glfwSwapInterval(vsyncInterval); + glfwShowWindow(id); + time.startDeltaTime(); + } + + private void setupCallbacks() { + GLFWWindowCloseCallback closeCallback = new GLFWWindowCloseCallback() { + @Override + public void invoke(long window) { + closeCallbacks.forEach(CloseCallback::onClose); + } + }; + glfwSetWindowCloseCallback(id, closeCallback); + GLFWWindowSizeCallback windowSizeCallback = new GLFWWindowSizeCallback() { + @Override + public void invoke(long window, int newWidth, int newHeight) { + Window.this.width = newWidth; + Window.this.height = newHeight; + resizeCallbacks.forEach(callback -> callback.onResize(newWidth, newHeight)); + } + }; + glfwSetWindowSizeCallback(id, windowSizeCallback); + } + + public void update() { + glfwSwapBuffers(id); + glfwPollEvents(); + time.updateDeltaTime(); + onUpdate(); + } + + public void destroy() { + glfwFreeCallbacks(id); + glfwDestroyWindow(id); + glfwTerminate(); + Objects.requireNonNull(glfwSetErrorCallback(null)).free(); + onDestroy(); + } + + public void resize(int width, int height) { + glfwSetWindowSize(id, width, height); + resizeCallbacks.forEach(callback -> callback.onResize(width, height)); + } + + public void setVsync(int interval) { + glfwSwapInterval(interval); + } + + public void addResizeCallback(ResizeCallback resizeCallback) { + resizeCallbacks.add(resizeCallback); + } + + public void removeResizeCallback(ResizeCallback resizeCallback) { + resizeCallbacks.remove(resizeCallback); + } + + public void addCloseCallback(CloseCallback closeCallback) { + closeCallbacks.add(closeCallback); + } + + public void removeCloseCallback(CloseCallback closeCallback) { + closeCallbacks.remove(closeCallback); + } + + + protected abstract void onUpdate(); + + protected abstract void onDestroy(); + + +} diff --git a/Engine/src/opengl/build.gradle.kts b/Engine/src/opengl/build.gradle.kts new file mode 100644 index 0000000..75357db --- /dev/null +++ b/Engine/src/opengl/build.gradle.kts @@ -0,0 +1,49 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +repositories { + mavenCentral() +} + +plugins { + java + id("com.gradleup.shadow") version "8.3.0" +} + +val lwjglVersion: String by rootProject.extra +val jomlVersion: String by rootProject.extra +val jomlPrimitivesVersion: String by rootProject.extra +val lwjglNatives: String by rootProject.extra + +sourceSets { + val main by getting { + java.srcDir("java") + } +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:1.18.34") + implementation("org.projectlombok:lombok:1.18.34") + annotationProcessor("javax.annotation:javax.annotation-api:1.3.2") + implementation("javax.annotation:javax.annotation-api:1.3.2") + + compileOnly(project(":core", configuration = "shadow")) + compileOnly(project(":rendering", configuration = "shadow")) + + implementation("org.lwjgl", "lwjgl-assimp") + runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives) + + implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) + implementation("org.lwjgl", "lwjgl-opengl") + runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives) + + shadow(localGroovy()) + shadow(gradleApi()) +} + +tasks.named("shadowJar") { + archiveFileName.set("dev.euph.engine.opengl.jar") +} + +configurations { + shadow +} \ No newline at end of file diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GL.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GL.java new file mode 100644 index 0000000..7bce6ef --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GL.java @@ -0,0 +1,44 @@ +package dev.euph.engine.opengl; + +import org.lwjgl.opengl.GL31; + +public class GL extends GL31 { + + protected static int getTextureIndex(int index) { + return switch (index) { + case 0 -> GL_TEXTURE0; + case 1 -> GL_TEXTURE1; + case 2 -> GL_TEXTURE2; + case 3 -> GL_TEXTURE3; + case 4 -> GL_TEXTURE4; + case 5 -> GL_TEXTURE5; + case 6 -> GL_TEXTURE6; + case 7 -> GL_TEXTURE7; + case 8 -> GL_TEXTURE8; + case 9 -> GL_TEXTURE9; + case 10 -> GL_TEXTURE10; + case 11 -> GL_TEXTURE11; + case 12 -> GL_TEXTURE12; + case 13 -> GL_TEXTURE13; + case 14 -> GL_TEXTURE14; + case 15 -> GL_TEXTURE15; + case 16 -> GL_TEXTURE16; + case 17 -> GL_TEXTURE17; + case 18 -> GL_TEXTURE18; + case 19 -> GL_TEXTURE19; + case 20 -> GL_TEXTURE20; + case 21 -> GL_TEXTURE21; + case 22 -> GL_TEXTURE22; + case 23 -> GL_TEXTURE23; + case 24 -> GL_TEXTURE24; + case 25 -> GL_TEXTURE25; + case 26 -> GL_TEXTURE26; + case 27 -> GL_TEXTURE27; + case 28 -> GL_TEXTURE28; + case 29 -> GL_TEXTURE29; + case 30 -> GL_TEXTURE30; + case 31 -> GL_TEXTURE31; + default -> throw new IndexOutOfBoundsException("There are Only 32 GL Textures"); + }; + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLMesh.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMesh.java new file mode 100644 index 0000000..d24f1c9 --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMesh.java @@ -0,0 +1,34 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.rendering.resources.Mesh; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; + +public class GLMesh implements Mesh { + private final int vaoId; + private final int vertexCount; + + public GLMesh(float[] vertices, int[] indices) { + this.vertexCount = indices.length; + vaoId = GLMeshHelper.getVao(); + GLMeshHelper.prepareLoad(vaoId); + GLMeshHelper.loadVertices(vertices); + GLMeshHelper.loadIndices(indices); + GLMeshHelper.cleanupLoad(); + } + + @Override + public void bind() { + glBindVertexArray(vaoId); + } + + @Override + public void unbind() { + glBindVertexArray(0); + } + + @Override + public int getVertexCount() { + return vertexCount; + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshHelper.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshHelper.java new file mode 100644 index 0000000..9e3545e --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshHelper.java @@ -0,0 +1,63 @@ +package dev.euph.engine.opengl; + +import org.lwjgl.BufferUtils; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL15.*; +import static org.lwjgl.opengl.GL20.glVertexAttribPointer; +import static org.lwjgl.opengl.GL30.glBindVertexArray; +import static org.lwjgl.opengl.GL30.glGenVertexArrays; + +public class GLMeshHelper { + private final static int VERTICE_INDEX = 0; + private final static int TEXTURE_COORDINATE_INDEX = 1; + + protected static int getVao() { + return glGenVertexArrays(); + } + + protected static void prepareLoad(int vaoId) { + glBindVertexArray(vaoId); + } + + protected static void cleanupLoad() { + glBindVertexArray(0); + } + + protected static void loadVertices(float[] vertices) { + int vboId = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, vboId); + + FloatBuffer buffer = BufferUtils.createFloatBuffer(vertices.length); + buffer.put(vertices).flip(); + glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); + + glVertexAttribPointer(VERTICE_INDEX, 3, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + protected static void loadIndices(int[] indices) { + int eboId = glGenBuffers(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); + + IntBuffer buffer = BufferUtils.createIntBuffer(indices.length); + buffer.put(indices).flip(); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); + } + + protected static void loadTextureCoordinates(float[] textureCoordinates) { + int vboTextureCoords = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, vboTextureCoords); + + FloatBuffer buffer = BufferUtils.createFloatBuffer(textureCoordinates.length); + buffer.put(textureCoordinates).flip(); + glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW); + + glVertexAttribPointer(TEXTURE_COORDINATE_INDEX, 2, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshLoader.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshLoader.java new file mode 100644 index 0000000..2af3fe2 --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLMeshLoader.java @@ -0,0 +1,108 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.rendering.resources.Mesh; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.assimp.*; + +import java.net.URL; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class GLMeshLoader{ + + public static Mesh loadMesh(URL modelResource) { + try { + AIMesh mesh = loadMeshIntoAssimp(modelResource); + + List verticesList = new ArrayList<>(); + for (int i = 0; i < mesh.mNumVertices(); i++) { + loadVertex(mesh, i, verticesList); + } + float[] vertices = listToFloatArray(verticesList); + int[] indices = listToIntArray(loadIndices(mesh)); + + return new GLMesh(vertices, indices); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error loading mesh: " + modelResource.getPath()); + } + } + + public static Mesh loadTexturedMesh(URL modelResource) { + try { + AIMesh mesh = loadMeshIntoAssimp(modelResource); + + List verticesList = new ArrayList<>(); + List textureCoordinatesList = new ArrayList<>(); + + for (int i = 0; i < mesh.mNumVertices(); i++) { + loadVertex(mesh, i, verticesList); + loadTextureCoordinates(mesh, i, textureCoordinatesList); + } + float[] vertices = listToFloatArray(verticesList); + int[] indices = listToIntArray(loadIndices(mesh)); + float[] texCoords = listToFloatArray(textureCoordinatesList); + + return new GLTexturedMesh(vertices, indices, texCoords); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error loading mesh: " + modelResource.getPath()); + } + } + + private static void loadTextureCoordinates(AIMesh mesh, int i, List texCoordsList) { + AIVector3D textureCoordinate = mesh.mTextureCoords(0).get(i); + texCoordsList.add(textureCoordinate.x()); + texCoordsList.add(textureCoordinate.y()); + } + + private static void loadVertex(AIMesh mesh, int i, List verticesList) { + AIVector3D vertex = mesh.mVertices().get(i); + verticesList.add(vertex.x()); + verticesList.add(vertex.y()); + verticesList.add(vertex.z()); + } + + private static List loadIndices(AIMesh mesh) { + List indicesList = new ArrayList<>(); + AIFace.Buffer faceBuffer = mesh.mFaces(); + while (faceBuffer.remaining() > 0) { + AIFace face = faceBuffer.get(); + IntBuffer buffer = face.mIndices(); + while (buffer.remaining() > 0) { + indicesList.add(buffer.get()); + } + } + return indicesList; + } + + private static @NotNull AIMesh loadMeshIntoAssimp(URL modelResource) { + AIScene scene = Assimp.aiImportFile(modelResource.getPath(), Assimp.aiProcess_Triangulate | Assimp.aiProcess_FlipUVs); + + if (scene == null || scene.mNumMeshes() == 0) { + throw new RuntimeException("Failed to load mesh: " + modelResource.getPath()); + } + + AIMesh mesh = AIMesh.create(Objects.requireNonNull(scene.mMeshes()).get(0)); + scene.close(); + return mesh; + } + + private static float[] listToFloatArray(List list) { + float[] array = new float[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + private static int[] listToIntArray(List list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLRenderer.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLRenderer.java new file mode 100644 index 0000000..b39b017 --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLRenderer.java @@ -0,0 +1,90 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.Scene; +import dev.euph.engine.core.transform.TransformationMatrix; +import dev.euph.engine.rendering.Material; +import dev.euph.engine.rendering.RenderPipeline; +import dev.euph.engine.rendering.Shader; +import dev.euph.engine.rendering.ShaderManager; +import dev.euph.engine.rendering.components.Camera; +import dev.euph.engine.rendering.components.MeshRenderer; +import dev.euph.engine.rendering.resources.Mesh; +import dev.euph.engine.rendering.utils.ProjectionMatrix; +import dev.euph.engine.rendering.utils.ViewMatrix; +import lombok.Getter; +import lombok.Setter; + +import static dev.euph.engine.opengl.GL.*; + +public class GLRenderer extends RenderPipeline { + private final ShaderManager shaderManager; + public Camera activeCamera; + @Getter + @Setter + private Scene scene; + + public GLRenderer(Scene scene, ShaderManager shaderManager) { + this.scene = scene; + this.shaderManager = shaderManager; + } + + @Override + public void init() { + glEnable(GL_DEPTH_TEST); + } + + @Override + public void render() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (activeCamera == null) { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + return; + } + + glClearColor( + activeCamera.backgroundColor.getRed(), + activeCamera.backgroundColor.getGreen(), + activeCamera.backgroundColor.getBlue(), + activeCamera.backgroundColor.getAlpha() + ); + + Shader shader = shaderManager.getShader("default"); + if (shader == null) return; + + startShader(shader); + ViewMatrix viewMatrix = new ViewMatrix(activeCamera); + loadMatrix4(shader, getUniformLocation(shader, "viewMatrix"), viewMatrix); + ProjectionMatrix projectionMatrix = new ProjectionMatrix(activeCamera); + loadMatrix4(shader, getUniformLocation(shader, "projectionMatrix"), projectionMatrix); + stopShader(shader); + + for (Entity entity : scene.getByComponents(false, MeshRenderer.class)) { + renderEntity(entity, entity.getComponent(MeshRenderer.class)); + } + } + + private void renderEntity(Entity entity, MeshRenderer meshRenderer) { + Mesh mesh = meshRenderer.getMesh(); + Material material = meshRenderer.getMaterial(); + Shader shader = material.getShader(); + + if (shader == null || mesh == null) return; + + startShader(shader); + bindShaderAttributes(shader); + + TransformationMatrix transformationMatrix = entity.getComponent(Transform.class).getTransformationMatrix(); + loadMatrix4(shader, getUniformLocation(shader, "transformationMatrix"), transformationMatrix); + + material.useMaterial(); + mesh.bind(); + enableShaderAttributes(shader); + glDrawElements(GL_TRIANGLES, mesh.getVertexCount(), GL_UNSIGNED_INT, 0); + disableShaderAttributes(shader); + mesh.unbind(); + stopShader(shader); + } + +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLShader.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLShader.java new file mode 100644 index 0000000..91a0706 --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLShader.java @@ -0,0 +1,200 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.rendering.Shader; +import dev.euph.engine.rendering.resources.Texture; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.lwjgl.BufferUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.FloatBuffer; +import java.util.HashMap; +import java.util.Map; + +import static dev.euph.engine.opengl.GL.*; + +public class GLShader extends Shader { + + private static final FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); + private final int programId; + private final Map uniformLocations = new HashMap<>(); + + public GLShader(String name, URL vertexShader, URL fragmentShader) throws IOException { + super( + name, + loadShader(vertexShader.openStream(), GL_VERTEX_SHADER), + loadShader(fragmentShader.openStream(), GL_FRAGMENT_SHADER) + ); + programId = glCreateProgram(); + setupProgram(); + } + + public GLShader(String name, InputStream vertexShaderStream, InputStream fragmentShaderStream) throws IOException { + super( + name, + loadShader(vertexShaderStream, GL_VERTEX_SHADER), + loadShader(fragmentShaderStream, GL_FRAGMENT_SHADER) + ); + programId = glCreateProgram(); + setupProgram(); + } + + private static int loadShader(InputStream shaderStream, int type) throws IOException { + StringBuilder shaderSource = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(shaderStream))) { + String line; + while ((line = reader.readLine()) != null) { + shaderSource.append(line).append("\n"); + } + } catch (IOException exception) { + System.err.println("Could not read file!"); + exception.printStackTrace(); + throw exception; + } + int shaderId = glCreateShader(type); + glShaderSource(shaderId, shaderSource); + glCompileShader(shaderId); + if (glGetShaderi(shaderId, GL_COMPILE_STATUS) == GL_FALSE) { + System.err.println(glGetShaderInfoLog(shaderId, 512)); + System.err.println("Couldn't compile shader!"); + System.exit(-1); + } + return shaderId; + } + + private void setupProgram() { + glAttachShader(programId, vertexShaderId); + glAttachShader(programId, fragmentShaderId); + bindAttributes(); + glLinkProgram(programId); + glValidateProgram(programId); + getAllUniformLocations(); + } + + @Override + public void start() { + glUseProgram(programId); + } + + @Override + public void stop() { + glUseProgram(0); + } + + @Override + public void close() { + stop(); + glDetachShader(programId, vertexShaderId); + glDetachShader(programId, fragmentShaderId); + glDeleteShader(vertexShaderId); + glDeleteShader(fragmentShaderId); + glDeleteProgram(programId); + } + + private void getAllUniformLocations() { + int numUniforms = glGetProgrami(programId, GL_ACTIVE_UNIFORMS); + + for (int i = 0; i < numUniforms; i++) { + String name = glGetActiveUniformName(programId, i); + int location = glGetUniformLocation(programId, name); + + uniformLocations.put(name, location); + } + } + + @Override + protected int getUniformLocation(String uniformName) { + if (uniformLocations.containsKey(uniformName)) { + return uniformLocations.get(uniformName); + } + + int location = glGetUniformLocation(programId, uniformName); + uniformLocations.put(uniformName, location); + return location; + } + + @Override + protected void bindAttributes() { + glBindAttribLocation(programId, 0, "vertexPositions"); + glBindAttribLocation(programId, 1, "textureCoords"); + glBindAttribLocation(programId, 2, "normals"); + } + + @Override + protected void enableAttributes() { + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + } + + @Override + protected void disableAttributes() { + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + } + + @Override + protected void loadBoolean(int location, boolean value) { + glUniform1f(location, value ? 1f : 0f); + } + + @Override + protected void loadInt(int location, int value) { + glUniform1i(location, value); + } + + @Override + protected void loadFloat(int location, float value) { + glUniform1f(location, value); + } + + @Override + protected void loadVector2(int location, Vector2f value) { + glUniform2f(location, value.x, value.y); + } + + @Override + protected void loadVector3(int location, Vector3f value) { + glUniform3f(location, value.x, value.y, value.z); + } + + @Override + protected void loadVector4(int location, Vector4f value) { + glUniform4f(location, value.x, value.y, value.z, value.w); + } + + @Override + protected void loadMatrix4(int location, Matrix4f value) { + value.get(matrixBuffer); + glUniformMatrix4fv(location, false, matrixBuffer); + } + + @Override + protected void deleteTexture(Texture texture) { + if(texture == null) { + return; + } + glDeleteTextures(texture.getId()); + } + + @Override + protected void bindTexture2D(int location, Texture texture, int textureIndex) { + loadInt(location, textureIndex); + glActiveTexture(getTextureIndex(textureIndex)); + glBindTexture(GL_TEXTURE_2D, texture.getId()); + } + + @Override + protected void bindTexture3D(int location, Texture texture, int textureIndex) { + loadInt(location, textureIndex); + glActiveTexture(getTextureIndex(textureIndex)); + glBindTexture(GL_TEXTURE_3D, texture.getId()); + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLTextureLoader.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLTextureLoader.java new file mode 100644 index 0000000..93a2b3b --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLTextureLoader.java @@ -0,0 +1,35 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.core.data.ImageLoader; +import dev.euph.engine.rendering.resources.Texture; +import dev.euph.engine.rendering.resources.loader.TextureLoader; + +import java.net.URL; + +import static dev.euph.engine.opengl.GL.*; + +public class GLTextureLoader { + public static Texture loadTexture(URL imageResource) { + int textureID = GL.glGenTextures(); + glBindTexture(GL_TEXTURE_2D, textureID); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + ImageLoader.Image image = ImageLoader.loadImage(imageResource.getPath()); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGBA, + image.width(), image.height(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + image.data() + ); + glGenerateMipmap(GL_TEXTURE_2D); + return new Texture(textureID, image.width(), image.height()); + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLTexturedMesh.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLTexturedMesh.java new file mode 100644 index 0000000..6ef0e21 --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLTexturedMesh.java @@ -0,0 +1,35 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.rendering.resources.Mesh; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; + +public class GLTexturedMesh implements Mesh { + private final int vaoId; + private final int vertexCount; + + public GLTexturedMesh(float[] vertices, int[] indices, float[] textureCoords) { + this.vertexCount = indices.length; + vaoId = GLMeshHelper.getVao(); + GLMeshHelper.prepareLoad(vaoId); + GLMeshHelper.loadVertices(vertices); + GLMeshHelper.loadIndices(indices); + GLMeshHelper.loadTextureCoordinates(textureCoords); + GLMeshHelper.cleanupLoad(); + } + + @Override + public void bind() { + glBindVertexArray(vaoId); + } + + @Override + public void unbind() { + glBindVertexArray(0); + } + + @Override + public int getVertexCount() { + return vertexCount; + } +} diff --git a/Engine/src/opengl/java/dev/euph/engine/opengl/GLWindow.java b/Engine/src/opengl/java/dev/euph/engine/opengl/GLWindow.java new file mode 100644 index 0000000..3b6013c --- /dev/null +++ b/Engine/src/opengl/java/dev/euph/engine/opengl/GLWindow.java @@ -0,0 +1,51 @@ +package dev.euph.engine.opengl; + +import dev.euph.engine.core.window.Window; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.opengl.GL; + +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL11.glViewport; + +public class GLWindow extends Window { + + public GLWindow(int width, int height, String title) { + super(width, height, title); + + glfwSetKeyCallback(id, (window, key, scancode, action, mods) -> { + if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { + glfwSetWindowShouldClose(window, true); + } + }); + + if (glfwGetPlatform() != GLFW_PLATFORM_WAYLAND) { + GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + assert vidmode != null; + glfwSetWindowPos( + id, + (vidmode.width() - width) / 2, + (vidmode.height() - height) / 2 + ); + } + + addResizeCallback((int newWidth, int newHeight) -> { + GLWindow.this.width = newWidth; + GLWindow.this.height = newHeight; + glViewport(0, 0, newWidth, newHeight); + }); + + GL.createCapabilities(); + } + + @Override + protected void onUpdate() { + + } + + @Override + protected void onDestroy() { + + } + + +} diff --git a/Engine/src/rendering/build.gradle.kts b/Engine/src/rendering/build.gradle.kts new file mode 100644 index 0000000..984f68b --- /dev/null +++ b/Engine/src/rendering/build.gradle.kts @@ -0,0 +1,42 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +repositories { + mavenCentral() +} + +plugins { + java + id("com.gradleup.shadow") version "8.3.0" +} + +val lwjglVersion: String by rootProject.extra +val jomlVersion: String by rootProject.extra +val jomlPrimitivesVersion: String by rootProject.extra +val lwjglNatives: String by rootProject.extra + +sourceSets { + val main by getting { + java.srcDir("java") + } +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:1.18.34") + implementation("org.projectlombok:lombok:1.18.34") + annotationProcessor("javax.annotation:javax.annotation-api:1.3.2") + implementation("javax.annotation:javax.annotation-api:1.3.2") + + compileOnly(project(":core", configuration = "shadow")) + implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) + + shadow(localGroovy()) + shadow(gradleApi()) +} + +tasks.named("shadowJar") { + archiveFileName.set("dev.euph.engine.rendering.jar") +} + +configurations { + shadow +} \ No newline at end of file diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/Config.java b/Engine/src/rendering/java/dev/euph/engine/rendering/Config.java new file mode 100644 index 0000000..0ff35f9 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/Config.java @@ -0,0 +1,6 @@ +package dev.euph.engine.rendering; + +public class Config { + public static final int MAX_LIGHTS = 6; + public static final int MAX_LIGHT_DISTANCE = 100; +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/Material.java b/Engine/src/rendering/java/dev/euph/engine/rendering/Material.java new file mode 100644 index 0000000..38879c3 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/Material.java @@ -0,0 +1,60 @@ +package dev.euph.engine.rendering; + +import dev.euph.engine.rendering.resources.Texture; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.experimental.Accessors; + +import java.awt.*; + +@Setter +@Getter +@Accessors(chain = true) +public class Material { + + @Setter(AccessLevel.NONE) + protected final Shader shader; + private Texture albedoTexture; + private Color color; + + public Material(Shader shader) { + this.shader = shader; + this.albedoTexture = null; + this.color = Color.WHITE; + } + + public void useMaterial() { + shader.start(); + prepareColor(); + prepareAlbedoTexture(); + } + + private void prepareColor() { + int colorLocation = shader.getUniformLocation("color"); + shader.loadVector4(colorLocation, new org.joml.Vector4f( + color.getRed() / 255f, + color.getGreen() / 255f, + color.getBlue() / 255f, + 1f) + ); + } + + private void prepareAlbedoTexture() { + int useAlbedoTextureLocation = shader.getUniformLocation("useAlbedoTexture"); + if (albedoTexture != null) { + int albedoTextureLocation = shader.getUniformLocation("albedoTexture"); + shader.bindTexture2D(albedoTextureLocation, albedoTexture, Texture.ALBEDO_INDEX); + shader.loadBoolean(useAlbedoTextureLocation, true); + } else { + shader.loadBoolean(useAlbedoTextureLocation, false); + } + } + + @SneakyThrows + public void cleanup() { + shader.deleteTexture(albedoTexture); + shader.close(); + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/PBRMaterial.java b/Engine/src/rendering/java/dev/euph/engine/rendering/PBRMaterial.java new file mode 100644 index 0000000..d731855 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/PBRMaterial.java @@ -0,0 +1,50 @@ +package dev.euph.engine.rendering; + +import dev.euph.engine.rendering.resources.Texture; +import lombok.Setter; + +@Setter +public class PBRMaterial extends Material { + private Texture normalTexture; + private Texture metallicTexture; + private Texture roughnessTexture; + private Texture aoTexture; + + public PBRMaterial(Shader shader) { + super(shader); + } + + @Override + public void useMaterial() { + super.useMaterial(); + + if (normalTexture != null) { + int normalTextureLocation = shader.getUniformLocation("normalTexture"); + shader.bindTexture2D(normalTextureLocation, normalTexture, Texture.NORMAL_INDEX); + } + + if (metallicTexture != null) { + int metallicTextureLocation = shader.getUniformLocation("metallicTexture"); + shader.bindTexture2D(metallicTextureLocation, metallicTexture, Texture.METALLIC_INDEX); + } + + if (roughnessTexture != null) { + int roughnessTextureLocation = shader.getUniformLocation("roughnessTexture"); + shader.bindTexture2D(roughnessTextureLocation, roughnessTexture, Texture.ROUGHNESS_INDEX); + } + + if (aoTexture != null) { + int aoTextureLocation = shader.getUniformLocation("aoTexture"); + shader.bindTexture2D(aoTextureLocation, aoTexture, Texture.AMBIENT_OCCLUSION_INDEX); + } + } + + @Override + public void cleanup() { + super.cleanup(); + shader.deleteTexture(normalTexture); + shader.deleteTexture(metallicTexture); + shader.deleteTexture(roughnessTexture); + shader.deleteTexture(aoTexture); + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/RenderPipeline.java b/Engine/src/rendering/java/dev/euph/engine/rendering/RenderPipeline.java new file mode 100644 index 0000000..be9fe77 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/RenderPipeline.java @@ -0,0 +1,65 @@ +package dev.euph.engine.rendering; + +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; + +public abstract class RenderPipeline { + protected static void startShader(Shader shader) { + shader.start(); + } + + protected static void stopShader(Shader shader) { + shader.stop(); + } + + protected static void bindShaderAttributes(Shader shader) { + shader.bindAttributes(); + } + + protected static void enableShaderAttributes(Shader shader) { + shader.enableAttributes(); + } + + protected static void disableShaderAttributes(Shader shader) { + shader.disableAttributes(); + } + + protected static void loadBoolean(Shader shader, int location, boolean value) { + shader.loadBoolean(location, value); + } + + protected static void loadInt(Shader shader, int location, int value) { + shader.loadInt(location, value); + } + + protected static void loadFloat(Shader shader, int location, float value) { + shader.loadFloat(location, value); + } + + protected static void loadVector2(Shader shader, int location, Vector2f value) { + shader.loadVector2(location, value); + } + + protected static void loadVector3(Shader shader, int location, Vector3f value) { + shader.loadVector3(location, value); + } + + protected static void loadVector4(Shader shader, int location, Vector4f value) { + shader.loadVector4(location, value); + } + + protected static void loadMatrix4(Shader shader, int location, Matrix4f value) { + shader.loadMatrix4(location, value); + } + + protected static int getUniformLocation(Shader shader, String name) { + return shader.getUniformLocation(name); + } + + public abstract void render(); + + public abstract void init(); + +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/Shader.java b/Engine/src/rendering/java/dev/euph/engine/rendering/Shader.java new file mode 100644 index 0000000..0950def --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/Shader.java @@ -0,0 +1,53 @@ +package dev.euph.engine.rendering; + +import dev.euph.engine.rendering.resources.Texture; +import lombok.Getter; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.joml.Matrix4f; + +public abstract class Shader implements AutoCloseable { + protected final int vertexShaderId; + protected final int fragmentShaderId; + @Getter + private final String name; + + protected Shader(String name, int vertexShaderId, int fragmentShaderId) { + this.name = name; + this.vertexShaderId = vertexShaderId; + this.fragmentShaderId = fragmentShaderId; + } + + protected abstract void start(); + + protected abstract void stop(); + + protected abstract void bindAttributes(); + + protected abstract void enableAttributes(); + + protected abstract void disableAttributes(); + + protected abstract int getUniformLocation(String name); + + protected abstract void loadBoolean(int location, boolean value); + + protected abstract void loadInt(int location, int value); + + protected abstract void loadFloat(int location, float value); + + protected abstract void loadVector2(int location, Vector2f value); + + protected abstract void loadVector3(int location, Vector3f value); + + protected abstract void loadVector4(int location, Vector4f value); + + protected abstract void loadMatrix4(int location, Matrix4f value); + + protected abstract void deleteTexture(Texture texture); + + protected abstract void bindTexture2D(int location, Texture texture, int textureIndex); + + protected abstract void bindTexture3D(int location, Texture texture, int textureIndex); +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/ShaderManager.java b/Engine/src/rendering/java/dev/euph/engine/rendering/ShaderManager.java new file mode 100644 index 0000000..f8ce115 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/ShaderManager.java @@ -0,0 +1,31 @@ +package dev.euph.engine.rendering; + +import lombok.AllArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@AllArgsConstructor +public class ShaderManager { + + private final Map shaders = new HashMap<>(); + + public void addShader(Shader shader) { + shaders.put(shader.getName(), shader); + } + + public Shader getShader(String shaderName) { + return shaders.get(shaderName); + } + + public void cleanup() { + shaders.values().forEach(shader -> { + try { + shader.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + shaders.clear(); + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/Camera.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/Camera.java new file mode 100644 index 0000000..d258876 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/Camera.java @@ -0,0 +1,23 @@ +package dev.euph.engine.rendering.components; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Requires; +import dev.euph.engine.core.window.Window; +import lombok.Getter; + +import java.awt.*; + +@Requires(Transform.class) +public class Camera extends Component { + public float fov = 90f; + public float nearPlane = 0.1f; + public float farPlane = 500f; + public Color backgroundColor = new Color(0, 200, 255); + @Getter + private Window window; + + public Camera(Window window) { + this.window = window; + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/MeshRenderer.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/MeshRenderer.java new file mode 100644 index 0000000..4b411e0 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/MeshRenderer.java @@ -0,0 +1,17 @@ +package dev.euph.engine.rendering.components; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Requires; +import dev.euph.engine.rendering.Material; +import dev.euph.engine.rendering.resources.Mesh; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Requires(Transform.class) +public class MeshRenderer extends Component { + private final Mesh mesh; + private final Material material; +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/DirectionalLight.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/DirectionalLight.java new file mode 100644 index 0000000..c923a68 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/DirectionalLight.java @@ -0,0 +1,9 @@ +package dev.euph.engine.rendering.components.lights; + +import java.awt.*; + +public class DirectionalLight extends LightSource { + public DirectionalLight(Color color) { + super(color, LightType.Directional); + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/LightSource.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/LightSource.java new file mode 100644 index 0000000..2928a25 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/LightSource.java @@ -0,0 +1,27 @@ +package dev.euph.engine.rendering.components.lights; + +import dev.euph.engine.core.ecs.Component; +import lombok.Getter; +import lombok.Setter; + +import java.awt.*; + +@Getter +public abstract class LightSource extends Component { + + private final int type; + @Setter + private Color color; + public LightSource(Color color, int type) { + this.color = color; + this.type = type; + } + + public static class LightType { + public static final int Directional = 0; + public static final int Point = 1; + public static final int Spot = 2; + public static final int Area = 3; + } +} + diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/PointLight.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/PointLight.java new file mode 100644 index 0000000..196da4c --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/PointLight.java @@ -0,0 +1,17 @@ +package dev.euph.engine.rendering.components.lights; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Requires; +import org.joml.Vector3f; + +import java.awt.*; + +@Requires(Transform.class) +public class PointLight extends LightSource { + public Vector3f attenuation; + + public PointLight(Color color, Vector3f attenuation) { + super(color, LightType.Point); + this.attenuation = attenuation; + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/SpotLight.java b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/SpotLight.java new file mode 100644 index 0000000..8e6a5e0 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/components/lights/SpotLight.java @@ -0,0 +1,23 @@ +package dev.euph.engine.rendering.components.lights; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Requires; +import org.joml.Vector3f; + +import java.awt.*; + +@Requires(Transform.class) +public class SpotLight extends LightSource { + public float coneAngle; + public float hardCutoff; + public float softCutoff; + public Vector3f attenuation; + + public SpotLight(Color color, Vector3f attenuation, float coneAngle, float hardCutoff, float softCutoff) { + super(color, LightType.Spot); + this.attenuation = attenuation; + this.coneAngle = coneAngle; + this.hardCutoff = hardCutoff; + this.softCutoff = softCutoff; + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Mesh.java b/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Mesh.java new file mode 100644 index 0000000..28da075 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Mesh.java @@ -0,0 +1,7 @@ +package dev.euph.engine.rendering.resources; + +public interface Mesh { + void bind(); + void unbind(); + int getVertexCount(); +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Texture.java b/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Texture.java new file mode 100644 index 0000000..b224bcc --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/resources/Texture.java @@ -0,0 +1,23 @@ +package dev.euph.engine.rendering.resources; + +import lombok.Getter; + +@Getter +public class Texture { + + public final static int ALBEDO_INDEX = 0; + public final static int NORMAL_INDEX = 1; + public final static int METALLIC_INDEX = 2; + public final static int ROUGHNESS_INDEX = 3; + public final static int AMBIENT_OCCLUSION_INDEX = 4; + + private final int id; + private final int height; + private final int width; + + public Texture(int id, int width, int height) { + this.id = id; + this.width = width; + this.height = height; + } +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ProjectionMatrix.java b/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ProjectionMatrix.java new file mode 100644 index 0000000..fccd595 --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ProjectionMatrix.java @@ -0,0 +1,29 @@ +package dev.euph.engine.rendering.utils; + +import dev.euph.engine.core.window.Window; +import dev.euph.engine.rendering.components.Camera; +import org.joml.Matrix4f; + +public class ProjectionMatrix extends Matrix4f { + + public ProjectionMatrix(Camera camera) { + super(); + calculateProjection(camera.getWindow(), camera.fov, camera.nearPlane, camera.farPlane); + } + + private void calculateProjection(Window window, float fov, float nearPlane, float farPlane) { + float aspectRatio = (float) window.getWidth() / (float) window.getHeight(); + + float y_scale = (float) (1f / Math.tan(Math.toRadians(fov / 2f))) * aspectRatio; + float x_scale = y_scale / aspectRatio; + float frustum_length = farPlane - nearPlane; + + m00(x_scale); + m11(y_scale); + m22(-((farPlane + nearPlane) / frustum_length)); + m23(-1); + m32(-((2 * nearPlane * farPlane) / frustum_length)); + m33(0); + } + +} diff --git a/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ViewMatrix.java b/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ViewMatrix.java new file mode 100644 index 0000000..658779c --- /dev/null +++ b/Engine/src/rendering/java/dev/euph/engine/rendering/utils/ViewMatrix.java @@ -0,0 +1,21 @@ +package dev.euph.engine.rendering.utils; + +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.rendering.components.Camera; +import org.joml.Math; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class ViewMatrix extends Matrix4f { + + public ViewMatrix(Camera camera) { + super(); + Transform cameraTransform = camera.getEntity().getComponent(Transform.class); + super.identity(); + super.rotate(Math.toRadians(cameraTransform.getRotation().x), new Vector3f(1, 0, 0)); + super.rotate(Math.toRadians(cameraTransform.getRotation().y), new Vector3f(0, 1, 0)); + super.rotate(Math.toRadians(cameraTransform.getRotation().z), new Vector3f(0, 0, 1)); + Vector3f pos = new Vector3f(cameraTransform.getPosition()); + super.translate(pos.negate()); + } +} diff --git a/Engine/src/resources/build.gradle.kts b/Engine/src/resources/build.gradle.kts new file mode 100644 index 0000000..e17f1e7 --- /dev/null +++ b/Engine/src/resources/build.gradle.kts @@ -0,0 +1,46 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +repositories { + mavenCentral() +} + +plugins { + java + id("com.gradleup.shadow") version "8.3.0" +} + +val lwjglVersion: String by rootProject.extra +val jomlVersion: String by rootProject.extra +val jomlPrimitivesVersion: String by rootProject.extra +val lwjglNatives: String by rootProject.extra + +sourceSets { + val main by getting { + java.srcDir("java") + } +} + +dependencies { + annotationProcessor("org.projectlombok:lombok:1.18.34") + implementation("org.projectlombok:lombok:1.18.34") + annotationProcessor("javax.annotation:javax.annotation-api:1.3.2") + implementation("javax.annotation:javax.annotation-api:1.3.2") + + compileOnly(project(":core", configuration = "shadow")) + implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) + + implementation("org.lwjgl", "lwjgl-assimp") + runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives) + + + shadow(localGroovy()) + shadow(gradleApi()) +} + +tasks.named("shadowJar") { + archiveFileName.set("dev.euph.engine.resources.jar") +} + +configurations { + shadow +} diff --git a/TestGame/.gitignore b/TestGame/.gitignore new file mode 100644 index 0000000..331c8ba --- /dev/null +++ b/TestGame/.gitignore @@ -0,0 +1,7 @@ +.gradle/ +gradle/ +gradlew +gradlew.bat +build/ + +.idea/ \ No newline at end of file diff --git a/TestGame/build.gradle.kts b/TestGame/build.gradle.kts new file mode 100644 index 0000000..eb3fb82 --- /dev/null +++ b/TestGame/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + application + java +} + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(22)) + } +} + +sourceSets { + main { + java { + srcDir("src") + + } + resources { + srcDir("res") + } + } +} + +application { + mainClass.set("dev.euph.game.Main") +} + +val lwjglVersion = "3.3.4" +val jomlVersion = "1.10.7" +val jomlPrimitivesVersion = "1.10.0" + +val lwjglNatives = Pair( + System.getProperty("os.name")!!, + System.getProperty("os.arch")!! +).let { (name, arch) -> + when { + arrayOf("Linux", "SunOS", "Unit").any { name.startsWith(it) } -> + if (arrayOf("arm", "aarch64").any { arch.startsWith(it) }) + "natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}" + else if (arch.startsWith("ppc")) + "natives-linux-ppc64le" + else if (arch.startsWith("riscv")) + "natives-linux-riscv64" + else + "natives-linux" + + arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } -> + "natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}" + + arrayOf("Windows").any { name.startsWith(it) } -> + if (arch.contains("64")) + "natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}" + else + "natives-windows-x86" + + else -> + throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually") + } +} +extra["lwjglVersion"] = lwjglVersion +extra["jomlVersion"] = jomlVersion +extra["jomlPrimitivesVersion"] = jomlPrimitivesVersion +extra["lwjglNatives"] = lwjglNatives + +dependencies { + implementation(project(":core", configuration = "shadow")) + implementation(project(":rendering", configuration = "shadow")) + implementation(project(":opengl", configuration = "shadow")) +} \ No newline at end of file diff --git a/game/src/main/resources/human.blend b/TestGame/res/dev/euph/game/models/human.blend similarity index 100% rename from game/src/main/resources/human.blend rename to TestGame/res/dev/euph/game/models/human.blend diff --git a/game/src/main/resources/human_rigged.mtl b/TestGame/res/dev/euph/game/models/human_rigged.mtl similarity index 100% rename from game/src/main/resources/human_rigged.mtl rename to TestGame/res/dev/euph/game/models/human_rigged.mtl diff --git a/game/src/main/resources/human_rigged.obj b/TestGame/res/dev/euph/game/models/human_rigged.obj similarity index 100% rename from game/src/main/resources/human_rigged.obj rename to TestGame/res/dev/euph/game/models/human_rigged.obj diff --git a/game/src/main/resources/dev/euph/game/shader/default.fs.glsl b/TestGame/res/dev/euph/game/shader/default.fs.glsl similarity index 100% rename from game/src/main/resources/dev/euph/game/shader/default.fs.glsl rename to TestGame/res/dev/euph/game/shader/default.fs.glsl diff --git a/game/src/main/resources/dev/euph/game/shader/default.vs.glsl b/TestGame/res/dev/euph/game/shader/default.vs.glsl similarity index 100% rename from game/src/main/resources/dev/euph/game/shader/default.vs.glsl rename to TestGame/res/dev/euph/game/shader/default.vs.glsl diff --git a/game/src/main/resources/uv.png b/TestGame/res/dev/euph/game/textures/uv.png similarity index 100% rename from game/src/main/resources/uv.png rename to TestGame/res/dev/euph/game/textures/uv.png diff --git a/game/src/main/resources/shader/testFS.glsl b/TestGame/res/shader/testFS.glsl similarity index 100% rename from game/src/main/resources/shader/testFS.glsl rename to TestGame/res/shader/testFS.glsl diff --git a/game/src/main/resources/shader/testVS.glsl b/TestGame/res/shader/testVS.glsl similarity index 100% rename from game/src/main/resources/shader/testVS.glsl rename to TestGame/res/shader/testVS.glsl diff --git a/TestGame/settings.gradle.kts b/TestGame/settings.gradle.kts new file mode 100644 index 0000000..94f30db --- /dev/null +++ b/TestGame/settings.gradle.kts @@ -0,0 +1,8 @@ +rootProject.name = "Game" + +include(":core") +project(":core").projectDir = file("../Engine/src/core") +include(":rendering") +project(":rendering").projectDir = file("../Engine/src/rendering") +include(":opengl") +project(":opengl").projectDir = file("../Engine/src/opengl") \ No newline at end of file diff --git a/game/src/main/java/dev/euph/game/CameraController.java b/TestGame/src/dev/euph/game/CameraController.java similarity index 92% rename from game/src/main/java/dev/euph/game/CameraController.java rename to TestGame/src/dev/euph/game/CameraController.java index ceee1e9..de698ce 100644 --- a/game/src/main/java/dev/euph/game/CameraController.java +++ b/TestGame/src/dev/euph/game/CameraController.java @@ -1,16 +1,19 @@ package dev.euph.game; -import dev.euph.engine.ecs.Component; -import dev.euph.engine.ecs.components.Camera; -import dev.euph.engine.ecs.components.Transform; +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.ecs.Component; +import dev.euph.engine.core.ecs.Requires; +import dev.euph.engine.rendering.components.Camera; import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.glfw.GLFW; -import org.lwjgl.glfw.GLFWKeyCallback; +import lombok.NoArgsConstructor; import static java.lang.Math.max; import static java.lang.Math.min; +@NoArgsConstructor +@Requires({Transform.class, Camera.class}) public class CameraController extends Component { Camera camera; @@ -24,11 +27,6 @@ public class CameraController extends Component { private Vector2f prevMousePos; private Vector2f inputVectorWASD = new Vector2f(); private boolean justCapturedMouse = true; - public CameraController() { - super(); - requireComponent(Transform.class); - requireComponent(Camera.class); - } @Override public void OnReady() { diff --git a/TestGame/src/dev/euph/game/Main.java b/TestGame/src/dev/euph/game/Main.java new file mode 100644 index 0000000..02e0bb5 --- /dev/null +++ b/TestGame/src/dev/euph/game/Main.java @@ -0,0 +1,100 @@ +package dev.euph.game; + +import dev.euph.engine.core.ecs.Entity; +import dev.euph.engine.core.ecs.Scene; +import dev.euph.engine.core.transform.Transform; +import dev.euph.engine.core.window.Window; +import dev.euph.engine.opengl.*; +import dev.euph.engine.rendering.Material; +import dev.euph.engine.rendering.ShaderManager; +import dev.euph.engine.rendering.components.Camera; +import dev.euph.engine.rendering.components.MeshRenderer; +import dev.euph.engine.rendering.components.lights.DirectionalLight; +import dev.euph.engine.rendering.components.lights.PointLight; +import dev.euph.engine.rendering.resources.Mesh; +import dev.euph.engine.rendering.resources.Texture; +import org.joml.Vector3f; + +import java.awt.*; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Main { + public static void main(String[] args) throws IOException { + Window window = new GLWindow(1200, 720, "Test Window"); + window.setVsync(1); + + ShaderManager shaderManager = loadShaders(); + Scene scene = loadScene(window, shaderManager); + Entity camera = scene.getByName("Camera").stream().findFirst().orElse(null); + Entity Model = scene.getByName("Model").stream().findFirst().orElse(null); + + GLRenderer forwardRenderer = new GLRenderer(scene, shaderManager); + forwardRenderer.activeCamera = camera.getComponent(Camera.class); + forwardRenderer.init(); + + float rotationAngle = 0; + float rotationSpeed = 20f; + scene.start(); + AtomicBoolean running = new AtomicBoolean(true); + window.addCloseCallback(() -> { + running.set(false); + }); + while (running.get()) { + scene.update(window.getTime().getDeltaTime()); + + rotationAngle += rotationSpeed * window.getTime().getDeltaTime(); + rotationAngle %= 360; + Model.getComponent(Transform.class).setRotation(new Vector3f(0, rotationAngle, 0)); + + forwardRenderer.render(); + window.update(); + } + shaderManager.cleanup(); + window.destroy(); + } + + private static ShaderManager loadShaders() throws IOException { + ShaderManager shaderManager = new ShaderManager(); + URL fragmentShader = Main.class.getResource("/dev/euph/game/shader/default.fs.glsl"); + URL vertexShader = Main.class.getResource("/dev/euph/game/shader/default.vs.glsl"); + assert vertexShader != null; + assert fragmentShader != null; + shaderManager.addShader(new GLShader( + "default", + Main.class.getResourceAsStream("/dev/euph/game/shader/default.vs.glsl"), + Main.class.getResourceAsStream("/dev/euph/game/shader/default.fs.glsl") + )); + return shaderManager; + } + + private static Scene loadScene(Window window, ShaderManager shaderManager) { + Scene scene = new Scene(); + Entity cameraEntity = new Entity("Camera") + .addComponent(Transform.FromPosition(0, 0, 3)) + .addComponent(new Camera(window)) + .addComponent(new CameraController()) + .addToScene(scene); + Entity light1 = new Entity("DirectionalLight") + .addComponent(Transform.FromRotation(-0.5f, -1.0f, -0.5f)) + .addComponent(new DirectionalLight(Color.WHITE)) + .addToScene(scene); + Entity light2 = new Entity("PointLight") + .addComponent(Transform.FromRotation(-0.5f, -1.0f, -0.5f)) + .addComponent(new PointLight(Color.red, new Vector3f(10))) + .addToScene(scene); + + Mesh modelMesh = GLMeshLoader.loadTexturedMesh(Main.class.getResource("/dev/euph/game/models/human_rigged.obj")); + Texture uvTexture = GLTextureLoader.loadTexture(Main.class.getResource("/dev/euph/game/textures/uv.png")); + Material modelMaterial = new Material(shaderManager.getShader("default")) + .setColor(new Color(7, 77, 255, 255)) + .setAlbedoTexture(uvTexture); + + Entity modelEntity = new Entity("Model") + .addComponent(Transform.FromPosition(0, -4, -6).setScale(3)) + .addComponent(new MeshRenderer(modelMesh, modelMaterial)) + .addToScene(scene); + return scene; + } +} diff --git a/game/src/main/java/dev/euph/game/OctreeTest.java b/TestGame/src/dev/euph/game/OctreeTest.java similarity index 94% rename from game/src/main/java/dev/euph/game/OctreeTest.java rename to TestGame/src/dev/euph/game/OctreeTest.java index 09e9e73..00e11a4 100644 --- a/game/src/main/java/dev/euph/game/OctreeTest.java +++ b/TestGame/src/dev/euph/game/OctreeTest.java @@ -1,6 +1,7 @@ package dev.euph.game; -import dev.euph.engine.datastructs.octree.Octree; +import dev.euph.engine.core.data.octree.Octree; +import dev.euph.engine.core.data.octree.OctreeNode; import org.joml.Vector3f; import javax.imageio.ImageIO; @@ -15,7 +16,11 @@ public class OctreeTest { public static void main(String[] args) { // Create an octree with a center at (0, 0, 0) and a half-size of 20 units - Octree octree = new Octree<>(8, new Vector3f(0, 0, 0), 20); + Octree octree = new Octree<>(new OctreeNode( + 8, + new Vector3f(), + 20 + )); // Generate clustered noise for more interesting patterns Random random = new Random(); diff --git a/game/src/main/java/dev/euph/game/PipelineTest.java b/TestGame/src/dev/euph/game/PipelineTest.java similarity index 89% rename from game/src/main/java/dev/euph/game/PipelineTest.java rename to TestGame/src/dev/euph/game/PipelineTest.java index 39652b5..3d0ca1f 100644 --- a/game/src/main/java/dev/euph/game/PipelineTest.java +++ b/TestGame/src/dev/euph/game/PipelineTest.java @@ -1,7 +1,7 @@ package dev.euph.game; -import dev.euph.engine.datastructs.pipeline.Pipeline; -import dev.euph.engine.datastructs.pipeline.PipelineStage; +import dev.euph.engine.core.data.pipeline.Pipeline; +import dev.euph.engine.core.data.pipeline.PipelineStage; public class PipelineTest { public static void main(String[] args) { diff --git a/game/build.gradle.kts b/game/build.gradle.kts deleted file mode 100644 index e7e753d..0000000 --- a/game/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - application - java -} - -repositories { - mavenCentral() -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) - } -} - -application { - mainClass.set("dev.euph.game.Main") -} - -dependencies { - implementation(project(path = ":engine", configuration = "shadow")) -} - diff --git a/game/settings.gradle.kts b/game/settings.gradle.kts deleted file mode 100644 index ca4e2cd..0000000 --- a/game/settings.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name = "game" -include(":engine") -project(":engine").projectDir = file("../") \ No newline at end of file diff --git a/game/src/main/java/dev/euph/game/Main.java b/game/src/main/java/dev/euph/game/Main.java deleted file mode 100644 index be48cd9..0000000 --- a/game/src/main/java/dev/euph/game/Main.java +++ /dev/null @@ -1,121 +0,0 @@ -package dev.euph.game; - -import dev.euph.engine.ecs.Entity; -import dev.euph.engine.ecs.components.Camera; -import dev.euph.engine.ecs.components.MeshRenderer; -import dev.euph.engine.ecs.components.Transform; -import dev.euph.engine.ecs.components.lights.DirectionalLight; -import dev.euph.engine.ecs.components.lights.PointLight; -import dev.euph.engine.render.ForwardRenderer; -import dev.euph.engine.managers.ShaderManager; -import dev.euph.engine.managers.Window; -import dev.euph.engine.render.Material; -import dev.euph.engine.resources.Texture; -import dev.euph.engine.resources.TexturedMesh; -import dev.euph.engine.resources.loader.MeshLoader; -import dev.euph.engine.resources.loader.TextureLoader; -import dev.euph.engine.ecs.Scene; -import dev.euph.engine.util.Path; -import org.joml.Vector3f; - -import java.awt.*; -import java.io.InputStream; - -public class Main { - public static void main(String[] args) { - Window window = new Window(1200, 720, "Test Window"); - Scene scene = new Scene(); - - InputStream fragmentShaderStream = Main.class.getResourceAsStream("/dev/euph/game/shader/default.fs.glsl"); - InputStream vertexShaderStream = Main.class.getResourceAsStream("/dev/euph/game/shader/default.vs.glsl"); - ShaderManager shaderManager = new ShaderManager(vertexShaderStream, fragmentShaderStream); - - //Creating a Camera - Entity cameraEntity = new Entity("Camera"); - Transform cameraTransform = new Transform( - new Vector3f(0, 0, 3), - new Vector3f(0f, 0f , 0), - new Vector3f(1) - ); - Camera cameraComponent = new Camera(window); - CameraController cameraControllerComponent = new CameraController(); - - cameraEntity.addComponent(cameraTransform); - cameraEntity.addComponent(cameraComponent); - cameraEntity.addComponent(cameraControllerComponent); - scene.addEntityToScene(cameraEntity); - - //creating a light - Entity lightEntity = new Entity("DirectionalLight"); - Transform directionalLightTransform = new Transform( - new Vector3f(0), - new Vector3f(-0.5f, -1.0f, -0.5f), - new Vector3f(1) - ); - DirectionalLight directionalLightComponent = new DirectionalLight(Color.WHITE); - lightEntity.addComponent(directionalLightTransform); - lightEntity.addComponent(directionalLightComponent); - scene.addEntityToScene(lightEntity); - - //creating a light - Entity lightEntity2 = new Entity("PointLight"); - Transform pointLightTransform = new Transform( - new Vector3f(0), - new Vector3f(-0.5f, -1.0f, -0.5f), - new Vector3f(1) - ); - PointLight pointLightComponent = new PointLight(Color.red, new Vector3f(10)); - lightEntity2.addComponent(pointLightTransform); - lightEntity2.addComponent(pointLightComponent); - scene.addEntityToScene(lightEntity2); - - - //creating some Mesh Entity - Entity modelEntity = new Entity("Model"); - TexturedMesh modelMesh = MeshLoader.loadTexturedMesh(Path.RES + "human_rigged.obj"); - Material modelMaterial = new Material(shaderManager.getShader("default")); - modelMaterial.setColor(new Color(7, 77, 255, 255)); - - Texture uvTexture = TextureLoader.loadTexture("uv.png"); - modelMaterial.setAlbedoTexture(uvTexture); - - Transform modelTransform = new Transform( - new Vector3f(0, -4, -6), - new Vector3f(0, 0, 0), - new Vector3f(3, 3, 3) - ); - - MeshRenderer modelMeshRender = new MeshRenderer(modelMesh, modelMaterial); - modelEntity.addComponent(modelTransform); - modelEntity.addComponent(modelMeshRender); - scene.addEntityToScene(modelEntity); - - //initialize renderer - ForwardRenderer forwardRenderer = new ForwardRenderer(scene, shaderManager); - forwardRenderer.activeCamera = cameraComponent; - forwardRenderer.init(); - - //logic - //float rotationAngle = 0; - //float rotationSpeed = 0.5f; - - - scene.start(); - - //Base gameloop - while (!window.isCloseRequested()) { - scene.update(window.getTime().getDeltaTime()); - - //rotationAngle += rotationSpeed; - // Keep the rotation angle within a valid range (e.g., 0 to 360 degrees) - //rotationAngle %= 360; - - // Update the model's rotation based on the new angle - //modelEntity.getComponent(Transform.class).setRotation(new Vector3f(0, rotationAngle, 0)); - - forwardRenderer.render(); - window.updateWindow(); - } - window.destroyWindowy(); - } -}