Fast-forward master to working and delete working #1

Merged
AMNatty merged 29 commits from working into master 2022-04-06 09:04:32 +00:00
290 changed files with 7168 additions and 3968 deletions

0
.gitattributes vendored Normal file → Executable file
View File

39
.github/workflows/gradle-publish.yml vendored Executable file
View File

@ -0,0 +1,39 @@
name: Gradle Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Execute Gradle build
run: |
chmod +x ./gradlew
./gradlew :engine-core:build --stacktrace --info -x test
- name: Publish to Vega
run: ./gradlew :engine-core:publish --stacktrace --info -x test
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSWORD }}
ORG_GRADLE_PROJECT_vegaUsername: ${{ secrets.VEGA_USERNAME }}
ORG_GRADLE_PROJECT_vegaPassword: ${{ secrets.VEGA_PASSWORD }}

8
.gitignore vendored Normal file → Executable file
View File

@ -1,13 +1,17 @@
/*/.settings
/.vscode
# Ignore Eclipse project files
/*/.project
/*/.classpath
/.project
/.settings
# Ignore IDEA project files
/.idea
/*/.idea
.idea
*.log
@ -19,3 +23,7 @@
# Ignore Gradle build output directory
/build
/*/build
/bin
/*/bin
/logs/

2
LICENSE Normal file → Executable file
View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019-2020 493msi
Copyright (c) 2019-2022 493msi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

31
NEXT_RELEASE_DRAFT.md Normal file → Executable file
View File

@ -1,16 +1,17 @@
## Features targeted for 20.2.0.0-alpha.3
* The initial minimal release of `[PlutoCommandParser]`
* `[PlutoLib]` Completely redo the ModLoader system
* The current implementation is a result of 5 years of feature creep
* Large scale API changes, however the general idea should stay the same
* Rethink the class loader system.
* `[PlutoLib]` Redo the resource system
## Features targeted for 22.0.1.0-alpha.0
* `[PlutoGUI]` Initial implementation of the new font renderer
* Full rewrite
* High quality font rendering
* Subpixel rendering support [?]
* Possibly a new system for bitmap fonts
* Improve upon the support of thread-local Pluto instances
* The long term goal is to allow an unlimited amount of Pluto applications at any given time
## Features targeted for 20.2.0.0-alpha.4
## Features targeted for 22.0.1.0-alpha.1
* The stage subsystem
* A "stage", in this context, is a set of assets bound together
by programming logic, not necessarily a game level.
Stage switching and asset management are handled by the engine.
by programming logic, not necessarily a game level.
Stage switching and asset management are handled by the engine.
* Upon stage switch
1. Unload unused assets
2. Load new assets
@ -22,21 +23,19 @@
2. Deferred switch
* The stage will continue running until all assets load
* Assets will load synchronously, but at a slower pace
to avoid frame stutter
to avoid frame stutter
3. Asynchronous switch
* Assets will be loaded in asynchronously, where applicable
* Falls back to deferred switching for synchronous loading,
such as OpenGL texture upload
such as OpenGL texture upload
* Automated asset loading
* All asset management will eventually be handled by `PlutoCore`
* This includes audio clips, textures, sprites
* Add a common interface for all assets
* Let the stage system handle audio playback
* This API should be as neutral as possible and avoid steering towards
game-only use
game-only use
* The stage manager should be relatively low-overhead and allow multiple
instances
instances
* Allow stages to be inherited from, creating a stack-like structure
* `[PlutoAudio]` Integrate the Audio API with the Stage API
* Improve upon the support of thread-local Pluto instances
* The long term goal is to allow an unlimited amount of Pluto applications at any given time

28
README.md Normal file → Executable file
View File

@ -25,18 +25,18 @@ version numbers.*
* **PlutoCore** - Stable
* **PlutoFramebuffer** - Stable
* **PlutoGUI** - Stable, awaiting a rewrite
* **PlutoLib** - Mostly stable, the module API still has some quirks
* **PlutoMesher** - Stable
* **PlutoShader** - Stable
* **PlutoTexture** - Stable
* **PlutoSpriteSheet** - Stable, some features are unfinished
* **PlutoStatic** - Stable, collision API nowhere near completion
* **PlutoDisplay** - Stable, collision API nowhere near completion
* **PlutoUSS2** - Stable
* **PlutoLib** - Mostly stable
### Unstable submodules
* **PlutoRuntime** - Somewhat tentative, the module API has been rewritten and might contain bugs
* **PlutoAudio** - Somewhat usable, unfinished
### Broken submodules, do NOT use
* **PlutoCommandParser** - Unfinished, broken, unusable
* **PlutoDB** - Broken, unusable
## Current priorities
@ -44,25 +44,31 @@ See `NEXT_RELEASE_DRAFT.md` for details.
### Very high priority
[ *Implemented in the current release.* ]
* Rewrite the ModLoader
* Finish PlutoCommandParser
* Streamline PlutoLib, remove bad APIs and improve code quality
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
* The stage system and automated asset loading
* Rewrite PlutoGUI
### High priority
[ *Implemented in the next release.* ]
* Finish PlutoAudio
* Depends on the stage system
* The stage system and automated asset loading
### Normal priority
[ *Planned for an upcoming release.* ]
* Rewrite PlutoGUI
* The collision system for PlutoStatic
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
### Low priority
[ *Items not required immediately, planned to be implemented eventually.* ]
* Allow multiple running instances of Pluto
* Alternatively, if this deems too difficult to implement,
prohibit the creation of more than instance per VM to avoid issues
prohibit the creation of more than one instance per JVM to avoid issues
* A networking API
* Re-add support for external mod jars to the ModLoader
* This feature requires a full rewrite and possibly a complete overhaul
* Mods should have limited execution levels, for example restricted file access
or disabled native library loading (this is probably not possible)
* Expand upon the Color API
* Color mixing and blending
* Color transformation
* High-performance serialization

96
UPDATE_NOTES.md Normal file → Executable file
View File

@ -1,3 +1,59 @@
## 20.2.0.0-alpha.3
* `[SDK]` Restructured the repository
* All build scripts are now written in Kotlin
* **Added runnabled examples**
* **Upgraded to Java 17** to take advantage of new language features and a more efficient JVM
* **The repostiory now contains examples**
* **Moved all classes to the `org.plutoengine` package**
* `[PlutoComponent]` **Added PlutoComponent as a new module**
* `[PlutoLib]` `PlutoLib` now depends on `PlutoComponent`
* `[PlutoUSS2]` **Added USS2 as a new module**
* `[PlutoLib]` `PlutoLib` now depends on `PlutoUSS2`
* `[PlutoLib]` **Greatly simplified the API and moved PlutoEngine specific classes to `PlutoRuntime`**
* ***Moved* the module system to `PlutoRuntime`**
* *Removed* `ResourceSubscriber`,
* *Removed* `cz.tefek.pluto.io.pluto.pp`
* *Removed* `RAID`
* *Moved* `Logger`, `OutputSplitStream` to `PlutoRuntime`
* *Removed* `Severity`, use `SmartSeverity` instead
* *Removed* `TextIn`, `TextOut`, `ResourceImage` and `ResourceInputStream`
* Use Java's NIO instead
* *Removed* `StaticPlutoEventManager` as the implementation was too obscure
* The module system now uses its own event management
* *Removed* the `EventData` class
* `[PlutoRuntime]` **Added PlutoRuntime as a new module**
* **Completely rewrote the module system**
* *Removed* support for external mods as the feature needs a complete overhaul
* **Revamped resource system now based on NIO**
* *Moved* the logging system from `PlutoLib` to `PlutoRuntime`
* Made `OutputSplitStream` public as it is now reusable
* **Added the Version API**
* Added the `IVersion` interface
* Added support for version objects
* As a result, all fields in `Pluto` except the version string are no longer compile-time constants
* `[PlutoDisplay]` **Renamed `PlutoStatic` to `PlutoDisplay`**
* Added the `ModGLFW` virtual module
* `DisplayErrorCallback` and simplified the callbacks in `Display`
* `[PlutoCommandParser]` **Module discontinued as a part of PlutoEngine, it will still be developed seprately**
* `[PlutoTexturing]` Renamed to `PlutoTexture`
* Removed `Texture#load(String)` and `Texture#load(String, MagFilter, MinFilter, WrapMode...)`
* `[PlutoLib]` Added the `@ConstantExpression` annotation
* `[PlutoLib]` The transitive dependency JOML is now provided by `PlutoLib` instead of `PlutoStatic`
* `[PlutoLib]` Created a simple Color API
* `[PlutoLib]` Added the 8-bit RGBA `Color` class as a counterpart to AWT's `Color` class
* `[PlutoLib]` Added the `RGBA` and `RGB` single precision float color objects
* `[PlutoLib]` Added the respective `IRGBA` and `IRGB` read-only interfaces
* `[PlutoLib]` Added the `HSBA` and `HSB` single precision float color objects
* `[PlutoLib]` Added methods to convert between HSBA, RGBA, HSB and RGB
* `[PlutoShader]` Added the `UniformRGBA` and `UniformRGB` shader uniform types
* `[PlutoCore]` Made `PlutoApplication`'s constructor private
* `[PlutoLib]` `MiniTimeParseException` no longer contains a hardcoded String message
* `build.gradle` *Removed* the prepackaged JVM wrapper introduced in the previous alpha
as it caused numerous issues
* In the future, JDKs will be packaged with the SDK
## 20.2.0.0-alpha.2
* `build.gradle` Extracted the version numbers into separate variables
* `build.gradle` **[experimental]** `gradlew` should now automatically download JDK11 when needed
@ -10,7 +66,7 @@
* Renamed `VertexArray#createArrayAttrib` to `VertexArray#createArrayAttribute`
* Renamed `VertexArray#getVertexAttribs` to `VertexArray#getVertexAttributes`
* `[PlutoCore]` Made `PlutoApplication.StartupConfig` fields private, options
can now only be modified only through public setters
can now only be modified only through public setters
* `[PlutoLib]` Added the `ThreadSensitive` annotation
* `[PlutoLib]` Renamed `MiniTimeCouldNotBeParsedException` to `MiniTimeParseException`
* `[PlutoCore]` Refactored `InputBus` and added several convenience methods
@ -19,27 +75,27 @@ can now only be modified only through public setters
## 20.2.0.0-alpha.1
* `[PlutoLib#cz.tefek.pluto.io.logger]` Refactored the Logger subsystem
* Renamed `Logger#logException` to `Logger#log` to match the rest
of log methods and updated references to this method accordingly
* Streamlined `StdOutSplitStream` and `StdErrSplitStream` into a more generalized
`OutputSplitStream`
* `Logger`'s output filenames now look cleaner with `log--YYYY-MM-DD--HH-MM-SS.txt`
* `[Logger#setup]` can now throw `IOException`
* `[PlutoCore]` As a result, `[PlutoApplication#run]` can now throw `Exception`
* Renamed `Logger#logException` to `Logger#log` to match the rest
of log methods and updated references to this method accordingly
* Streamlined `StdOutSplitStream` and `StdErrSplitStream` into a more generalized
`OutputSplitStream`
* `Logger`'s output filenames now look cleaner with `log--YYYY-MM-DD--HH-MM-SS.txt`
* `[Logger#setup]` can now throw `IOException`
* `[PlutoCore]` As a result, `[PlutoApplication#run]` can now throw `Exception`
* `[PlutoLib]` Updated JavaDoc in `ResourceAddress`, `TPL`, `TPNImage`
* `[PlutoLib]` Code cleanup in `MiniTime`, `TPL`
* `[PlutoLib]` Deprecated `TPL#load(String)` in favor of `TPL#load(ResourceAddress)`,
`TPL#load(File)` and `TPL#load(Path)`
* `[PlutoTexturing]` Deprecated the `String` variant of `Texture#load`
to reflect this change
* `[PlutoSpritesheet]` Removed the usage of this method
in `DisposablePlaceholderSprite`
* `[PlutoLib]` Added an option to flip loaded images with `TPL#loadImageSpecial`
and added respective `TPL#loadSpecial` for every `TPL#load`
* `[PlutoLib]` *Removed* `TPJImage`
* `[PlutoLib]` Removed `TPL#loadPixels`
* `[PlutoCore]` Updated `GLFWImageUtil` to remove the usage of `TPJImage`
* `[PlutoLib]` Deprecated `TPL#load(String)` in favor of `TPL#load(ResourceAddress)`,
`TPL#load(File)` and `TPL#load(Path)`
* `[PlutoTexturing]` Deprecated the `String` variant of `Texture#load`
to reflect this change
* `[PlutoSpritesheet]` Removed the usage of this method
in `DisposablePlaceholderSprite`
* `[PlutoLib]` Added an option to flip loaded images with `TPL#loadImageSpecial`
and added respective `TPL#loadSpecial` for every `TPL#load`
* `[PlutoLib]` *Removed* `TPJImage`
* `[PlutoLib]` Removed `TPL#loadPixels`
* `[PlutoCore]` Updated `GLFWImageUtil` to remove the usage of `TPJImage`
* `[PlutoCore]` `[PlutoApplication]` now properly closes the `Logger` on exit
* `[PlutoLib]` Various typo fixes
* `[Pluto*]` Deprecated `Severity` for `SmartSeverity` and replaced all usages
* `[Pluto*]` Deprecated `CRLF` with `LF` in all Java source files
* `[Pluto*]` Replaced `CRLF` with `LF` in all Java source files

View File

@ -1,83 +0,0 @@
import org.gradle.internal.os.OperatingSystem
plugins {
id "me.filippov.gradle.jvm.wrapper" version "0.9.3"
}
jvmWrapper {
linuxJvmUrl = "https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.tar.gz"
macJvmUrl = "https://corretto.aws/downloads/latest/amazon-corretto-11-x64-macos-jdk.tar.gz"
windowsJvmUrl = "https://corretto.aws/downloads/latest/amazon-corretto-11-x64-windows-jdk.zip"
}
wrapper {
distributionType = Wrapper.DistributionType.ALL
}
project.ext.versionYear = 20
project.ext.versionMajor = 2
project.ext.versionMinor = 0
project.ext.versionPatch = 0
project.ext.isPrerelease = true
project.ext.prereleaseName = "alpha"
project.ext.prerealeaseUpdate = 2
subprojects {
apply plugin: 'java'
apply plugin: 'maven-publish'
project.ext.lwjglVersion = "3.2.3"
project.ext.jomlVersion = "1.9.25"
project.ext.steamworks4jVersion = "1.8.0"
project.ext.steamworks4jServerVersion = "1.8.0"
group = "cz.tefek"
version = isPrerelease ?
"${versionYear}.${versionMajor}.${versionMinor}.${versionPatch}-${prereleaseName}.${prerealeaseUpdate}"
:
"${versionYear}.${versionMajor}.${versionMinor}.${versionPatch}"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
task sourcesJar(type: Jar, dependsOn: classes) {
from sourceSets.main.allJava
}
publishing {
publications {
maven(MavenPublication) {
from components.java
artifact sourcesJar {
classifier "sources"
}
}
}
}
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
repositories {
mavenCentral()
}
}

12
build.gradle.kts Executable file
View File

@ -0,0 +1,12 @@
tasks.withType<Wrapper> {
distributionType = Wrapper.DistributionType.ALL
gradleVersion = "7.4.2"
}
subprojects {
group = "cz.tefek"
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
}

24
buildSrc/build.gradle.kts Executable file
View File

@ -0,0 +1,24 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
implementation("org.jetbrains.kotlin", "kotlin-gradle-plugin", "1.6.20")
implementation(gradleApi())
}

View File

@ -0,0 +1,35 @@
package org.plutoengine
import org.gradle.internal.os.OperatingSystem
import org.gradle.api.JavaVersion
object Versions {
const val lwjglVersion = "3.3.1"
val lwjglNatives = when (OperatingSystem.current()) {
OperatingSystem.LINUX -> "natives-linux"
OperatingSystem.WINDOWS -> "natives-windows"
else -> throw Error("Unsupported operating system!")
}
const val jomlVersion = "1.10.2"
const val steamworks4jVersion = "1.8.0"
const val steamworks4jServerVersion = "1.8.0"
const val versionYear = 20
const val versionMajor = 2
const val versionMinor = 0
const val versionPatch = 0
const val isPrerelease = true
const val prereleaseName = "alpha"
const val prerealeaseUpdate = 3
val versionFull =
if (isPrerelease)
"$versionYear.$versionMajor.$versionMinor.$versionPatch-$prereleaseName.$prerealeaseUpdate"
else
"$versionYear.$versionMajor.$versionMinor.$versionPatch"
val javaTargetVersion = JavaVersion.VERSION_17
}

28
engine-core/.gitignore vendored Executable file
View File

@ -0,0 +1,28 @@
/*/.settings
/.vscode
# Ignore Eclipse project files
/*/.project
/*/.classpath
/.project
/.settings
# Ignore IDEA project files
/.idea
/*/.idea
.idea
*.log
/.gradle/
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
/build
/*/build
/bin
/*/bin

70
engine-core/build.gradle.kts Executable file
View File

@ -0,0 +1,70 @@
import org.plutoengine.Versions
task("publish") {
dependsOn(":plutoengine:plutouss2:publish")
dependsOn(":plutoengine:plutolib:publish")
dependsOn(":plutoengine:plutocomponent:publish")
dependsOn(":plutoengine:plutoruntime:publish")
dependsOn(":plutoengine:plutodisplay:publish")
dependsOn(":plutoengine:plutotexture:publish")
dependsOn(":plutoengine:plutomesher:publish")
dependsOn(":plutoengine:plutoshader:publish")
dependsOn(":plutoengine:plutoframebuffer:publish")
dependsOn(":plutoengine:plutospritesheet:publish")
dependsOn(":plutoengine:plutogui:publish")
dependsOn(":plutoengine:plutoaudio:publish")
dependsOn(":plutoengine:plutocore:publish")
}
subprojects {
apply(plugin = "java")
apply(plugin = "java-library")
apply(plugin = "maven-publish")
apply(plugin = "signing")
repositories {
mavenCentral()
}
configure<JavaPluginExtension> {
sourceCompatibility = Versions.javaTargetVersion
targetCompatibility = Versions.javaTargetVersion
}
configure<SourceSetContainer> {
named("main") {
tasks.withType<Jar> {
from(allJava)
}
}
}
configure<PublishingExtension> {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
repositories {
maven {
name = "Vega"
url = uri("https://vega.botdiril.com/")
credentials {
val vegaUsername: String? by project
val vegaPassword: String? by project
username = vegaUsername
password = vegaPassword
}
}
}
}
configure<SigningExtension> {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(the<PublishingExtension>().publications["maven"])
}
}

View File

@ -0,0 +1,16 @@
import org.plutoengine.Versions
plugins {
java
`java-library`
}
description = "PlutoEngine's sound subsystem."
dependencies {
api(project(":plutoengine:plutodisplay"))
api("org.lwjgl:lwjgl-openal")
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = Versions.lwjglNatives)
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.audio;
package org.plutoengine.audio;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
@ -10,67 +10,64 @@ import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import cz.tefek.pluto.engine.buffer.BufferHelper;
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class AudioLoader
{
/**
* Loads an audio track denoted by this {@link ResourceAddress} into memory
* Loads an audio track denoted by this {@link Path} into memory
* for from-memory Vorbis decoding and streaming. Good for frequently used
* medium sized audio files, however it is discouraged to use such a track
* medium-sized audio files, however it is discouraged to use such a track
* in multiple audio sources at once due to the cost of seeking.
*/
public static ISeekableAudioTrack loadMemoryDecoded(ResourceAddress address)
public static ISeekableAudioTrack loadMemoryDecoded(Path path)
{
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
try
{
return new MemoryDecodedVorbisTrack(address);
return new MemoryDecodedVorbisTrack(path);
}
catch (IOException e)
{
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", path);
e.printStackTrace();
return null;
}
}
/**
* Loads an audio track denoted by this {@link ResourceAddress} into memory
* Loads an audio track denoted by this {@link Path} into memory
* for from-memory PCM streaming. Good for frequently used small audio
* files.
*/
public static ISeekableAudioTrack loadMemoryPCM(ResourceAddress address)
public static ISeekableAudioTrack loadMemoryPCM(Path path)
{
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
try
{
return new MemoryPCMTrack(address);
return new MemoryPCMTrack(path);
}
catch (IOException e)
{
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", path);
e.printStackTrace();
return null;
}
}
private static ByteBuffer loadIntoMemory(ResourceAddress addr) throws IOException
private static ByteBuffer loadIntoMemory(Path path) throws IOException
{
var path = addr.toNIOPath();
var size = Files.size(path);
if (size > Integer.MAX_VALUE)
{
throw new IOException("File '" + addr.toString() + "' is too big to be loaded!");
}
throw new IOException("File '%s' is too big to be loaded!".formatted(path));
var readData = MemoryUtil.memAlloc((int) size);
@ -108,18 +105,18 @@ public class AudioLoader
public static class MemoryPCMTrack extends SeekableTrack
{
private ShortBuffer pcmAudio = null;
private final ShortBuffer pcmAudio;
private int sampleOffset = 0;
private MemoryPCMTrack(ResourceAddress address) throws IOException
private MemoryPCMTrack(Path path) throws IOException
{
long handle = MemoryUtil.NULL;
ByteBuffer audioBytes = null;
try (MemoryStack stack = MemoryStack.stackPush())
{
audioBytes = loadIntoMemory(address);
audioBytes = loadIntoMemory(path);
IntBuffer error = stack.mallocInt(1);
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
@ -127,10 +124,10 @@ public class AudioLoader
if (handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(handle, info);
this.channels = info.channels();
@ -201,11 +198,11 @@ public class AudioLoader
private final ByteBuffer encodedAudio;
private MemoryDecodedVorbisTrack(ResourceAddress address) throws IOException
private MemoryDecodedVorbisTrack(Path path) throws IOException
{
try
{
this.encodedAudio = loadIntoMemory(address);
this.encodedAudio = loadIntoMemory(path);
}
catch (IOException e)
{
@ -221,10 +218,10 @@ public class AudioLoader
if (this.handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(this.handle, info);
this.channels = info.channels();
@ -234,7 +231,11 @@ public class AudioLoader
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
Logger.logf(SmartSeverity.AUDIO, "\tSample rate:\t%d\n\t\tChannels:\t%d\n\t\tSamples:\t%d\n", this.sampleRate, this.channels, this.samplesLength);
Logger.logf(SmartSeverity.AUDIO, """
\tSample rate:\t%d
\t\tChannels:\t%d
\t\tSamples:\t%d
%n""", this.sampleRate, this.channels, this.samplesLength);
}
@Override

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.audio;
package org.plutoengine.audio;
import java.nio.ShortBuffer;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.audio;
package org.plutoengine.audio;
public interface ISeekableAudioTrack extends IAudioStream
{

View File

@ -1,23 +1,15 @@
package cz.tefek.pluto.engine.audio.al;
package org.plutoengine.audio.al;
import org.joml.Vector3f;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCCapabilities;
import org.lwjgl.openal.ALCapabilities;
import org.lwjgl.openal.EXTThreadLocalContext;
import org.lwjgl.openal.*;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.plutoengine.Pluto;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import javax.annotation.concurrent.ThreadSafe;
import cz.tefek.pluto.Pluto;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* @author 493msi
@ -26,23 +18,11 @@ import cz.tefek.pluto.io.logger.SmartSeverity;
@ThreadSafe
public class AudioEngine
{
private static ThreadLocal<Long> device = new ThreadLocal<>() {
@Override
protected Long initialValue()
{
return MemoryUtil.NULL;
}
};
private static final ThreadLocal<Long> device = ThreadLocal.withInitial(() -> MemoryUtil.NULL);
private static ThreadLocal<Long> context = new ThreadLocal<>() {
@Override
protected Long initialValue()
{
return MemoryUtil.NULL;
}
};
private static final ThreadLocal<Long> context = ThreadLocal.withInitial(() -> MemoryUtil.NULL);
private static ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>();
private static final ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>();
public static void initialize()
{

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.audio.al;
package org.plutoengine.audio.al;
import org.joml.Vector3fc;
import org.lwjgl.openal.AL10;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.audio.al;
package org.plutoengine.audio.al;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.SOFTDirectChannels;
@ -6,8 +6,8 @@ import org.lwjgl.system.MemoryUtil;
import java.nio.ShortBuffer;
import cz.tefek.pluto.engine.audio.IAudioStream;
import cz.tefek.pluto.engine.audio.ISeekableAudioTrack;
import org.plutoengine.audio.ISeekableAudioTrack;
import org.plutoengine.audio.IAudioStream;
public class AudioTrack extends AudioSource
{
@ -29,17 +29,11 @@ public class AudioTrack extends AudioSource
{
this.track = track;
switch (track.getChannels())
{
case 1:
this.format = AL10.AL_FORMAT_MONO16;
break;
case 2:
this.format = AL10.AL_FORMAT_STEREO16;
break;
default:
throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels());
}
this.format = switch (track.getChannels()) {
case 1 -> AL10.AL_FORMAT_MONO16;
case 2 -> AL10.AL_FORMAT_STEREO16;
default -> throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels());
};
int bufferSize = track.getChannels() * BUFFER_SIZE_PER_CHANNEL;

View File

@ -1,10 +1,10 @@
package cz.tefek.pluto.engine.audio.util;
package org.plutoengine.audio.util;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class AudioUtil
{

View File

@ -0,0 +1,13 @@
plugins {
java
`java-library`
}
description = "A module acting as glue for all PlutoEngine components."
dependencies {
api("org.apache.commons:commons-lang3:3.12.0")
api("org.apache.commons:commons-collections4:4.4")
api("com.google.code.findbugs:jsr305:3.0.2")
}

View File

@ -0,0 +1,49 @@
package org.plutoengine.component;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractComponent implements IComponent
{
private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id;
protected AbstractComponent()
{
this.id = ID_SOURCE.getAndIncrement();
}
@Override
public long getID()
{
return this.id;
}
@Override
public void onMount() throws Exception
{
}
@Override
public void onUnmount() throws Exception
{
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
AbstractComponent that = (AbstractComponent) o;
return this.id == that.id;
}
@Override
public int hashCode()
{
return Objects.hash(this.id);
}
}

View File

@ -0,0 +1,140 @@
package org.plutoengine.component;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.lang3.ClassUtils;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.stream.Stream;
public class ComponentManager<R extends AbstractComponent>
{
private final Class<R> base;
protected final MultiValuedMap<ComponentToken<? extends R>, R> components;
protected final Map<R, ComponentToken<? extends R>> tokens;
protected final MultiValuedMap<Class<?>, R> implementationProviders;
protected final MultiValuedMap<R, Class<?>> implementationReceivers;
public ComponentManager(@Nonnull Class<R> base)
{
this.base = base;
this.components = new HashSetValuedHashMap<>();
this.tokens = new HashMap<>();
this.implementationProviders = new HashSetValuedHashMap<>();
this.implementationReceivers = new ArrayListValuedHashMap<>();
}
public <T extends R> T addComponent(@Nonnull ComponentToken<T> token)
{
T component = token.createInstance();
var clazz = component.getClass();
if (component.isUnique() && this.implementationProviders.containsKey(clazz))
throw new IllegalArgumentException("Cannot have two components of the same class '%s'".formatted(clazz.getCanonicalName()));
var superclasses = ClassUtils.getAllSuperclasses(clazz);
for (var superclass : superclasses)
{
if (superclass.isAssignableFrom(AbstractComponent.class))
continue;
this.implementationProviders.put(superclass, component);
this.implementationReceivers.put(component, superclass);
}
this.implementationProviders.put(clazz, component);
this.components.put(token, component);
this.tokens.put(component, token);
try
{
component.onMount();
}
catch (Exception e)
{
throw new RuntimeException("An exception has occured while mounting the component", e);
}
return component;
}
public Class<R> getComponentBase()
{
return this.base;
}
public <T extends R> Stream<T> streamComponents(@Nonnull Class<T> componentClazz)
{
var providers = this.implementationProviders.get(componentClazz);
return providers.stream().map(componentClazz::cast);
}
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz) throws NoSuchElementException
{
return this.streamComponents(componentClazz)
.findAny()
.orElseThrow();
}
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz, @Nonnull Comparator<T> heuristic) throws NoSuchElementException
{
return this.streamComponents(componentClazz)
.max(heuristic)
.orElseThrow();
}
public <T extends R> List<T> getComponents(@Nonnull Class<T> componentClazz)
{
return this.streamComponents(componentClazz).toList();
}
public void removeComponent(@Nonnull R component) throws IllegalArgumentException
{
var token = this.tokens.remove(component);
if (token == null)
throw new IllegalArgumentException("Component to token mapping could not be found: %d -> ???".formatted(component.getID()));
this.components.removeMapping(token, component);
var classes = this.implementationReceivers.remove(component);
classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component));
try
{
component.onUnmount();
}
catch (Exception e)
{
throw new RuntimeException("An exception has occured whiile unmounting the component", e);
}
}
public <T extends R> void removeComponents(@Nonnull ComponentToken<T> componentToken)
{
var activeComponents = this.components.remove(componentToken);
activeComponents.forEach(component -> {
this.tokens.remove(component);
var classes = this.implementationReceivers.remove(component);
classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component));
try
{
component.onUnmount();
}
catch (Exception e)
{
throw new RuntimeException("An exception has occured whiile unmounting the component", e);
}
});
}
}

View File

@ -0,0 +1,50 @@
package org.plutoengine.component;
import javax.annotation.Nonnull;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
public final class ComponentToken<T extends IComponent>
{
private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id;
private final Supplier<T> supplier;
private ComponentToken(Supplier<T> valueSupplier)
{
this.id = ID_SOURCE.getAndIncrement();
this.supplier = valueSupplier;
}
public static <T extends IComponent> ComponentToken<T> create(@Nonnull Supplier<T> valueSupplier)
{
return new ComponentToken<>(valueSupplier);
}
public T createInstance()
{
return this.supplier.get();
}
public long getID()
{
return this.id;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
ComponentToken<?> that = (ComponentToken<?>) o;
return this.id == that.id;
}
@Override
public int hashCode()
{
return Objects.hash(this.id);
}
}

View File

@ -0,0 +1,22 @@
package org.plutoengine.component;
public interface IComponent
{
/**
* Denotes whether this component should be unique.
* Unique components can only exist once per instance
* in a given {@link ComponentManager}.
*
* @return whether this component should be unique
*
* @author 493msi
* @since 20.2.0.0-alpha.3
*/
boolean isUnique();
long getID();
void onMount() throws Exception;
void onUnmount() throws Exception;
}

View File

@ -0,0 +1,11 @@
plugins {
java
`java-library`
}
description = "The foundation module for games and apps built on top of PlutoEngine."
dependencies {
api(project(":plutoengine:plutogui"))
api(project(":plutoengine:plutoaudio"))
}

View File

@ -1,20 +1,21 @@
package cz.tefek.pluto;
import java.util.Locale;
package org.plutoengine;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL33;
import org.plutoengine.audio.al.AudioEngine;
import org.plutoengine.buffer.GLFWImageUtil;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.display.Display;
import org.plutoengine.display.DisplayBuilder;
import org.plutoengine.input.InputBus;
import org.plutoengine.l10n.PlutoL10n;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import org.plutoengine.mod.ModLoader;
import cz.tefek.pluto.engine.audio.al.AudioEngine;
import cz.tefek.pluto.engine.buffer.GLFWImageUtil;
import cz.tefek.pluto.engine.display.Display;
import cz.tefek.pluto.engine.display.DisplayBuilder;
import cz.tefek.pluto.engine.input.InputBus;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import cz.tefek.pluto.l10n.PlutoL10n;
import cz.tefek.pluto.modloader.ModLoaderCore;
import java.nio.file.Path;
import java.util.Locale;
/**
* The main entry point for OpenGL applications built around the Pluto framework.
@ -31,6 +32,11 @@ public abstract class PlutoApplication
protected abstract void loop();
protected PlutoApplication()
{
}
/**
* A set of values used to create a new {@link PlutoApplication}.
*
@ -111,6 +117,13 @@ public abstract class PlutoApplication
private int windowMinHeight = 600;
private int vsync = 0;
private boolean windowResizable = true;
private Path[] icons = null;
public StartupConfig icons(Path... paths)
{
this.icons = paths;
return this;
}
public StartupConfig coreProfile(boolean coreProfile)
{
@ -169,31 +182,58 @@ public abstract class PlutoApplication
}
}
public final void run(String[] args, StartupConfig config) throws Exception
/**
* TODO: Start the application in a new thread
* */
public final void run(String[] args, StartupConfig config)
{
if (config == null)
{
config = new StartupConfig();
}
Logger.setup();
var globalComponents = PlutoGlobal.COMPONENTS;
globalComponents.addComponent(Logger.TOKEN);
Logger.log(SmartSeverity.INFO, "Debug mode: " + (Pluto.DEBUG_MODE ? "enabled" : "disabled"));
PlutoL10n.init(Locale.UK);
var local = PlutoLocal.instance();
var components = local.COMPONENTS;
DisplayBuilder.initGLFW();
DisplayBuilder displayBuilder;
if (config.coreProfile)
{
this.display = new DisplayBuilder().hintOpenGLVersion(config.majorOpenGLVersion, config.minorOpenGLVersion).hintDebugContext(Pluto.DEBUG_MODE).hintMSAA(config.windowMSAA).hintVisible(true).hintResizeable(config.windowResizable).setInitialSize(config.windowInitialWidth, config.windowInitialHeight).export();
displayBuilder = new DisplayBuilder()
.hintOpenGLVersion(config.majorOpenGLVersion, config.minorOpenGLVersion)
.hintDebugContext(Pluto.DEBUG_MODE)
.hintMSAA(config.windowMSAA)
.hintVisible(true)
.hintResizeable(config.windowResizable)
.setInitialSize(config.windowInitialWidth, config.windowInitialHeight);
}
else
{
this.display = new DisplayBuilder().hintOpenGLVersionLegacy(config.majorOpenGLVersion, config.minorOpenGLVersion).hintDebugContext(Pluto.DEBUG_MODE).hintMSAA(config.windowMSAA).hintVisible(true).hintResizeable(config.windowResizable).setInitialSize(config.windowInitialWidth, config.windowInitialHeight).export();
displayBuilder = new DisplayBuilder()
.hintOpenGLVersionLegacy(config.majorOpenGLVersion, config.minorOpenGLVersion)
.hintDebugContext(Pluto.DEBUG_MODE)
.hintMSAA(config.windowMSAA)
.hintVisible(true)
.hintResizeable(config.windowResizable)
.setInitialSize(config.windowInitialWidth, config.windowInitialHeight);
}
this.display.create(config.windowName);
var displayToken = ComponentToken.create(displayBuilder::export);
this.display = components.addComponent(displayToken);
this.display.setName(config.windowName);
this.display.create();
this.display.setWindowSizeLimits(config.windowMinWidth, config.windowMinHeight, GLFW.GLFW_DONT_CARE, GLFW.GLFW_DONT_CARE);
@ -201,20 +241,24 @@ public abstract class PlutoApplication
this.display.show();
// TODO Un-hardcode these
var icons = GLFWImageUtil.loadIconSet("data/icon16.png", "data/icon32.png", "data/icon64.png", "data/icon128.png");
this.display.setIcons(icons);
this.display.createOpenGLCapabilities();
InputBus.init(this.display);
components.addComponent(InputBus.TOKEN);
AudioEngine.initialize();
ModLoaderCore.registerMod(this.getMainModule());
var modLoader = components.addComponent(ModLoader.TOKEN);
modLoader.registerMod(this.getMainModule());
modLoader.load();
if (config.icons != null)
{
var icons = GLFWImageUtil.loadIconSet(config.icons);
this.display.setIcons(icons);
}
ModLoaderCore.loadProcedure();
while (!this.display.isClosing())
{
@ -233,17 +277,21 @@ public abstract class PlutoApplication
AudioEngine.exit();
InputBus.destroy();
ModLoaderCore.unloadProcedure();
modLoader.unload();
GL.destroy();
components.removeComponent(modLoader);
components.removeComponents(InputBus.TOKEN);
this.display.destroy();
components.removeComponents(displayToken);
DisplayBuilder.destroyGLFW();
Logger.close();
globalComponents.removeComponents(Logger.TOKEN);
}
public Display getDisplayInstance()

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.input;
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWCursorPosCallback;

View File

@ -0,0 +1,183 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.PlutoLocal;
import org.plutoengine.address.ThreadSensitive;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.display.Display;
@ThreadSensitive(localContexts = true)
public class InputBus extends PlutoLocalComponent
{
public static final ComponentToken<InputBus> TOKEN = ComponentToken.create(InputBus::new);
private final KeyboardInputCallback keyboard = new KeyboardInputCallback();
private final MouseButtonCallback mouseButton = new MouseButtonCallback();
private final CursorPositionCallback cursorPosition = new CursorPositionCallback();
private final ScrollInputCallback scroll = new ScrollInputCallback();
private final KeyboardCharInput charInput = new KeyboardCharInput();
private long windowPointer;
private InputBus()
{
}
private static InputBus instance()
{
return PlutoLocal.components().getComponent(InputBus.class);
}
@Override
public void onMount()
{
var display = PlutoLocal.components().getComponent(Display.class);
this.windowPointer = display.getWindowPointer();
GLFW.glfwSetKeyCallback(this.windowPointer, this.keyboard);
GLFW.glfwSetMouseButtonCallback(this.windowPointer, this.mouseButton);
GLFW.glfwSetCursorPosCallback(this.windowPointer, this.cursorPosition);
GLFW.glfwSetScrollCallback(this.windowPointer, this.scroll);
GLFW.glfwSetCharCallback(this.windowPointer, this.charInput);
}
@Override
public void onUnmount()
{
GLFW.glfwSetKeyCallback(this.windowPointer, null);
GLFW.glfwSetMouseButtonCallback(this.windowPointer, null);
GLFW.glfwSetCursorPosCallback(this.windowPointer, null);
GLFW.glfwSetScrollCallback(this.windowPointer, null);
GLFW.glfwSetCharCallback(this.windowPointer, null);
this.scroll.free();
this.mouseButton.free();
this.keyboard.free();
this.cursorPosition.free();
this.charInput.free();
}
public static KeyboardInputCallback keyboard()
{
return instance().keyboard;
}
public static MouseButtonCallback mouseButtons()
{
return instance().mouseButton;
}
public static ScrollInputCallback scroll()
{
return instance().scroll;
}
public static CursorPositionCallback cursorPosition()
{
return instance().cursorPosition;
}
public static KeyboardCharInput charInput()
{
return instance().charInput;
}
public static void resetStates()
{
var instance = instance();
instance.keyboard.resetPressed();
instance.mouseButton.reset();
instance.scroll.reset();
instance.cursorPosition.reset();
instance.charInput.reset();
}
@Override
public boolean isUnique()
{
return true;
}
@ThreadSensitive(localContexts = true)
public static class Mouse
{
public static boolean clicked(int button)
{
return instance().mouseButton.buttonClicked[button];
}
public static boolean released(int button)
{
return instance().mouseButton.buttonReleased[button];
}
public static boolean isButtonDown(int button)
{
return instance().mouseButton.buttonDown[button];
}
public static double getX()
{
return instance().cursorPosition.getX();
}
public static double getY()
{
return instance().cursorPosition.getY();
}
public static boolean isInside(int x1, int y1, int x2, int y2)
{
return instance().cursorPosition.isInside(x1, y1, x2, y2);
}
public static double getDX()
{
return instance().cursorPosition.getDeltaX();
}
public static double getDY()
{
return instance().cursorPosition.getDeltaY();
}
public static double getScrollX()
{
return instance().scroll.getXScroll();
}
public static double getScrollY()
{
return instance().scroll.getYScroll();
}
}
@ThreadSensitive(localContexts = true)
public static class Keyboard
{
public static boolean pressed(int key)
{
return instance().keyboard.hasBeenPressed(key);
}
public static boolean released(int key)
{
return instance().keyboard.hasBeenReleased(key);
}
public static boolean isKeyDown(int key)
{
return instance().keyboard.isKeyDown(key);
}
public static String getTypedText()
{
return instance().charInput.getTypedText();
}
}
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.input;
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWCharCallback;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.input;
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWKeyCallback;
@ -11,8 +11,8 @@ import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
public class KeyboardInputCallback extends GLFWKeyCallback
{
private final Set<Integer> keyPressed = new HashSet<>();
private Set<Integer> keyDown = new HashSet<>();
private Set<Integer> keyReleased = new HashSet<>();
private final Set<Integer> keyDown = new HashSet<>();
private final Set<Integer> keyReleased = new HashSet<>();
@Override
public void invoke(long window, int key, int scancode, int action, int mods)

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.input;
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWMouseButtonCallback;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.input;
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWScrollCallback;

View File

@ -0,0 +1,24 @@
import org.plutoengine.Versions
plugins {
java
`java-library`
}
description = ""
dependencies {
api(project(":plutoengine:plutoruntime"))
api("org.lwjgl", "lwjgl")
api("org.lwjgl", "lwjgl-glfw")
api("org.lwjgl", "lwjgl-opengl")
api("org.lwjgl", "lwjgl-stb")
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = Versions.lwjglNatives)
api("com.code-disaster.steamworks4j", "steamworks4j", Versions.steamworks4jVersion)
api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion)
}

View File

@ -0,0 +1,11 @@
package org.plutoengine;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.mod.ModEntry;
@ModEntry(modID = "glfw", version = ModGLFW.VERSION, dependencies = ModLWJGL.class)
public class ModGLFW
{
public static final String VERSION = GLFW.GLFW_VERSION_MAJOR + "." + GLFW.GLFW_VERSION_MINOR + "." + GLFW.GLFW_VERSION_REVISION;
}

View File

@ -0,0 +1,12 @@
package org.plutoengine;
import org.lwjgl.Version;
import org.plutoengine.mod.ModEntry;
@ModEntry(modID = "lwjgl",
version = ModLWJGL.version)
public class ModLWJGL
{
public static final String version = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION;
}

View File

@ -1,6 +1,5 @@
package cz.tefek.pluto.engine.buffer;
package org.plutoengine.buffer;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import java.io.IOException;
@ -11,8 +10,6 @@ import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
/**
* A utility class to handle primitive native buffers.
*
@ -101,35 +98,6 @@ public final class BufferHelper
return newBuffer;
}
/**
* Loads a file denoted by the specified path and returns a
* {@link ByteBuffer} containing the read bytes.
*
* @param path The file's path.
* @return A {@link ByteBuffer} containing the file's contents.
* @throws IOException Upon standard I/O errors.
*
* @author 493msi
* @since 0.1
*/
public static ByteBuffer readToFlippedByteBuffer(String path) throws IOException
{
try (var fc = FileChannel.open(Path.of(path)))
{
var size = fc.size();
if (size > Integer.MAX_VALUE)
{
throw new IOException("File ' + pah + ' is too big to be read into a ByteBuffer!");
}
ByteBuffer buf = BufferUtils.createByteBuffer((int) size);
fc.read(buf);
buf.flip();
return buf;
}
}
/**
* Loads a file denoted by the specified {@link Path} and fills the input
* {@link ByteBuffer} with the read bytes.
@ -158,25 +126,7 @@ public final class BufferHelper
}
/**
* {@link ResourceAddress} version of
* {@link BufferHelper#readToByteBuffer(Path path, ByteBuffer buf)}.
*
* @param addr The file's {@link ResourceAddress}.
* @param buf The input buffer to be filled with data.
*
* @return The input {@link ByteBuffer}.
* @throws IOException Upon standard I/O errors.
*
* @author 493msi
* @since 0.3
*/
public static ByteBuffer readToByteBuffer(ResourceAddress addr, ByteBuffer buf) throws IOException
{
return readToByteBuffer(addr.toNIOPath(), buf);
}
/**
* Loads a file denoted by the specified {@link ResourceAddress} and returns
* Loads a file denoted by the specified {@link Path} and returns
* a {@link ByteBuffer} containing the read bytes.
*
* @param path The file's path.
@ -186,12 +136,8 @@ public final class BufferHelper
* @author 493msi
* @since 0.3
*/
public static ByteBuffer readToFlippedByteBuffer(ResourceAddress path) throws IOException
public static ByteBuffer readToFlippedByteBuffer(Path path) throws IOException
{
try (var is = Files.newInputStream(path.toNIOPath()))
{
var ba = IOUtils.toByteArray(is);
return flippedByteBuffer(ba);
}
return flippedByteBuffer(Files.readAllBytes(path));
}
}

View File

@ -1,12 +1,11 @@
package cz.tefek.pluto.engine.buffer;
package org.plutoengine.buffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWImage;
import org.plutoengine.tpl.ImageLoader;
import java.nio.file.Path;
import cz.tefek.pluto.io.tpl.TPL;
/**
* A utility class to load image files for use in GLFW.
*
@ -26,13 +25,13 @@ public class GLFWImageUtil
* @author 493msi
* @since 0.2
*/
public static GLFWImage.Buffer loadIconSet(String... icons)
public static GLFWImage.Buffer loadIconSet(Path... icons)
{
var icon = GLFWImage.create(icons.length);
for (String iconPath : icons)
for (var iconPath : icons)
{
var img = TPL.loadSpecial(Path.of(iconPath), false);
var img = ImageLoader.loadSpecial(iconPath, false);
var imgData = img.getData();
int imgWidth = img.getWidth();
int imgHeight = img.getHeight();

View File

@ -4,4 +4,4 @@
* @author 493msi
*
*/
package cz.tefek.pluto.engine.buffer;
package org.plutoengine.buffer;

View File

@ -1,16 +1,15 @@
package cz.tefek.pluto.engine.display;
package org.plutoengine.display;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLDebugMessageARBCallback;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import cz.tefek.pluto.annotation.ThreadSensitive;
import cz.tefek.pluto.engine.gl.GLDebugInfo;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.Pluto;
import org.plutoengine.address.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.gl.GLDebugInfo;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
/**
* A wrapper class to provide abstraction over GLFW windows.
@ -18,37 +17,45 @@ import cz.tefek.pluto.io.logger.SmartSeverity;
* @author 493msi
* @since 0.2
*/
@ThreadSensitive
public class Display
@ThreadSensitive(localContexts = true)
public class Display extends PlutoLocalComponent
{
int width;
int height;
boolean debugMode;
boolean coreProfile = true;
private String name = Pluto.ENGINE_NAME;
private boolean wasResized;
private boolean openGLContext;
private long windowPointer;
private final GLFWErrorCallback glfwErrorCallback;
private final GLFWErrorCallbackI glfwErrorCallback;
private GLFWWindowSizeCallback resizeCallback;
private GLFWWindowSizeCallbackI resizeCallback;
private GLDebugMessageARBCallback glDebugCallback;
private GLDebugMessageARBCallbackI glDebugCallback;
Display()
{
this.glfwErrorCallback = new DisplayErrorCallback();
this.windowPointer = MemoryUtil.NULL;
this.glfwErrorCallback = (int error, long description) -> {
Logger.logf(SmartSeverity.ERROR, "GLFW Error code %d:\n", error);
Logger.logf(GLFWErrorCallback.getDescription(description));
};
GLFW.glfwSetErrorCallback(this.glfwErrorCallback);
}
public void create(String name)
public void create()
{
GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, 4);
this.windowPointer = GLFW.glfwCreateWindow(this.width, this.height, name, MemoryUtil.NULL, MemoryUtil.NULL);
this.windowPointer = GLFW.glfwCreateWindow(this.width, this.height, this.name, MemoryUtil.NULL, MemoryUtil.NULL);
if (this.windowPointer == MemoryUtil.NULL)
{
@ -68,25 +75,21 @@ public class Display
GLFW.glfwMakeContextCurrent(this.windowPointer);
this.resizeCallback = new GLFWWindowSizeCallback() {
@Override
public void invoke(long window, int width, int height)
this.resizeCallback = (long window, int width, int height) -> {
if (width > 0 && height > 0)
{
if (width > 0 && height > 0)
if (this.debugMode)
{
if (Display.this.debugMode)
{
Logger.logf(SmartSeverity.INFO, "Resized to %dx%d.\n", width, height);
}
Logger.logf(SmartSeverity.INFO, "Resized to %dx%d.\n", width, height);
}
Display.this.width = width;
Display.this.height = height;
Display.this.wasResized = true;
this.width = width;
this.height = height;
this.wasResized = true;
if (Display.this.openGLContext)
{
GL33.glViewport(0, 0, Display.this.width, Display.this.height);
}
if (this.openGLContext)
{
GL33.glViewport(0, 0, this.width, this.height);
}
}
};
@ -96,7 +99,10 @@ public class Display
public void setName(String newName)
{
GLFW.glfwSetWindowTitle(this.windowPointer, newName);
this.name = newName;
if (this.windowPointer != MemoryUtil.NULL)
GLFW.glfwSetWindowTitle(this.windowPointer, this.name);
}
public void show()
@ -152,19 +158,19 @@ public class Display
public void destroy()
{
if (this.glfwErrorCallback != null)
if (this.glfwErrorCallback instanceof GLFWErrorCallback glfwErrorCallback)
{
this.glfwErrorCallback.free();
glfwErrorCallback.free();
}
if (this.glDebugCallback != null)
if (this.glDebugCallback instanceof GLDebugMessageARBCallback glDebugMessageARBCallback)
{
this.glDebugCallback.free();
glDebugMessageARBCallback.free();
}
if (this.resizeCallback != null)
if (this.resizeCallback instanceof GLFWWindowSizeCallback windowSizeCallback)
{
this.resizeCallback.free();
windowSizeCallback.free();
}
if (this.windowPointer != MemoryUtil.NULL)
@ -201,16 +207,12 @@ public class Display
if (this.debugMode)
{
this.glDebugCallback = new GLDebugMessageARBCallback() {
@Override
public void invoke(int source, int type, int id, int severity, int length, long messagePtr, long userParam)
{
var message = GLDebugMessageARBCallback.getMessage(length, messagePtr);
Logger.log(SmartSeverity.WARNING, message);
}
this.glDebugCallback = (int source, int type, int id, int severity, int length, long messagePtr, long userParam) -> {
var message = GLDebugMessageARBCallback.getMessage(length, messagePtr);
Logger.log(SmartSeverity.WARNING, message);
};
ARBDebugOutput.glDebugMessageCallbackARB(this.glDebugCallback, 0);
ARBDebugOutput.glDebugMessageCallbackARB(this.glDebugCallback, MemoryUtil.NULL);
}
GL33.glEnable(GL33.GL_CULL_FACE);
@ -218,4 +220,10 @@ public class Display
this.openGLContext = true;
}
@Override
public boolean isUnique()
{
return true;
}
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.display;
package org.plutoengine.display;
import org.lwjgl.glfw.GLFW;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.display;
package org.plutoengine.display;
import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;
@ -20,7 +20,7 @@ public class Framerate
private static boolean firstRemoved = false;
private static Deque<Long> drawTimestamps = new LinkedBlockingDeque<>();
private static final Deque<Long> drawTimestamps = new LinkedBlockingDeque<>();
public static double getFrameTime()
{

View File

@ -4,4 +4,4 @@
* @author 493msi
*
*/
package cz.tefek.pluto.engine.display;
package org.plutoengine.display;

View File

@ -1,12 +1,12 @@
package cz.tefek.pluto.engine.gl;
package org.plutoengine.gl;
import org.lwjgl.opengl.ARBFramebufferObject;
import org.lwjgl.opengl.ARBUniformBufferObject;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLCapabilities;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class GLDebugInfo
{

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.gl;
package org.plutoengine.gl;
/**
* Denotes the implementing class is a set of OpenGL enums.

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.math;
package org.plutoengine.math;
import org.joml.Matrix4f;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.math;
package org.plutoengine.math;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.math;
package org.plutoengine.math;
import org.joml.Matrix3x2f;

View File

@ -4,4 +4,4 @@
* @author 493msi
*
*/
package cz.tefek.pluto.math;
package org.plutoengine.math;

View File

@ -0,0 +1,10 @@
plugins {
java
`java-library`
}
description = ""
dependencies {
api(project(":plutoengine:plutotexture"))
}

View File

@ -1,18 +1,18 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
package org.plutoengine.graphics.gl.fbo;
import org.lwjgl.opengl.GL33;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.opengl.GL33;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class Framebuffer
{
private int id;
private List<FramebufferTexture> textures;
private final List<FramebufferTexture> textures;
private FramebufferDepthTexture depthTexture;

View File

@ -1,10 +1,10 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
package org.plutoengine.graphics.gl.fbo;
import org.lwjgl.opengl.GL33;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
public class FramebufferDepthTexture extends FramebufferTexture
{

View File

@ -1,11 +1,11 @@
package cz.tefek.pluto.engine.graphics.gl.fbo;
package org.plutoengine.graphics.gl.fbo;
import org.lwjgl.system.MemoryUtil;
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
public class FramebufferTexture extends RectangleTexture
{

View File

@ -0,0 +1,10 @@
plugins {
java
`java-library`
}
description = ""
dependencies {
api(project(":plutoengine:plutospritesheet"))
}

View File

@ -0,0 +1,57 @@
package org.plutoengine.graphics;
import org.plutoengine.Pluto;
import org.plutoengine.graphics.font.FontManager;
import org.plutoengine.graphics.font.FontShader;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.gui.font.FontRenderer;
import org.plutoengine.mod.IModEntryPoint;
import org.plutoengine.mod.Mod;
import org.plutoengine.mod.ModEntry;
import org.plutoengine.shader.RenderShaderBuilder;
/**
* @author 493msi
*
*/
@ModEntry(modID = PlutoGUIMod.MOD_ID,
dependencies = { PlutoSpriteSheetMod.class },
version = Pluto.VERSION)
public class PlutoGUIMod implements IModEntryPoint
{
public static final String MOD_ID = "tefek.plutogui";
public static Mod instance;
public static RectangleTexture uiElementsAtlas;
private static FontShader fontShader;
public void onLoad(Mod mod)
{
instance = mod;
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
// Load the default font
FontManager.loadFont(mod.getResource("font.default"));
FontRenderer.load(fontShader);
uiElementsAtlas = new RectangleTexture();
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}
public void onUnload()
{
uiElementsAtlas.delete();
FontManager.unloadAll();
FontRenderer.unload();
fontShader.dispose();
}
}

View File

@ -0,0 +1,96 @@
package org.plutoengine.graphics.font;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.Texture;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.gui.font.CharacterInfo;
import org.plutoengine.gui.font.Font;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class FontManager
{
private static final Map<String, Font> fonts = new HashMap<>();
public static void loadFont(Path address)
{
String fontname = null;
int width = 0;
int height = 0;
var def = new HashMap<Character, CharacterInfo>();
int row = 0;
try
{
var lines = Files.readAllLines(address.resolve("definitions#txt"));
for (var line : lines)
{
if (line.startsWith("//"))
continue;
if (row == 0)
{
String[] fontinfo = line.split(",");
fontname = fontinfo[0];
String[] dim = fontinfo[1].split("x");
width = Integer.parseInt(dim[0]);
height = Integer.parseInt(dim[1]);
}
else
{
String[] offs = line.split(" ")[1].split(";");
def.put(line.charAt(0), new CharacterInfo(row - 1, Integer.parseInt(offs[0]), Integer.parseInt(offs[1])));
}
row++;
}
}
catch (Exception e)
{
Logger.log(SmartSeverity.ERROR, "Could not load font: " + address.toString());
e.printStackTrace();
}
Font font = new Font(fontname, width, height, def);
RectangleTexture texture = new RectangleTexture();
texture.load(address.resolve("tex#png"), MagFilter.NEAREST, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
font.setTexture(texture);
fonts.put(fontname, font);
}
public static void unloadAll()
{
fonts.values()
.stream()
.map(Font::getTexture)
.forEach(Texture::delete);
fonts.clear();
}
public static Font getFontByName(String fontname)
{
var font = fonts.get(fontname);
if (font == null)
{
// Logger.log(SmartSeverity.WARNING, "Font with name " + fontname + " could not be found, using the default one instead (if there is one).");
return fonts.get("default");
}
return font;
}
}

View File

@ -0,0 +1,37 @@
package org.plutoengine.graphics.font;
import org.plutoengine.graphics.gl.vao.attrib.ReservedAttributes;
import org.plutoengine.shader.ShaderBase;
import org.plutoengine.shader.ShaderProgram;
import org.plutoengine.shader.VertexArrayAttribute;
import org.plutoengine.shader.uniform.*;
import org.plutoengine.shader.uniform.auto.AutoViewportProjection;
@ShaderProgram
public final class FontShader extends ShaderBase
{
@AutoViewportProjection
@Uniform(name = "projection")
public UniformMat4 projectionMatrix;
@Uniform(name = "transformation")
public UniformMat4 transformationMatrix;
@Uniform
public UniformVec2 uvBase;
@Uniform
public UniformVec2 uvDelta;
@Uniform
public UniformRGBA recolor;
@Uniform
public UniformBoolean italic;
@VertexArrayAttribute(ReservedAttributes.POSITION)
public int position;
@VertexArrayAttribute(ReservedAttributes.UV)
public int uvCoords;
}

View File

@ -0,0 +1,6 @@
package org.plutoengine.gui.font;
public record CharacterInfo(int number, int leftOffset, int rightOffset)
{
}

View File

@ -1,15 +1,15 @@
package cz.tefek.pluto.engine.gui.font;
package org.plutoengine.gui.font;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import java.util.HashMap;
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
public class Font
{
private String name;
private int width;
private int height;
private HashMap<Character, CharacterInfo> definitions;
private final HashMap<Character, CharacterInfo> definitions;
private RectangleTexture texture;
public Font(String name, int width, int height, HashMap<Character, CharacterInfo> def)

View File

@ -1,6 +1,6 @@
package cz.tefek.pluto.engine.gui.font;
package org.plutoengine.gui.font;
import cz.tefek.pluto.engine.graphics.font.FontManager;
import org.plutoengine.graphics.font.FontManager;
public class FontHelper
{
@ -100,9 +100,9 @@ public class FontHelper
charInf = font.getDefinitions().get('?');
}
totalSpacing -= charInf.getLeftOffset() * relativeSize - relativeSize;
totalSpacing -= charInf.leftOffset() * relativeSize - relativeSize;
totalSpacing += absoluteCharWidth * relativeSize;
totalSpacing -= charInf.getRightOffset() * relativeSize - 1;
totalSpacing -= charInf.rightOffset() * relativeSize - 1;
}
maxW = Math.max(maxW, totalSpacing);
@ -113,9 +113,8 @@ public class FontHelper
public static int calcStringHeight(Object string, float relSize)
{
int size = (int) (30f * relSize * string.toString().split("\n").length);
return size;
return (int) (30f * relSize * string.toString().split("\n").length);
}
}

View File

@ -1,15 +1,15 @@
package cz.tefek.pluto.engine.gui.font;
package org.plutoengine.gui.font;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import cz.tefek.pluto.engine.graphics.font.FontManager;
import cz.tefek.pluto.engine.graphics.font.FontShader;
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
import cz.tefek.pluto.engine.math.TransformationMatrix;
import org.plutoengine.graphics.font.FontManager;
import org.plutoengine.graphics.font.FontShader;
import org.plutoengine.graphics.gl.DrawMode;
import org.plutoengine.graphics.gl.vao.QuadPresets;
import org.plutoengine.graphics.gl.vao.VertexArray;
import org.plutoengine.math.TransformationMatrix;
public class FontRenderer
{
@ -72,8 +72,8 @@ public class FontRenderer
for (int characterIndex = 0; characterIndex < text.length(); characterIndex++)
{
int column = 0;
int row = 0;
int column;
int row;
var currentChar = text.charAt(characterIndex);
@ -104,17 +104,11 @@ public class FontRenderer
cBef = text.charAt(characterIndex - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
if (c != '\\' || cBef == '\\')
{
if (!isShadow)
{
char[] col = new char[10];
text.getChars(characterIndex + 3, characterIndex + 13, col, 0);
String clr = String.valueOf(col);
color(clr);
color(text.substring(characterIndex + 3, characterIndex + 13));
}
characterIndex += 13;
@ -141,16 +135,9 @@ public class FontRenderer
cBef = text.charAt(characterIndex - 2);
}
if (c != '\\' || cBef == '\\' && c == '\\')
if (c != '\\' || cBef == '\\')
{
if (text.charAt(characterIndex + 2) == '1')
{
italic(true);
}
else
{
italic(false);
}
italic(text.charAt(characterIndex + 2) == '1');
characterIndex += 2;
@ -164,26 +151,19 @@ public class FontRenderer
switch (currentChar)
{
case '\n':
case '\n' -> {
color(color);
drawX = xPos;
drawY += lineHeight;
continue;
case ' ':
}
case ' ' -> {
drawX += spaceWidth;
continue;
case 'g':
case 'y':
case 'p':
case 'j':
shift = 6 * relativeSize;
break;
default:
break;
}
case 'g', 'y', 'p', 'j' -> shift = 6 * relativeSize;
default -> {
}
}
var fontDefs = font.getDefinitions();
@ -194,7 +174,7 @@ public class FontRenderer
charInf = fontDefs.get('?');
}
var atlasIndex = charInf.getNumber();
var atlasIndex = charInf.number();
row = atlasIndex / CHAR_WIDTH;
column = atlasIndex % CHAR_WIDTH;
@ -204,7 +184,7 @@ public class FontRenderer
float v = row * CHAR_HEIGHT;
// Offset from the left
drawX -= charInf.getLeftOffset() * relativeSize;
drawX -= charInf.leftOffset() * relativeSize;
float posY = shift + drawY;
@ -218,7 +198,7 @@ public class FontRenderer
charVAO.draw(DrawMode.TRIANGLES);
drawX += charWidth;
drawX -= charInf.getRightOffset() * relativeSize;
drawX -= charInf.rightOffset() * relativeSize;
drawX += relativeSize;
}
@ -239,10 +219,8 @@ public class FontRenderer
dark = 0.35f;
}
if (color instanceof float[])
if (color instanceof float[] c)
{
float[] c = (float[]) color;
if (c.length == 4)
{
recolor(c[0] - dark, c[1] - dark, c[2] - dark, c[3]);
@ -253,10 +231,8 @@ public class FontRenderer
}
}
if (color instanceof String)
if (color instanceof String col)
{
String col = (String) color;
if (col.length() == 7)
{
recolor(Integer.valueOf(col.substring(1, 3), 16) / 256f - dark, Integer.valueOf(col.substring(3, 5), 16) / 256f - dark, Integer.valueOf(col.substring(5, 7), 16) / 256f - dark, 1);

View File

@ -0,0 +1,27 @@
import org.plutoengine.Versions
plugins {
java
`java-library`
}
description = "Multi-purpose utility library that can be used in basically any project."
dependencies {
api(project(":plutoengine:plutouss2"))
api("org.jetbrains", "annotations", "23.0.0")
api("org.yaml", "snakeyaml", "1.28")
api("com.fasterxml.jackson.core", "jackson-core", "2.13.2")
api("com.fasterxml.jackson.core", "jackson-databind", "2.13.2")
api("com.google.guava", "guava", "28.0-jre")
api("org.joml", "joml", Versions.jomlVersion)
api("commons-io", "commons-io", "2.6")
api("org.apache.commons", "commons-lang3", "3.12.0")
}

View File

@ -0,0 +1,295 @@
package org.plutoengine.address;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@JsonDeserialize(using = VirtualAddress.Deserializer.class, keyUsing = VirtualAddress.KeyAddrDeserializer.class)
@JsonSerialize(using = VirtualAddress.Serializer.class, keyUsing = VirtualAddress.Serializer.class)
public final class VirtualAddress implements Comparable<VirtualAddress>
{
public static final int MAX_LENGTH = 128;
public static final int MAX_KEYS = 32;
public static final int MAX_KEY_LENGTH = 32;
public static final int TOKEN_PATH_SEPARATOR = '.';
public static final int TOKEN_HIERARCHY_UP = '~';
private static final VirtualAddress ROOT_ADDRESS = new VirtualAddress("", List.of(), false, 0);
private final String fullPath;
private final List<String> components;
private final boolean relative;
@Range(from = 0, to = Integer.MAX_VALUE)
private final int rootOffset;
VirtualAddress(String fullPath, List<String> components, boolean relative, int rootOffset)
{
this.fullPath = fullPath;
this.components = Collections.unmodifiableList(components);
this.relative = relative;
this.rootOffset = rootOffset;
}
public static VirtualAddress ofRoot()
{
return ROOT_ADDRESS;
}
public List<String> getComponents()
{
return this.components;
}
public boolean isRelative()
{
return this.relative;
}
public boolean isEmpty()
{
return this.fullPath.isEmpty();
}
@Range(from = 0, to = Integer.MAX_VALUE)
public int getRootOffset()
{
return this.rootOffset;
}
@Override
public String toString()
{
return this.fullPath;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || this.getClass() != o.getClass())
return false;
VirtualAddress that = (VirtualAddress) o;
return this.fullPath.equals(that.fullPath);
}
@Override
public int hashCode()
{
return Objects.hash(this.fullPath);
}
public static VirtualAddress parse(String inputStr)
{
return parse(inputStr, false);
}
public static VirtualAddress parse(String inputStr, boolean permitRelative)
{
var parser = new VirtualAddressParser(permitRelative);
inputStr.codePoints()
.sequential()
.forEachOrdered(parser::accept);
return parser.build();
}
public static VirtualAddressParser createParser(boolean permitRelative)
{
return new VirtualAddressParser(permitRelative);
}
@Override
public int compareTo(VirtualAddress o)
{
if (this.rootOffset != o.rootOffset)
return this.rootOffset - o.rootOffset;
return this.fullPath.compareToIgnoreCase(o.fullPath);
}
public int getNameCount()
{
return this.components.size() + this.rootOffset;
}
public @NotNull VirtualAddress getName(int index)
{
if (index < this.rootOffset)
return new VirtualAddress("", List.of(), true, 1);
var component = this.components.get(index - this.rootOffset);
return new VirtualAddress(component, List.of(component), true, 0);
}
public @NotNull VirtualAddress getParent()
{
if (this.components.isEmpty())
{
if (!this.relative)
{
throw new IllegalStateException("Cannot get a parent of a root absolute address!");
}
else
{
var offset = this.rootOffset - 1;
var components = List.<String>of();
return new VirtualAddress(VirtualAddressParser.getNormalizedString(offset, components), components, true, offset);
}
}
var componentsNew = this.components.subList(0, this.components.size() - 1);
return new VirtualAddress(VirtualAddressParser.getNormalizedString(this.rootOffset, componentsNew), componentsNew, this.relative, this.rootOffset);
}
public @NotNull VirtualAddress relativize(@NotNull VirtualAddress other)
{
if (this.relative != other.relative)
throw new IllegalArgumentException("Cannot relativize an address when only one of the inputs is absolute!");
if (this.relative && this.rootOffset > other.rootOffset)
throw new IllegalArgumentException("Cannot relativize against a relative address with a root offset higher than the target one!");
if (this.isEmpty())
return other;
int newOffset = other.rootOffset - this.rootOffset;
var thIt = this.components.iterator();
var oIt = other.components.iterator();
var newPath = new ArrayList<String>();
if (newOffset == 0)
{
while (thIt.hasNext() && oIt.hasNext())
{
var thComp = thIt.next();
var oComp = oIt.next();
if (!thComp.equals(oComp))
{
newOffset++;
newPath.add(oComp);
break;
}
}
}
while (thIt.hasNext())
{
newOffset++;
thIt.next();
}
while (oIt.hasNext())
newPath.add(oIt.next());
return new VirtualAddress(VirtualAddressParser.getNormalizedString(newOffset, newPath), newPath, true, newOffset);
}
public @NotNull VirtualAddress resolve(@NotNull VirtualAddress other)
{
if (!other.relative)
return other;
int removedComponentsSigned = other.rootOffset - this.components.size();
int newOffset = this.rootOffset + Math.max(removedComponentsSigned, 0);
if (!this.relative && newOffset > 0)
throw new IllegalArgumentException("Cannot resolve a relative address against an absolute address that would make it higher than the root!");
int compListEnd = Math.max(-removedComponentsSigned, 0);
var componentsNew = Stream.concat(this.components.stream().limit(compListEnd), other.components.stream()).toList();
return new VirtualAddress(VirtualAddressParser.getNormalizedString(newOffset, componentsNew), componentsNew, this.relative, newOffset);
}
public @NotNull VirtualAddress subAddress(int startIndex, int endIndex)
{
var componentCount = this.components.size();
var maxComponents = this.rootOffset + componentCount;
if (startIndex == 0 && endIndex == 0)
{
var components = List.<String>of();
return new VirtualAddress(VirtualAddressParser.getNormalizedString(0, components), components, this.relative, 0);
}
if (startIndex < 0 || endIndex > maxComponents || endIndex > startIndex)
throw new IndexOutOfBoundsException();
var newComponents = this.components.subList(startIndex - this.rootOffset, endIndex - this.rootOffset);
var newOffset = Math.max(0, this.rootOffset - startIndex);
return new VirtualAddress(VirtualAddressParser.getNormalizedString(newOffset, newComponents), newComponents, this.relative, newOffset);
}
public boolean startsWith(@NotNull VirtualAddress other)
{
return this.fullPath.startsWith(other.fullPath);
}
public boolean endsWith(@NotNull VirtualAddress other)
{
return this.fullPath.endsWith(other.fullPath);
}
public static class KeyAddrDeserializer extends KeyDeserializer
{
@Override
public VirtualAddress deserializeKey(String key, DeserializationContext ctxt)
{
return VirtualAddress.parse(key, false);
}
}
public static class Serializer extends StdSerializer<VirtualAddress>
{
public Serializer()
{
super(VirtualAddress.class);
}
@Override
public void serialize(VirtualAddress value, JsonGenerator gen, SerializerProvider provider) throws IOException
{
gen.writeString(value.fullPath);
}
}
public static class Deserializer extends StdDeserializer<VirtualAddress>
{
public Deserializer()
{
super(VirtualAddress.class);
}
@Override
public VirtualAddress deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
return VirtualAddress.parse(p.getValueAsString(), false);
}
}
}

View File

@ -0,0 +1,9 @@
package org.plutoengine.address;
public class VirtualAddressParseException extends RuntimeException
{
public VirtualAddressParseException(String message)
{
super(message);
}
}

View File

@ -0,0 +1,155 @@
package org.plutoengine.address;
import org.jetbrains.annotations.Range;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
public class VirtualAddressParser
{
private enum State
{
HIERARCHY_UP,
KEY,
PATH_SEPARATOR
}
private int position;
private int depth;
private State state;
private final Deque<String> components;
private StringBuilder tokenBuilder;
private final boolean permitRelative;
// Decrement for every root-level ~
@Range(from = 0, to = Integer.MAX_VALUE)
private int rootOffset = 0;
VirtualAddressParser(boolean permitRelative)
{
this.depth = 0;
this.position = 0;
this.state = State.PATH_SEPARATOR;
this.components = new ArrayDeque<>(VirtualAddress.MAX_KEYS);
this.permitRelative = permitRelative;
}
public void accept(int codepoint)
{
switch (this.state)
{
case PATH_SEPARATOR -> {
if (Character.isLetter(codepoint))
{
if (this.components.size() >= VirtualAddress.MAX_KEYS)
throw new VirtualAddressParseException("Max amount of keys (%d) exceeded!".formatted(VirtualAddress.MAX_KEYS));
this.state = State.KEY;
this.tokenBuilder = new StringBuilder(VirtualAddress.MAX_KEY_LENGTH);
this.tokenBuilder.appendCodePoint(codepoint);
}
else if (codepoint == VirtualAddress.TOKEN_HIERARCHY_UP)
{
if (!this.permitRelative)
throw new VirtualAddressParseException("Cannot use the hierarchy-up token (%s) in a non-relative context.".formatted(VirtualAddress.TOKEN_HIERARCHY_UP));
this.state = State.HIERARCHY_UP;
this.tokenBuilder = null;
if (!this.components.isEmpty())
this.components.removeLast();
else
this.rootOffset++;
}
else
{
throw new VirtualAddressParseException("Unexpected character at position %d: %s".formatted(this.position, Character.toString(codepoint)));
}
}
case KEY -> {
if (Character.isLetterOrDigit(codepoint) || codepoint == '_' || codepoint == '-')
{
if (this.tokenBuilder.length() >= VirtualAddress.MAX_KEY_LENGTH)
throw new VirtualAddressParseException("Single key length (%d) exceeded!".formatted(VirtualAddress.MAX_KEY_LENGTH));
this.state = State.KEY;
this.tokenBuilder.appendCodePoint(codepoint);
}
else if (codepoint == VirtualAddress.TOKEN_PATH_SEPARATOR)
{
if (this.depth >= VirtualAddress.MAX_KEYS)
throw new VirtualAddressParseException("Max amount of keys (%d) exceeded!".formatted(VirtualAddress.MAX_KEYS));
if (this.state != State.HIERARCHY_UP)
this.components.addLast(this.tokenBuilder.toString());
this.state = State.PATH_SEPARATOR;
this.depth++;
}
else
{
throw new VirtualAddressParseException("Unexpected character at position %d: %s".formatted(this.position, Character.toString(codepoint)));
}
}
case HIERARCHY_UP -> {
if (codepoint == VirtualAddress.TOKEN_HIERARCHY_UP)
{
if (!this.components.isEmpty())
this.components.removeLast();
else
this.rootOffset++;
}
else if (codepoint == VirtualAddress.TOKEN_PATH_SEPARATOR)
{
if (this.depth >= VirtualAddress.MAX_KEYS)
throw new VirtualAddressParseException("Max amount of keys (%d) exceeded!".formatted(VirtualAddress.MAX_KEYS));
this.state = State.PATH_SEPARATOR;
this.depth++;
}
else
{
throw new VirtualAddressParseException("Unexpected character at position %d: %s".formatted(this.position, Character.toString(codepoint)));
}
}
}
this.position++;
if (this.rootOffset >= VirtualAddress.MAX_KEYS)
throw new VirtualAddressParseException("Cannot move than %d levels up in the hierarchy!".formatted(VirtualAddress.MAX_KEYS));
}
public VirtualAddress build()
{
if (this.state == State.KEY && this.tokenBuilder != null)
this.components.addLast(this.tokenBuilder.toString());
var normalizedAddress = getNormalizedString(this.rootOffset, this.components);
return new VirtualAddress(normalizedAddress, List.copyOf(this.components), this.permitRelative, this.rootOffset);
}
static String getNormalizedString(int rootOffset, Iterable<String> components)
{
var separator = Character.toString(VirtualAddress.TOKEN_PATH_SEPARATOR);
var componentsJoined = String.join(separator, components);
var sb = new StringBuilder();
sb.append(Character.toString(VirtualAddress.TOKEN_HIERARCHY_UP).repeat(rootOffset));
if (rootOffset > 0 && !componentsJoined.isEmpty())
sb.append(separator);
sb.append(componentsJoined);
return sb.toString();
}
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.chrono;
package org.plutoengine.chrono;
import java.util.concurrent.TimeUnit;
@ -45,19 +45,8 @@ public class MiniTime
{
private static class MiniTimeParseException extends RuntimeException
{
/**
*
*/
private static final long serialVersionUID = -5403949842120041373L;
public MiniTimeParseException()
{
super("Time period could not be parsed. Correct format: \\_w\\_d\\_h\\_m\\_s **without spaces** between the units. You can skip a time unit. Example: 1h15m");
}
}
private static final TimeUnit miliseconds = TimeUnit.MILLISECONDS;
private static final int DAYS_IN_WEEK = 7;
private static final int HOURS_IN_DAY = 24;
private static final int MINUTES_IN_HOUR = 60;
@ -79,9 +68,7 @@ public class MiniTime
public static long parse(String input)
{
if (input == null)
{
throw new IllegalArgumentException("MiniTime string cannot be null!");
}
throw new NullPointerException();
// Nothing to parse
if (input.isEmpty())
@ -148,33 +135,28 @@ public class MiniTime
switch (type.toLowerCase())
{
case "w":
case "W":
case "w" -> {
allow = Integer.MAX_VALUE;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_WEEK * MILLIS_IN_MINUTE;
break;
case "d":
case "D":
}
case "d" -> {
allow = DAYS_IN_WEEK;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * MILLIS_IN_MINUTE;
break;
case "h":
case "H":
}
case "h" -> {
allow = HOURS_IN_DAY;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * MILLIS_IN_MINUTE;
break;
case "m":
case "M":
}
case "m" -> {
allow = MINUTES_IN_HOUR;
multiplier = SECONDS_IN_MINUTE * MILLIS_IN_MINUTE;
break;
case "s":
case "S":
}
case "s" -> {
allow = SECONDS_IN_MINUTE;
multiplier = MILLIS_IN_MINUTE;
break;
default:
break;
}
default -> {
}
}
// The top one can be more than it normally could have, for example you can
@ -189,7 +171,7 @@ public class MiniTime
throw new MiniTimeParseException();
}
time += multiplier * number;
time += (long) multiplier * number;
}
return System.currentTimeMillis() + time;
@ -269,17 +251,17 @@ public class MiniTime
throw new IllegalArgumentException("Negative time span cannot be converted to MiniTime.");
}
var xweeks = miliseconds.toDays(diff) / DAYS_IN_WEEK;
var xweeks = TimeUnit.MILLISECONDS.toDays(diff) / DAYS_IN_WEEK;
if (xweeks > Integer.MAX_VALUE)
{
return "forever";
}
var xdays = miliseconds.toDays(diff) % DAYS_IN_WEEK;
var xhours = miliseconds.toHours(diff) % HOURS_IN_DAY;
var xminutes = miliseconds.toMinutes(diff) % MINUTES_IN_HOUR;
var xseconds = miliseconds.toSeconds(diff) % SECONDS_IN_MINUTE;
var xdays = TimeUnit.MILLISECONDS.toDays(diff) % DAYS_IN_WEEK;
var xhours = TimeUnit.MILLISECONDS.toHours(diff) % HOURS_IN_DAY;
var xminutes = TimeUnit.MILLISECONDS.toMinutes(diff) % MINUTES_IN_HOUR;
var xseconds = TimeUnit.MILLISECONDS.toSeconds(diff) % SECONDS_IN_MINUTE;
return formatTime(xweeks, xdays, xhours, xminutes, xseconds);
}

View File

@ -4,4 +4,4 @@
* @author 493msi
*
*/
package cz.tefek.pluto.chrono;
package org.plutoengine.chrono;

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.event.lambda;
package org.plutoengine.event.lambda;
import java.util.ArrayList;
import java.util.List;

View File

@ -0,0 +1,66 @@
package org.plutoengine.io.property;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
public class YAMLPropertiesReader
{
public static Map<String, String> loadFromFile(Path file) throws IOException, YAMLException
{
try (var br = Files.newBufferedReader(file))
{
var yaml = new Yaml(new Constructor() {
private void recursivelyBuildTree(Map<String, String> map, String accessor, Node node)
{
if (node instanceof MappingNode mappingNode)
{
var kvps = mappingNode.getValue();
for (var kvp : kvps)
{
var key = kvp.getKeyNode();
if (key.getTag() != Tag.STR || !(key instanceof ScalarNode))
throw new YAMLException("All keys in property trees must be strings!");
var valueNode = kvp.getValueNode();
var newAccessorFormat = valueNode instanceof MappingNode ? "%s%s." : "%s%s";
this.recursivelyBuildTree(map, newAccessorFormat.formatted(accessor, super.constructScalar((ScalarNode) key)), valueNode);
}
}
else if (node instanceof ScalarNode scalarNode)
{
map.put(accessor, super.constructScalar(scalarNode));
}
else
{
throw new YAMLException("Invalid node tag: %s".formatted(node.getTag()));
}
}
@Override
protected Object constructObject(Node node)
{
var propertyMap = new LinkedHashMap<String, String>();
this.recursivelyBuildTree(propertyMap, "", node);
return propertyMap;
}
});
return yaml.load(br);
}
}
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.math;
package org.plutoengine.math;
/**
* A clamped sine wave generator, for animations, mostly.

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.math;
package org.plutoengine.math;
/**
* A class to generate a cubic bezier interpolation function. Not very
@ -10,7 +10,7 @@ package cz.tefek.pluto.math;
public class CubicBezier
{
private static final int iterations = 16;
private double a, b, c, d;
private final double a, b, c, d;
/**
* Creates a new {@code CubicBezier} from the given parameters.
@ -62,23 +62,22 @@ public class CubicBezier
double t = 0.5;
double x;
double y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
// Resulting Y value
double result = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
double delta = 0.25;
boolean uh;
for (int i = 0; i < iterations; i++)
{
x = 3 * (1 - t) * (1 - t) * t * this.a + 3 * (1 - t) * t * t * this.c + t * t * t;
y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
result = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
uh = x > xIn;
t += uh ? -delta : delta;
t += x > xIn ? -delta : delta;
delta /= 2;
}
return y;
return result;
}
}

View File

@ -1,8 +1,9 @@
package cz.tefek.pluto.math;
package org.plutoengine.math;
/**
* A class to generate a cubic bezier interpolation function. This
* implementation creates a lookup table.
* implementation creates a lookup table, so create it one and then
* use it with basically no speed penalty.
*
* @since 0.2
* @author 493msi
@ -42,7 +43,7 @@ public class CubicBezierLT
}
/**
* Retrives the approximate value for the given x and the supplied
* Retrieves the approximate value for the given x and the supplied
* parameters in the constructor.
*
* @param xIn the input X position

View File

@ -4,4 +4,4 @@
* @author 493msi
*
*/
package cz.tefek.pluto.engine.math;
package org.plutoengine.math;

View File

@ -0,0 +1,468 @@
package org.plutoengine.util.color;
import javax.annotation.Nonnull;
/**
* A simple 8-bit RGBA color container.
*
* <p><em>
* Some methods mutate the object to avoid new object creation.
* These methods are prefixed with "store".
* </em></p>
*
* @implNote Each of the color components is stored separately as a 32-bit integer
* to avoid unnecessary type conversion at the cost of some memory.
*
* <p>
* This however should not be a problem as <em>this class is not designed
* for large-scale or performance-sensitive color operations</em>.
* </p>
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public final class Color
{
// Black and white
public static Color WHITE = new Color(255, 255, 255);
public static Color BLACK = new Color(0, 0, 0);
// Shades of gray
public static Color VERY_DARK_GRAY = new Color(40, 40, 40);
public static Color DARK_GRAY = new Color(85, 85, 85);
public static Color GRAY = new Color(128, 128, 128);
public static Color SILVER = new Color(192, 192, 192);
public static Color LIGHT_GRAY = new Color(212, 212, 212);
// Basic colors
public static Color RED = new Color(255, 0, 0);
public static Color GREEN = new Color(0, 255, 0);
public static Color BLUE = new Color(0, 0, 255);
public static Color YELLOW = new Color(255, 255, 0);
public static Color TRANSPARENT = new Color(0, 0, 0, 0);
public static Color TRANSPARENT_WHITE = new Color(255, 255, 255, 0);
public static Color AMBER = new Color(255, 190, 0);
public static Color AMETHYST = new Color(153, 102, 204);
public static Color APRICOT = new Color(235, 147, 115);
public static Color AZURE = new Color(0, 57, 169);
public static Color BROWN = new Color(150, 75, 0);
public static Color COBALT = new Color(0, 71, 171);
public static Color COPPER = new Color(184, 115, 51);
public static Color CORAL_RED = new Color(255, 50, 60);
public static Color CORNFLOWER_BLUE = new Color(112, 112, 255);
public static Color CRIMSON = new Color(220, 26, 64);
public static Color CYAN = new Color(0, 188, 212);
public static Color DARK_BROWN = new Color(66, 33, 0);
public static Color DARK_GREEN = new Color(0, 150, 0);
public static Color LIGHT_AZURE = new Color(0, 128, 255);
public static Color LIME = new Color(191, 255, 0);
public static Color MAGENTA = new Color(255, 0, 255);
public static Color MALACHITE = new Color(11, 218, 81);
public static Color NAVY_BLUE = new Color(0, 0, 128);
public static Color OBSIDIAN = new Color(16, 18, 29);
public static Color ORANGE = new Color(255, 102, 0);
public static Color ORANGE_RED = new Color(255, 55, 0);
public static Color PEAR = new Color(209, 226, 49);
public static Color PINK = new Color(253, 109, 134);
public static Color PUMPKIN_ORANGE = new Color(255, 117, 0);
public static Color SAPPHIRE = new Color(47, 81, 158);
public static Color TEAL = new Color(0, 140, 140);
public static Color TURQUOISE = new Color(10, 255, 141);
public static Color PASTEL_PINK = new Color(255, 192, 203);
public static Color PASTEL_LIME = new Color(221, 255, 192);
public static Color PASTEL_YELLOW = new Color(252, 255, 192);
public static Color PASTEL_CYAN = new Color(192, 255, 234);
public static Color PASTEL_BLUE = new Color(192, 199, 255);
public static Color PASTEL_VIOLET = new Color(192, 199, 255);
public int red;
public int green;
public int blue;
public int alpha = 255;
/**
* Creates a new Color object from the supplied RGBA color components.
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public Color(int red, int green, int blue, int alpha)
{
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
}
/**
* Creates a new Color object from the supplied RGBA color components.
*
* Alpha is set to 255 by default.
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public Color(int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
}
/**
* Converts the supplied float-based {@link IRGBA} color object to a new {@link Color} object and returns it.
*
* @return A new {@link Color} object
*
* @param colorComponents An {@link IRGBA} color object
*
* @implNote Color values are rounded to the nearest integer.
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color from(@Nonnull IRGBA colorComponents)
{
return new Color(Math.round(colorComponents.red() * 255),
Math.round(colorComponents.green() * 255),
Math.round(colorComponents.blue() * 255),
Math.round(colorComponents.alpha() * 255));
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color from(@Nonnull IRGB colorComponents)
{
return new Color(Math.round(colorComponents.red() * 255),
Math.round(colorComponents.green() * 255),
Math.round(colorComponents.blue() * 255));
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color from(int color, @Nonnull EnumColorFormat colorFormat)
{
return switch (colorFormat)
{
case CF_INT_BGR -> new Color(color & 0xff, (color >> 8) & 0xff, (color >> 16) & 0xff);
case CF_INT_RGB -> new Color((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
case CF_INT_ABGR -> new Color(color & 0xff, (color >> 8) & 0xff, (color >> 16) & 0xff, (color >> 24) & 0xff);
case CF_INT_ARGB -> new Color((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, (color >> 24) & 0xff);
case CF_INT_BGRA -> new Color((color >> 8) & 0xff, (color >> 16) & 0xff, (color >> 24) & 0xff, color & 0xff);
case CF_INT_RGBA -> new Color((color >> 24) & 0xff, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
default -> throw new UnsupportedOperationException("Use the from(byte[], int, ColorFormat) for byte color formats!");
};
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color fromAWT(@Nonnull java.awt.Color color)
{
return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color from(@Nonnull byte[] color, @Nonnull EnumColorFormat colorFormat)
{
return from(color, 0, colorFormat);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public static Color from(@Nonnull byte[] color, int offset, @Nonnull EnumColorFormat colorFormat)
{
return switch (colorFormat)
{
case CF_3BYTE_BGR -> new Color(color[offset + 2] & 0xFF, color[offset + 1] & 0xFF, color[offset] & 0xFF);
case CF_3BYTE_RGB -> new Color(color[offset] & 0xFF, color[offset + 1] & 0xFF, color[offset + 2] & 0xFF);
case CF_4BYTE_ABGR -> new Color(color[offset + 3] & 0xFF, color[offset + 2] & 0xFF, color[offset + 1] & 0xFF, color[offset] & 0xFF);
case CF_4BYTE_ARGB -> new Color(color[offset + 1] & 0xFF, color[offset + 2] & 0xFF, color[offset + 3] & 0xFF, color[offset] & 0xFF);
case CF_4BYTE_BGRA -> new Color(color[offset + 2] & 0xFF, color[offset + 1] & 0xFF, color[offset] & 0xFF, color[offset + 3] & 0xFF);
case CF_4BYTE_RGBA -> new Color(color[offset] & 0xFF, color[offset + 1] & 0xFF, color[offset + 2] & 0xFF, color[offset + 3] & 0xFF);
default -> throw new UnsupportedOperationException("Use the from(int, ColorFormat) for int color formats!");
};
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getIntRGBA()
{
return get(EnumColorFormat.CF_INT_RGBA);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getIntARGB()
{
return get(EnumColorFormat.CF_INT_ARGB);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int get(@Nonnull EnumColorFormat colorFormat)
{
return switch (colorFormat)
{
case CF_INT_BGR -> (this.blue << 16) | (this.green << 8) | this.red;
case CF_INT_RGB -> (this.red << 16) | (this.green << 8) | this.blue;
case CF_INT_ABGR -> (this.alpha << 24) | (this.blue << 16) | (this.green << 8) | this.red;
case CF_INT_ARGB -> (this.alpha << 24) | (this.red << 16) | (this.green << 8) | this.blue;
case CF_INT_BGRA -> (this.blue << 24) | (this.green << 16) | (this.red << 8) | this.alpha;
case CF_INT_RGBA -> (this.red << 24) | (this.green << 16) | (this.blue << 8) | this.alpha;
default -> throw new UnsupportedOperationException("Use the get(ColorFormat, byte[], int) for byte color formats!");
};
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void get(@Nonnull EnumColorFormat colorFormat, @Nonnull byte[] dataOut)
{
get(colorFormat, dataOut, 0);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void get(@Nonnull EnumColorFormat colorFormat, @Nonnull byte[] dataOut, int offset)
{
switch (colorFormat)
{
case CF_3BYTE_BGR -> {
dataOut[offset++] = (byte) this.blue;
dataOut[offset++] = (byte) this.green;
dataOut[offset] = (byte) this.red;
}
case CF_3BYTE_RGB -> {
dataOut[offset++] = (byte) this.red;
dataOut[offset++] = (byte) this.green;
dataOut[offset] = (byte) this.blue;
}
case CF_4BYTE_ABGR -> {
dataOut[offset++] = (byte) this.alpha;
dataOut[offset++] = (byte) this.blue;
dataOut[offset++] = (byte) this.green;
dataOut[offset] = (byte) this.red;
}
case CF_4BYTE_ARGB -> {
dataOut[offset++] = (byte) this.alpha;
dataOut[offset++] = (byte) this.red;
dataOut[offset++] = (byte) this.green;
dataOut[offset] = (byte) this.blue;
}
case CF_4BYTE_BGRA -> {
dataOut[offset++] = (byte) this.blue;
dataOut[offset++] = (byte) this.green;
dataOut[offset++] = (byte) this.red;
dataOut[offset] = (byte) this.alpha;
}
case CF_4BYTE_RGBA -> {
dataOut[offset++] = (byte) this.red;
dataOut[offset++] = (byte) this.green;
dataOut[offset++] = (byte) this.blue;
dataOut[offset] = (byte) this.alpha;
}
default -> throw new UnsupportedOperationException("Use the get(ColorFormat) for int color formats!");
}
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getRed()
{
return this.red;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getGreen()
{
return this.green;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getBlue()
{
return this.blue;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public int getAlpha()
{
return this.alpha;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGB getFloatComponentsRGB()
{
return new RGB(this.red / 255.0f, this.green / 255.0f, this.blue / 255.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGBA getFloatComponentsRGBA()
{
return new RGBA(this.red / 255.0f, this.green / 255.0f, this.blue / 255.0f, this.alpha / 255.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSB getFloatComponentsHSB()
{
return this.getFloatComponentsRGB().toHSB();
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSBA getFloatComponentsHSBA()
{
return this.getFloatComponentsRGBA().toHSBA();
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void storeFloatComponentsRGBA(@Nonnull RGBA target)
{
storeFloatComponentsRGB(target);
target.a = this.alpha / 255.0f;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void storeFloatComponentsRGB(@Nonnull RGB target)
{
target.r = this.red / 255.0f;
target.g = this.green / 255.0f;
target.b = this.blue / 255.0f;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void storeFloatComponentsHSBA(@Nonnull HSBA target)
{
var hsb = this.getFloatComponentsHSBA();
target.h = hsb.h;
target.s = hsb.s;
target.b = hsb.b;
target.a = hsb.a;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public void storeFloatComponentsHSB(@Nonnull HSB target)
{
var hsb = this.getFloatComponentsHSB();
target.h = hsb.h;
target.s = hsb.s;
target.b = hsb.b;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public java.awt.Color toAWT()
{
return new java.awt.Color(this.red, this.green, this.blue, this.alpha);
}
}

View File

@ -0,0 +1,81 @@
package org.plutoengine.util.color;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public enum EnumColorFormat
{
/**
* 8-bit RGBA stored in a big-endian 32-bit integer
*/
CF_INT_RGBA(4),
/**
* 8-bit BGRA stored in a big-endian 32-bit integer
*/
CF_INT_BGRA(4),
/**
* 8-bit ARGB stored in a big-endian 32-bit integer
*/
CF_INT_ARGB(4),
/**
* 8-bit ABGR stored in a big-endian 32-bit integer
*/
CF_INT_ABGR(4),
/**
* 8-bit RGB stored in a big-endian 32-bit integer, the highest 8-bits are unused
*/
CF_INT_RGB(4),
/**
* 8-bit RGB stored in a big-endian 32-bit integer, the highest 8-bits are unused
*/
CF_INT_BGR(4),
/**
* 8-bit RGBA, one byte per color component
*/
CF_4BYTE_RGBA(4),
/**
* 8-bit BGRA, one byte per color component
*/
CF_4BYTE_BGRA(4),
/**
* 8-bit ARGB, one byte per color component
*/
CF_4BYTE_ARGB(4),
/**
* 8-bit ABGR, one byte per color component
*/
CF_4BYTE_ABGR(4),
/**
* 8-bit RGB, one byte per color component
*/
CF_3BYTE_RGB(3),
/**
* 8-bit BGR, one byte per color component
*/
CF_3BYTE_BGR(3);
private final int size;
EnumColorFormat(int size)
{
this.size = size;
}
/**
* Returns the size in bytes.
*
* @return The size of the color format, in bytes
*/
public int getSize()
{
return this.size;
}
}

View File

@ -0,0 +1,139 @@
package org.plutoengine.util.color;
import org.apache.commons.lang3.Range;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public class HSB
{
/**
* Hue [0°..360°]
* */
protected float h;
/**
* Saturation [0..1]
* */
protected float s;
private final static Range<Float> SATURATION_RANGE = Range.between(0.0f, 1.0f);
/**
* Value/Brightness [0..1]
* */
protected float b;
private final static Range<Float> BRIGHTNESS_RANGE = Range.between(0.0f, 1.0f);
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSB(float hue, float saturation, float brightness)
{
this.h = (360.0f + hue % 360.0f) % 360.0f;
this.s = SATURATION_RANGE.fit(saturation);
this.b = BRIGHTNESS_RANGE.fit(brightness);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGB toRGB()
{
return this.toRGBA(1.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGB toRGBA()
{
return this.toRGBA(1.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGBA toRGBA(float alpha)
{
float h6 = this.h / 60.0f;
int hueSide = (int) h6;
// The color component furthest on the hue wheel
float p = this.b * (1 - this.s);
float hueFractCCW = h6 - hueSide;
// The second-nearest color component on the hue wheel - counter-clockwise
float q = this.b * (1 - hueFractCCW * this.s);
float hueFractCW = 1 - hueFractCCW;
// The second-nearest color component on the hue wheel - clockwise
float t = this.b * (1 - hueFractCW * this.s);
return switch (hueSide % 6)
{
// Hues 60°-119° -- Green is the brightest color, no blue is present at max saturation
case 1 -> new RGBA(q, this.b, p, alpha);
// Hues 120°-179° -- Green is the brightest color, no red is present at max saturation
case 2 -> new RGBA(p, this.b, t, alpha);
// Hues 180°-239° -- Blue is the brightest color, no red is present at max saturation
case 3 -> new RGBA(p, q, this.b, alpha);
// Hues 240°-299° -- Blue is the brightest color, no green is present at max saturation
case 4 -> new RGBA(t, p, this.b, alpha);
// Hues 300°-359° -- Red is the brightest color, no green is present at max saturation
case 5 -> new RGBA(this.b, p, q, alpha);
// Hues 0°-59° -- Red is the brightest color, no blue is present at max saturation
case 0 -> new RGBA(this.b, t, p, alpha);
default -> throw new IllegalStateException("This HSB object's hue is negative - this is not legal.");
};
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float hue()
{
return this.h;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float saturation()
{
return this.s;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float brightness()
{
return this.b;
}
}

View File

@ -0,0 +1,51 @@
package org.plutoengine.util.color;
import org.apache.commons.lang3.Range;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public class HSBA extends HSB
{
protected float a;
private final static Range<Float> ALPHA_RANGE = Range.between(0.0f, 1.0f);
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSBA(float hue, float saturation, float lightness, float alpha)
{
super(hue, saturation, lightness);
this.a = ALPHA_RANGE.fit(alpha);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float alpha()
{
return this.a;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
@Override
public RGB toRGBA()
{
return this.toRGBA(this.a);
}
}

View File

@ -0,0 +1,31 @@
package org.plutoengine.util.color;
/**
* An interface for single precision RGB color objects.
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public interface IRGB
{
/**
* Returns the red color component.
*
* @return The red component, in range 0..1
*/
float red();
/**
* Returns the green color component.
*
* @return The green component, in range 0..1
*/
float green();
/**
* Returns the blue color component.
*
* @return The blue color component, in range 0..1
*/
float blue();
}

View File

@ -0,0 +1,17 @@
package org.plutoengine.util.color;
/**
* An interface for single precision RGBA color objects.
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public interface IRGBA extends IRGB
{
/**
* Returns the alpha color component.
*
* @return The alpha component, in range 0..1
*/
float alpha();
}

View File

@ -0,0 +1,121 @@
package org.plutoengine.util.color;
import org.apache.commons.lang3.math.NumberUtils;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public class RGB implements IRGB
{
protected float r;
protected float g;
protected float b;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGB(float r, float g, float b)
{
this.r = r;
this.g = g;
this.b = b;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSBA toHSB()
{
return this.toHSBA(1.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSBA toHSBA()
{
return this.toHSBA(1.0f);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public HSBA toHSBA(float alpha)
{
float brightness = NumberUtils.max(this.r, this.g, this.b);
float min = NumberUtils.min(this.r, this.g, this.b);
if (brightness == 0)
return new HSBA(0, 0, 0, alpha);
float chroma = brightness - min;
if (chroma == 0)
return new HSBA(0, 0, brightness, alpha);
float saturation = chroma / brightness;
float hue;
if (brightness == this.r)
if (this.g < this.b)
hue = (this.g - this.b) / chroma + 6;
else
hue = (this.g - this.b) / chroma;
else if (brightness == this.g)
hue = (this.b - this.r) / chroma + 2;
else
hue = (this.r - this.g) / chroma + 4;
hue *= 60;
return new HSBA(hue, saturation, brightness, alpha);
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float red()
{
return this.r;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float green()
{
return this.g;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float blue()
{
return this.b;
}
}

View File

@ -0,0 +1,47 @@
package org.plutoengine.util.color;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public class RGBA extends RGB implements IRGBA
{
protected float a;
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGBA(float r, float g, float b, float a)
{
super(r, g, b);
this.a = a;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public float alpha()
{
return this.a;
}
/**
* TODO
*
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
@Override
public HSBA toHSBA()
{
return this.toHSBA(this.a);
}
}

View File

@ -0,0 +1,44 @@
package org.plutoengine.address;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
public class VirtualAddressTest
{
public static void main(String[] args)
{
System.out.println();
var basic = VirtualAddress.parse("cz.tefek.pluto");
System.out.println(basic);
// ~.~ - ~.~.~.a => ~.a
// a.b.c.d - ~.a.b.c.d => ~.~.~.~.~.a.b.c.d
// a.b.c.d - d => ~.~.~.~.d
// a.b.x.d - a.b.c.d => ~.~.c.d
// a.b.c - d => ~.~.~.d
// a.b.c - a.b => ~
// a.b.c - a.e.g => ~.~.e.g
var testCases = List.of(
Pair.of("~.~", "~.~.~.a"),
Pair.of("a.b.c.d", "~.a.b.c.d"),
Pair.of("a.b.c.d", "d"),
Pair.of("a.b.x.d", "a.b.c.d"),
Pair.of("a.b.c", "d"),
Pair.of("a.b.c", "a.b"),
Pair.of("a.b.c", "a.e.g"),
Pair.of("a.b.c", "a.b.c"),
Pair.of("a.b.c", "a.b.c.d")
);
testCases.forEach(pair -> {
var a = VirtualAddress.parse(pair.getLeft(), true);
var b = VirtualAddress.parse(pair.getRight(), true);
System.out.printf("\"%s\" - \"%s\" => \"%s\"%n", a, b, a.relativize(b));
});
System.out.println();
}
}

View File

@ -0,0 +1,10 @@
plugins {
java
`java-library`
}
description = ""
dependencies {
api(project(":plutoengine:plutodisplay"))
}

View File

@ -1,9 +1,9 @@
package cz.tefek.pluto.engine.graphics.gl;
package org.plutoengine.graphics.gl;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL40;
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
import org.plutoengine.gl.IOpenGLEnum;
public enum DrawMode implements IOpenGLEnum
{
@ -26,9 +26,9 @@ public enum DrawMode implements IOpenGLEnum
TRIANGLE_STRIP_ADJACENCY(GL33.GL_TRIANGLE_STRIP_ADJACENCY),
PATCHES(GL40.GL_PATCHES);
private int glID;
private final int glID;
private DrawMode(int id)
DrawMode(int id)
{
this.glID = id;
}

View File

@ -1,6 +1,6 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
package org.plutoengine.graphics.gl.vao;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import org.plutoengine.graphics.gl.vao.attrib.data.VecArray;
/**
* @author 493msi

View File

@ -1,18 +1,18 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
package org.plutoengine.graphics.gl.vao;
import org.lwjgl.opengl.GL33;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.graphics.gl.DrawMode;
import org.plutoengine.graphics.gl.vbo.ArrayBuffer;
import org.plutoengine.graphics.gl.vbo.IndexArrayBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import org.lwjgl.opengl.GL33;
import org.lwjgl.system.MemoryUtil;
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
import cz.tefek.pluto.engine.graphics.gl.vbo.ArrayBuffer;
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class VertexArray
{

View File

@ -1,9 +1,9 @@
package cz.tefek.pluto.engine.graphics.gl.vao;
package org.plutoengine.graphics.gl.vao;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import cz.tefek.pluto.engine.graphics.gl.vbo.FloatArrayBuffer;
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
import org.plutoengine.graphics.gl.vao.attrib.ReservedAttributes;
import org.plutoengine.graphics.gl.vao.attrib.data.VecArray;
import org.plutoengine.graphics.gl.vbo.FloatArrayBuffer;
import org.plutoengine.graphics.gl.vbo.IndexArrayBuffer;
public class VertexArrayBuilder
{

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.graphics.gl.vao.attrib;
package org.plutoengine.graphics.gl.vao.attrib;
public class ReservedAttributes
{

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto.engine.graphics.gl.vao.attrib.data;
package org.plutoengine.graphics.gl.vao.attrib.data;
import java.lang.reflect.Array;

View File

@ -1,15 +1,12 @@
package cz.tefek.pluto.engine.graphics.gl.vbo;
package org.plutoengine.graphics.gl.vbo;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import org.plutoengine.graphics.gl.vao.attrib.data.VecArray;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import static org.lwjgl.opengl.GL15.*;
public abstract class ArrayBuffer<T extends VecArray<?>>
{
protected int glID = 0;
protected int glID;
private final int vertexDimensions;
private final int vertexCount;

View File

@ -1,8 +1,8 @@
package cz.tefek.pluto.engine.graphics.gl.vbo;
package org.plutoengine.graphics.gl.vbo;
import org.lwjgl.opengl.GL33;
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
import org.plutoengine.gl.IOpenGLEnum;
public enum EnumArrayBufferType implements IOpenGLEnum
{
@ -10,9 +10,9 @@ public enum EnumArrayBufferType implements IOpenGLEnum
INT(GL33.GL_INT),
UNSIGNED_INT(GL33.GL_UNSIGNED_INT);
private int glID;
private final int glID;
private EnumArrayBufferType(int glEnum)
EnumArrayBufferType(int glEnum)
{
this.glID = glEnum;
}

View File

@ -1,8 +1,7 @@
package cz.tefek.pluto.engine.graphics.gl.vbo;
package org.plutoengine.graphics.gl.vbo;
import org.lwjgl.opengl.GL33;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import org.plutoengine.graphics.gl.vao.attrib.data.VecArray;
public class FloatArrayBuffer extends ArrayBuffer<VecArray<float[]>>
{

View File

@ -1,11 +1,11 @@
package cz.tefek.pluto.engine.graphics.gl.vbo;
package org.plutoengine.graphics.gl.vbo;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
import org.plutoengine.graphics.gl.vao.attrib.data.VecArray;
public class IndexArrayBuffer extends ArrayBuffer<VecArray<int[]>>
{

View File

@ -0,0 +1,44 @@
import org.plutoengine.Versions
sourceSets {
java {
create("config") {
}
main {
java.srcDirs("$buildDir/generated/java")
}
}
}
tasks {
val generateConfigs = register("generateConfigs", Copy::class) {
val projectVariables = mapOf("plutoVersion" to Versions.versionFull)
inputs.properties(projectVariables)
from("src/config/java")
into("$buildDir/generated/java")
expand(projectVariables)
}
compileJava {
dependsOn(generateConfigs)
}
}
dependencies {
api(project(":plutoengine:plutolib"))
api(project(":plutoengine:plutocomponent"))
api(platform("org.lwjgl:lwjgl-bom:${Versions.lwjglVersion}"))
implementation("org.lwjgl:lwjgl")
implementation("org.lwjgl:lwjgl-xxhash")
implementation("org.lwjgl:lwjgl-zstd")
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-xxhash", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-zstd", classifier = Versions.lwjglNatives)
}

View File

@ -0,0 +1,11 @@
package org.plutoengine;
class PlutoVersionConfig
{
/**
* The combined version string.
*
* @since 20.2.0.0-alpha.3
* */
protected static final String VERSION = "$plutoVersion";
}

View File

@ -0,0 +1,13 @@
package org.plutoengine;
public interface IVersion<T> extends Comparable<T>
{
@Override
boolean equals(Object o);
@Override
int hashCode();
@Override
String toString();
}

View File

@ -1,4 +1,4 @@
package cz.tefek.pluto;
package org.plutoengine;
/**
* Constants shared by all Pluto libraries.
@ -7,65 +7,77 @@ package cz.tefek.pluto;
*
* @author 493msi
*/
public class Pluto
public class Pluto extends PlutoVersionConfig
{
/**
* The name of this engine.
*
* @since 20.2.0.0-alpha.3
* */
public static final String ENGINE_NAME = "PlutoEngine";
public static final boolean DEBUG_MODE = Boolean.parseBoolean(System.getProperty("cz.tefek.pluto.debug"));
// TODO: Automate setting the version numbers with gradle
/**
* The year version number, changes with breaking API changes.
*
* @since pre-alpha
* */
public static final int VERSION_YEAR = 20;
/**
* The major version number, changes with breaking API changes.
*
* @since pre-alpha
* */
public static final int VERSION_MAJOR = 2;
/**
* The minor version number, changes with backwards-compatible API changes.
*
* @since pre-alpha
* */
public static final int VERSION_MINOR = 0;
/**
* The patch number, changes with backwards-compatible fixes and patches.
*
* @since pre-alpha
* */
public static final int VERSION_PATCH = 0;
/**
* Denotes whether this build is a pre-release build.
*
* @since pre-alpha
* */
public static final boolean PRERELEASE = true;
/**
* The name of this pre-release, e.g. alpha, beta, RC and similar.
*
* @since pre-alpha
* */
public static final String PRERELEASE_NAME = "alpha";
/**
* The pre-release patch number, incremented by 1 with *any* pre-release update.
*
* @since pre-alpha
* */
public static final int PRERELEASE_PATCH = 2;
/**
* The combined version string.
*
* @since pre-alpha
* */
public static final String VERSION = VERSION_YEAR + "." + VERSION_MAJOR + "." + VERSION_MINOR + "." + VERSION_PATCH + (PRERELEASE ? "-" + PRERELEASE_NAME + "." + PRERELEASE_PATCH : "");
public static final String VERSION = PlutoVersionConfig.VERSION;
/**
* The version object.
*
* @since 20.2.0.0-alpha.3
* */
public static final PlutoVersion VERSION_OBJ = PlutoVersion.of(VERSION);
/**
* The year version number, changes with breaking API changes.
*
* @since pre-alpha
* */
public static final int VERSION_YEAR = VERSION_OBJ.getYear();
/**
* The major version number, changes with breaking API changes.
*
* @since pre-alpha
* */
public static final int VERSION_MAJOR = VERSION_OBJ.getMajor();
/**
* The minor version number, changes with backwards-compatible API changes.
*
* @since pre-alpha
* */
public static final int VERSION_MINOR = VERSION_OBJ.getMinor();
/**
* The patch number, changes with backwards-compatible fixes and patches.
*
* @since pre-alpha
* */
public static final int VERSION_PATCH = VERSION_OBJ.getPatch();
/**
* Denotes whether this build is a pre-release build.
*
* @since pre-alpha
* */
public static final boolean PRERELEASE = VERSION_OBJ.isPrerelease();
/**
* The name of this pre-release, e.g. alpha, beta, RC and similar.
*
* @since pre-alpha
* */
public static final String PRERELEASE_NAME = VERSION_OBJ.getPrereleaseName();
/**
* The pre-release patch number, incremented by 1 with *any* pre-release update.
*
* @since pre-alpha
* */
public static final int PRERELEASE_PATCH = VERSION_OBJ.getPrereleaseNumber();
}

View File

@ -0,0 +1,9 @@
package org.plutoengine;
import org.plutoengine.component.ComponentManager;
import org.plutoengine.component.PlutoGlobalComponent;
public class PlutoGlobal
{
public static final ComponentManager<PlutoGlobalComponent> COMPONENTS = new ComponentManager<>(PlutoGlobalComponent.class);
}

View File

@ -0,0 +1,33 @@
package org.plutoengine;
import org.plutoengine.address.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.component.ComponentManager;
/**
* @since 20.2.0.0-alpha.3
*
* @author 493msi
*/
@ThreadSensitive(localContexts = true)
public class PlutoLocal
{
private static final ThreadLocal<PlutoLocal> local = ThreadLocal.withInitial(PlutoLocal::new);
public final ComponentManager<PlutoLocalComponent> COMPONENTS;
private PlutoLocal()
{
this.COMPONENTS = new ComponentManager<>(PlutoLocalComponent.class);
}
public static PlutoLocal instance()
{
return local.get();
}
public static ComponentManager<PlutoLocalComponent> components()
{
return instance().COMPONENTS;
}
}

Some files were not shown because too many files have changed in this diff Show More