Compare commits

..

39 Commits

Author SHA1 Message Date
Natty 0ece56a871
Updated Libra 2023-03-27 19:57:06 +02:00
Natty 88c966974c
Rocket flames and fixed Maven repo URL 2023-03-27 19:55:46 +02:00
Natty 29e9f42bde
Removed pointless class hierarchy 2022-08-19 02:35:09 +02:00
Natty 54ade78b1d
Updated .gitignore to exclude out/ directories 2022-07-21 01:16:32 +02:00
Natty 6ba1dfa2cb
PlutoComponent cleanup 2022-06-22 01:37:27 +02:00
Natty 27f4882fcc
Used the integrated color buffer clear method 2022-05-10 01:13:16 +02:00
Natty d178a303bd
Code cleanup 2022-05-07 21:51:54 +02:00
Natty c1c248be87
Extra licensing info 2022-05-04 04:34:26 +02:00
Natty cbab0a8153
Extra licensing info 2022-05-04 04:23:52 +02:00
Natty 142c9fbed7
Licensed all .glsl sources 2022-05-04 02:34:59 +02:00
Natty a9719ede37
Added licenses to all .java files 2022-05-04 02:25:40 +02:00
Natty 12c9e6f8e9
Unified renderer subprojects, bump to 22.3.0.0-alpha.0 2022-05-04 01:51:03 +02:00
Natty 81064b6bdf
Updated README.md 2022-04-30 00:17:29 +02:00
Natty c580104d81
Optimized shader code 2022-04-29 23:57:19 +02:00
Natty 61ce682270
Randomized initial shooting cooldown 2022-04-27 19:36:14 +02:00
Natty 635df54849
Fixed fixed file permissions 2022-04-27 19:27:53 +02:00
Natty 085454d941
Fixed file permissions 2022-04-27 19:25:54 +02:00
Natty 1aaee9b17a
Added JSRClone, audio engine rewrite, bump to alpha.2 2022-04-27 19:24:18 +02:00
Natty 0a3026e3d9
Minor fixes 2022-04-26 02:56:09 +02:00
Natty 3082e88be9
Fixed SRClone font 2022-04-26 02:18:00 +02:00
Natty 376ebe1086
Updated libra 2022-04-19 22:44:49 +02:00
Natty 9f960de4f3
Undone Java source file perms 2022-04-19 22:44:18 +02:00
Natty 9df4da27ee
Bitmap font renderer, bug fixes and cleanup 2022-04-19 22:39:16 +02:00
Natty 6cbdd20731
Removed a debug print 2022-04-19 11:38:17 +02:00
Natty fe26a68c85
New file perms 2022-04-19 01:31:52 +02:00
Natty a7c6340751
Version bump 2022-04-18 12:54:28 +02:00
Natty 8e079a9c36
Made ModLoader opaque 2022-04-18 12:53:18 +02:00
Natty 249000c9d0
Updated README.md 2022-04-17 04:33:56 +02:00
Natty 304cb1f756
Init method for PlutoApplication 2022-04-17 04:21:13 +02:00
Natty 128577cdfa
Improved README.md 2022-04-16 19:10:17 +02:00
Natty 0e4ec26b62
Version bump, preparation for the layer subsystem implementation 2022-04-16 14:40:59 +02:00
Natty 7daa8c00c7
Fixed linear gradient rendering in the demo 2022-04-15 01:57:38 +02:00
Natty e3c37c6511
Fixed linear gradient rendering in the demo 2022-04-15 01:56:39 +02:00
Natty 6725e24135
Demo cleanup 2022-04-14 20:36:13 +02:00
Natty c79886d56d
Updated Libra 2022-04-14 20:33:51 +02:00
Natty 12ad72636b
SDF font rendering with proper kerning 2022-04-14 20:31:47 +02:00
Natty 6772912f66
PlutoLib cleanup, module-info.java and array uniforms for PlutoShader 2022-04-13 02:28:26 +02:00
Natty f04df2a6f8
Fixed GitHub actions to support submodules 2022-04-12 01:13:00 +02:00
Natty 7b7daafc15
Initial font renderer rewrite and Libra integration 2022-04-12 01:08:57 +02:00
417 changed files with 16281 additions and 3224 deletions

View File

@ -12,7 +12,10 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v2 - name: Check out the repository and all submodules
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2

5
.github/workflows/gradle-test-build.yml vendored Normal file → Executable file
View File

@ -14,7 +14,10 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v2 - name: Check out the repository and all submodules
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v2 uses: actions/setup-java@v2

3
.gitmodules vendored Executable file
View File

@ -0,0 +1,3 @@
[submodule "libra"]
path = libra
url = git@github.com:493msi/libra.git

35
LICENSING_INFO.txt Normal file
View File

@ -0,0 +1,35 @@
SOFTWARE LICENSING
==================
While PlutoEngine is licensed under the MIT license, see
individual libraries and their license requirements.
MEDIA LICENSING
===============
This repository contains some first-party media files, such as audio,
fonts and images. See "Third party media within this repository" for
exceptions.
Unless specified otherwise:
This content may be freely used under the https://creativecommons.org/publicdomain/zero/1.0/ license
as long as it is used within an application based on PlutoEngine and does not misrepresent
PlutoEngine or any of the PlutoEngine authors and does not remove this restriction.
OR
This content may be freely used under the https://creativecommons.org/licenses/by-nd/4.0/ license
with no further restrictions.
OR
This content may be freely used under the https://creativecommons.org/licenses/by-sa/4.0/ license
with no further restrictions.
Third party media within this repository
========================================
file "engine-demo/jsr-clone/mods/tefek.srclone/default/sound/game_st.ogg":
"Safehouse" by Selulance
https://www.youtube.com/watch?v=-kNx6sqReFk
Licensed under https://creativecommons.org/licenses/by/3.0/

View File

@ -1,41 +0,0 @@
## Features targeted for 22.1.0.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 22.2.0.0-alpha.0
* 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.
* Upon stage switch
1. Unload unused assets
2. Load new assets
* Provide multiple means of stage switching
* Three modes with the initial release
1. Instant switch
* Assets will be loaded and unloaded synchronously
* The stage switch will happen in one frame
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
3. Asynchronous switch
* Assets will be loaded in asynchronously, where applicable
* Falls back to deferred switching for synchronous loading,
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
* The stage manager should be relatively low-overhead and allow multiple
instances
* Allow stages to be inherited from, creating a stack-like structure
* `[PlutoAudio]` Integrate the Audio API with the Stage API

109
README.md
View File

@ -1,7 +1,56 @@
# plutoengine # plutoengine
![Build Status](https://github.com/493msi/plutoengine/workflows/Gradle%20Package/badge.svg)
![Maven Version](https://img.shields.io/github/v/tag/493msi/plutoengine?label=Latest%20Version)
My hobby game engine. This repository unifies all my previous Pluto repositories. My hobby game engine. This repository unifies all my previous Pluto repositories.
## How to use Pluto
**Quick start**
Download [the demo project](https://github.com/plutoengine/plutoengine-basic-demo) open it in IntelliJ IDEA.
**Gradle**
```groovy
repositories {
mavenCentral()
maven {
name = "Vega"
url = uri("https://vega.botdiril.com/releases")
}
}
dependencies {
implementation group: "org.plutoengine", name: "plutocore", version: "22.3.0.0-alpha.0"
}
```
**or for Gradle Kotlin DSL**
```kotlin
repositories {
mavenCentral()
maven {
name = "Vega"
url = uri("https://vega.botdiril.com/releases")
}
}
dependencies {
implementation("org.plutoengine", "plutocore", "22.3.0.0-alpha.0")
}
```
### Licensing
The code of PlutoEngine is licensed under the MIT license.
See [LICENSING_INFO](https://github.com/493msi/plutoengine/blob/master/LICENSING_INFO.txt) for further information.
### Versioning ### Versioning
All submodules share a version number for simplicity reasons. All submodules share a version number for simplicity reasons.
@ -21,54 +70,26 @@ version numbers.*
## Usability status of submodules ## Usability status of submodules
Keep in mind PlutoEngine is in alpha and all features are tentative.
The following list simply provides an overview of how likely breaking changes are to occur.
### Safe submodules ### Safe submodules
* **PlutoCore** - Stable * **PlutoCore** - Stable
* **PlutoFramebuffer** - Stable * **PlutoSpritesheet** - Stable, some features are unfinished
* **PlutoGUI** - Stable, awaiting a rewrite * **PlutoDisplay** - Stable, collision API nowhere near completion
* **PlutoMesher** - Stable * **PlutoLib** - Mostly stable
* **PlutoShader** - Stable * **PlutoRender** - Stable
* **PlutoTexture** - Stable * **PlutoRuntime** - Mostly stable
* **PlutoSpriteSheet** - Stable, some features are unfinished
* **PlutoDisplay** - Stable, collision API nowhere near completion
* **PlutoUSS2** - Stable
* **PlutoLib** - Mostly stable
### Unstable submodules ### Unstable submodules
* **PlutoRuntime** - Somewhat tentative, the module API has been rewritten and might contain bugs * **PlutoAudio** - Very tentative, work in progress
* **PlutoAudio** - Somewhat usable, unfinished * **PlutoGUI** - Recently rewritten, the API is highly unstable, work in progress
### Extensions
* **PlutoUSS2** - Stable
* **PlutoGameObject** - Stable
## Current priorities ## Current priorities
See `NEXT_RELEASE_DRAFT.md` for details. See [issues](https://github.com/493msi/plutoengine/issues) for details.
### Very high priority
[ *Implemented in the current release.* ]
* 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
### Normal priority
[ *Planned for an upcoming release.* ]
* The collision system for PlutoStatic
### 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 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

View File

@ -1,3 +1,66 @@
## 22.3.0.0-alpha.1
* `[PlutoComponent]` Removing components using a token should have the same semantics as removing individual components
* `[PlutoComponent]` Made the addition and removal of components hookable before mount events are fired
## 22.3.0.0-alpha.0
* `[SDK]` **Combined `PlutoFramebuffer`, `PlutoMesher`, `PlutoShader` and `PlutoTexture`
into `PlutoRender`**
* Unified the package structure
* `[SDK]` **Added the license text to all source files to conform with the MIT license requirements**
* `plutoengine-demos/jsr-clone` Added a proper license file for the music
## 22.2.0.0-alpha.2
* `[SDK]` The libraries now always reference natives for all architectures
* `[SDK]` Replaced `NEXT_RELEASE_DRAFT.md` with [an issue tracker](https://github.com/493msi/plutoengine/issues)
* `[PlutoAudio]` **Partial rewrite and support for managed sound effects**
* `plutoengine-demos/` **Added the `jsr-clone` demo**
* `[PlutoSpritesheet]` Renamed `TemporalSprite#getSideCount` to `getFrameCount`
## 22.2.0.0-alpha.1
* `[PlutoGUI]` **Added support for bitmap fonts**
* `[PlutoGUI]` Generalized the font renderer API
* `plutoengine-demos/` Removed third-party fonts
* `[PlutoRuntime]` The `ModLoader` component is now opaque,
pre-added mods are now specified when creating the token
* `[PlutoRuntime]` Added a linting annotation to `Logger#logf`
* `[PlutoRuntime]` Fixed a bug where creating relative resource paths
from URIs was not possible
* `[PlutoLib]` Fixed `toRGBA` in `HSB` and `HSBA`
* `[PlutoLib]` Added `BasicInterpolation`
* `[PlutoSpritesheet]` Added `recolor(IRGBA)` to `RectangleRenderer2D` and `Renderer2D`
* `[PlutoSpritesheet]` Added `TemporalSprite` and `OrientedSprite`
## 22.2.0.0-alpha.0
* `[PlutoComponent]` **Added support for dependencies and strengtened type checks**
* `[PlutoComponent]` *Removed* `IComponent` as it was redundant to `AbstractComponent`
* `[Pluto*]` *Removed* JSR 305 annotations in favor of JetBrains annotations
* `[Pluto*]` *Removed* the `ConstantExpression` annotation in favor of `Contract`
* `[PlutoRuntime]` *Moved* `ThreadSensitive` to `org.plutoengine.address`
* `[PlutoAudio]` Transformed into a PlutoLocalComponent
* `[PlutoAudio]` `IAudioStream` is now `AutoCloseable`
* `[PlutoCore]` `InputBus` is now tied to a specific `Display` instead of searching for one in the Local
* `[PlutoCore]` Separated `Mouse` and `Keyboard` from `InputBus` into child components
* `[PlutoCore]` Added an `init()` method that gets called before entering the main loop
## 22.1.0.0-alpha.1
* `plutoengine-demos/basic-application` Made the gradient in the fragment font shader rotatable
## 22.1.0.0-alpha.0
* `[PlutoMesher]` **Partial rewrite**
* *Removed* `VecArray`
* Reduced pointless abstraction
* Creation and destruction no longer logged
* `[PlutoGUI]` **Complete rewrite of the GUI library**
* `[Pluto*]` **Unified the cleanup methods of all OpenGL object classes to `close`**
* `[PlutoLib]` New dependency: `joml-primitives`
* `[PlutoLib]` Now has a `module-info.java`
* `[PlutoLib]` Now uses JetBrains annotations instead of JSR 305
* `[PlutoDisplay]` Removed the `flipped` word from all buffer functions
* `[PlutoRuntime]` Fixed opening .zip filesystems
* `[PlutoShader]` Added `UniformArrayFloat`, `UniformArrayRGBA`,
`UniformArrayVec2`, `UniformArrayVec3`, `UniformArrayVec4`
* `[PlutoRuntime]` `SmartSeverity.MODULE_CHECK` now correctly uses the standard output instead of `stderr`.
## 22.0.0.0-alpha.7 ## 22.0.0.0-alpha.7
* `[PlutoRuntime]` Fixed several resource filesystem bugs * `[PlutoRuntime]` Fixed several resource filesystem bugs

View File

@ -1,5 +1,7 @@
import org.plutoengine.Versions import org.plutoengine.Versions
project.ext["isPlutoBuild"] = true;
tasks.withType<Wrapper> { tasks.withType<Wrapper> {
distributionType = Wrapper.DistributionType.ALL distributionType = Wrapper.DistributionType.ALL
gradleVersion = "7.4.2" gradleVersion = "7.4.2"

View File

@ -1,28 +1,33 @@
package org.plutoengine package org.plutoengine
import org.gradle.internal.os.OperatingSystem
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
object Versions { object Versions {
const val lwjglVersion = "3.3.1" const val lwjglVersion = "3.3.1"
val lwjglNatives = when (OperatingSystem.current()) { val lwjglNatives = listOf(
OperatingSystem.LINUX -> "natives-linux" "natives-linux-arm64",
OperatingSystem.WINDOWS -> "natives-windows" "natives-linux-arm32",
else -> throw Error("Unsupported operating system!") "natives-linux",
} "natives-macos-arm64",
"natives-macos",
"natives-windows-arm64",
"natives-windows",
"natives-windows-x86"
)
const val jomlVersion = "1.10.2" const val jomlVersion = "1.10.2"
const val jomlPrimitivesVersion = "1.10.0"
const val steamworks4jVersion = "1.8.0" const val steamworks4jVersion = "1.8.0"
const val steamworks4jServerVersion = "1.8.0" const val steamworks4jServerVersion = "1.8.0"
const val versionYear = 22 const val versionYear = 22
const val versionMajor = 0 const val versionMajor = 3
const val versionMinor = 0 const val versionMinor = 0
const val versionPatch = 0 const val versionPatch = 0
const val isPrerelease = true const val isPrerelease = true
const val prereleaseName = "alpha" const val prereleaseName = "alpha"
const val prerealeaseUpdate = 7 const val prerealeaseUpdate = 1
val versionFull = val versionFull =
if (isPrerelease) if (isPrerelease)

View File

@ -26,3 +26,6 @@
/bin /bin
/*/bin /*/bin
/out
/*/out

View File

@ -45,7 +45,7 @@ subprojects {
repositories { repositories {
maven { maven {
name = "Vega" name = "Vega"
url = uri("https://vega.botdiril.com/") url = uri("https://vega.botdiril.com/releases")
credentials { credentials {
val vegaUsername: String? by project val vegaUsername: String? by project
val vegaPassword: String? by project val vegaPassword: String? by project

View File

@ -12,5 +12,7 @@ dependencies {
api("org.lwjgl:lwjgl-openal") api("org.lwjgl:lwjgl-openal")
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = Versions.lwjglNatives) org.plutoengine.Versions.lwjglNatives.forEach {
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = it)
}
} }

View File

@ -1,21 +1,39 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio; package org.plutoengine.audio;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import org.plutoengine.buffer.BufferHelper; import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity; import org.plutoengine.logger.SmartSeverity;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
public class AudioLoader public class AudioLoader
{ {
/** /**
@ -24,7 +42,7 @@ public class AudioLoader
* 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. * in multiple audio sources at once due to the cost of seeking.
*/ */
public static ISeekableAudioTrack loadMemoryDecoded(Path path) public static SeekableTrack loadMemoryDecoded(Path path)
{ {
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path); Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
@ -46,13 +64,13 @@ public class AudioLoader
* for from-memory PCM streaming. Good for frequently used small audio * for from-memory PCM streaming. Good for frequently used small audio
* files. * files.
*/ */
public static ISeekableAudioTrack loadMemoryPCM(Path path) public static RandomAccessClip loadMemoryPCM(Path path)
{ {
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path); Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
try try
{ {
return new MemoryPCMTrack(path); return new MemoryPCMClip(path);
} }
catch (IOException e) catch (IOException e)
{ {
@ -62,7 +80,7 @@ public class AudioLoader
} }
} }
private static ByteBuffer loadIntoMemory(Path path) throws IOException static ByteBuffer loadIntoMemory(Path path) throws IOException
{ {
var size = Files.size(path); var size = Files.size(path);
@ -74,203 +92,4 @@ public class AudioLoader
return BufferHelper.readToByteBuffer(path, readData); return BufferHelper.readToByteBuffer(path, readData);
} }
private abstract static class StreamableTrack implements IAudioStream
{
protected int channels;
protected int sampleRate;
@Override
public int getChannels()
{
return this.channels;
}
@Override
public int getSampleRate()
{
return this.sampleRate;
}
}
private abstract static class SeekableTrack extends StreamableTrack implements ISeekableAudioTrack
{
protected int samplesLength;
@Override
public int getLengthInSamples()
{
return this.samplesLength;
}
}
public static class MemoryPCMTrack extends SeekableTrack
{
private final ShortBuffer pcmAudio;
private int sampleOffset = 0;
private MemoryPCMTrack(Path path) throws IOException
{
long handle = MemoryUtil.NULL;
ByteBuffer audioBytes = null;
try (MemoryStack stack = MemoryStack.stackPush())
{
audioBytes = loadIntoMemory(path);
IntBuffer error = stack.mallocInt(1);
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
if (handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(handle, info);
this.channels = info.channels();
this.sampleRate = info.sample_rate();
// Downmix to mono, SOUNDS HORRIBLE
//
// this.channels = 1;
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
// var ptr = stack.pointers(this.pcmAudio);
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
}
catch (IOException e)
{
this.close();
throw e;
}
finally
{
MemoryUtil.memFree(audioBytes);
if (handle != MemoryUtil.NULL)
{
STBVorbis.stb_vorbis_close(handle);
}
}
}
@Override
public void seek(int sampleIndex)
{
this.sampleOffset = sampleIndex * this.getChannels();
}
@Override
public synchronized int getSamples(ShortBuffer pcm)
{
this.pcmAudio.limit(Math.min(this.sampleOffset + pcm.remaining(), this.pcmAudio.capacity()));
int read = this.pcmAudio.remaining();
pcm.put(this.pcmAudio);
this.sampleOffset += read;
pcm.clear();
return read / this.getChannels();
}
@Override
public int getSampleOffset()
{
return this.sampleOffset / this.getChannels();
}
@Override
public void close()
{
MemoryUtil.memFree(this.pcmAudio);
}
}
public static class MemoryDecodedVorbisTrack extends SeekableTrack
{
protected long handle;
private final ByteBuffer encodedAudio;
private MemoryDecodedVorbisTrack(Path path) throws IOException
{
try
{
this.encodedAudio = loadIntoMemory(path);
}
catch (IOException e)
{
this.close();
throw e;
}
try (MemoryStack stack = MemoryStack.stackPush())
{
IntBuffer error = stack.mallocInt(1);
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
if (this.handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(this.handle, info);
this.channels = info.channels();
this.sampleRate = info.sample_rate();
}
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
Logger.logf(SmartSeverity.AUDIO, """
\tSample rate:\t%d
\t\tChannels:\t%d
\t\tSamples:\t%d
%n""", this.sampleRate, this.channels, this.samplesLength);
}
@Override
public synchronized int getSamples(ShortBuffer pcm)
{
if (this.handle == MemoryUtil.NULL)
{
return -1;
}
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
}
@Override
public void close()
{
MemoryUtil.memFree(this.encodedAudio);
if (this.handle != MemoryUtil.NULL)
{
STBVorbis.stb_vorbis_close(this.handle);
this.handle = MemoryUtil.NULL;
}
}
@Override
public synchronized void seek(int sampleIndex)
{
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
}
@Override
public int getSampleOffset()
{
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
}
}
} }

View File

@ -0,0 +1,36 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public abstract class ClipTrack extends SeekableTrack implements ISeekableClip
{
protected int samplesLength;
@Override
public int getLengthInSamples()
{
return this.samplesLength;
}
}

View File

@ -0,0 +1,34 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public interface IAudio extends AutoCloseable
{
int getSampleRate();
int getChannels();
void close();
}

View File

@ -1,14 +0,0 @@
package org.plutoengine.audio;
import java.nio.ShortBuffer;
public interface IAudioStream
{
int getSamples(ShortBuffer pcm);
int getSampleRate();
int getChannels();
void close();
}

View File

@ -0,0 +1,30 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public interface IClip extends IAudio
{
int getLengthInSamples();
}

View File

@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
import java.nio.ShortBuffer;
public interface IRandomAccessAudio extends IClip
{
int getSamples(ShortBuffer pcm, int offset, boolean loopRead);
}

View File

@ -1,25 +0,0 @@
package org.plutoengine.audio;
public interface ISeekableAudioTrack extends IAudioStream
{
int getSampleOffset();
int getLengthInSamples();
default void skip(int sampleCount)
{
this.seek(Math.min(Math.max(0, this.getSampleOffset() + sampleCount), this.getLengthInSamples()));
}
default void skipTo(float offset0to1)
{
this.seek(Math.round(this.getLengthInSamples() * offset0to1));
}
default void rewind()
{
this.seek(0);
}
void seek(int sampleIndex);
}

View File

@ -0,0 +1,38 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public interface ISeekableClip extends ISeekableTrack, IClip
{
default void skip(int sampleCount)
{
this.seek(Math.min(Math.max(0, this.getSampleOffset() + sampleCount), this.getLengthInSamples()));
}
default void skipTo(float offset0to1)
{
this.seek(Math.round(this.getLengthInSamples() * offset0to1));
}
}

View File

@ -0,0 +1,35 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public interface ISeekableTrack extends IStreamingAudio
{
void seek(int sampleIndex);
default void rewind()
{
this.seek(0);
}
}

View File

@ -0,0 +1,34 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
import java.nio.ShortBuffer;
public interface IStreamingAudio extends IAudio
{
int getSamples(ShortBuffer pcm);
int getSampleOffset();
}

View File

@ -0,0 +1,119 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.file.Path;
public class MemoryDecodedVorbisTrack extends ClipTrack
{
protected long handle;
private final ByteBuffer encodedAudio;
MemoryDecodedVorbisTrack(Path path) throws IOException
{
try
{
this.encodedAudio = AudioLoader.loadIntoMemory(path);
}
catch (IOException e)
{
this.close();
throw e;
}
try (MemoryStack stack = MemoryStack.stackPush())
{
IntBuffer error = stack.mallocInt(1);
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
if (this.handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(this.handle, info);
this.channels = info.channels();
this.sampleRate = info.sample_rate();
}
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
Logger.logf(SmartSeverity.AUDIO, """
\tSample rate:\t%d
\t\tChannels:\t%d
\t\tSamples:\t%d
%n""", this.sampleRate, this.channels, this.samplesLength);
}
@Override
public synchronized int getSamples(ShortBuffer pcm)
{
if (this.handle == MemoryUtil.NULL)
{
return -1;
}
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
}
@Override
public void close()
{
MemoryUtil.memFree(this.encodedAudio);
if (this.handle != MemoryUtil.NULL)
{
STBVorbis.stb_vorbis_close(this.handle);
this.handle = MemoryUtil.NULL;
}
}
@Override
public synchronized void seek(int sampleIndex)
{
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
}
@Override
public int getSampleOffset()
{
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
}
}

View File

@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.file.Path;
public class MemoryPCMClip extends RandomAccessClip
{
private final ShortBuffer pcmAudio;
MemoryPCMClip(Path path) throws IOException
{
long handle = MemoryUtil.NULL;
ByteBuffer audioBytes = null;
try (MemoryStack stack = MemoryStack.stackPush())
{
audioBytes = AudioLoader.loadIntoMemory(path);
IntBuffer error = stack.mallocInt(1);
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
if (handle == MemoryUtil.NULL)
{
this.close();
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
}
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(handle, info);
this.channels = info.channels();
this.sampleRate = info.sample_rate();
// Downmix to mono, SOUNDS HORRIBLE
//
// this.channels = 1;
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
// var ptr = stack.pointers(this.pcmAudio);
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
}
catch (IOException e)
{
this.close();
throw e;
}
finally
{
MemoryUtil.memFree(audioBytes);
if (handle != MemoryUtil.NULL)
{
STBVorbis.stb_vorbis_close(handle);
}
}
}
@Override
public int getLengthInSamples()
{
return this.samplesLength / this.getChannels();
}
@Override
public int getSamples(ShortBuffer pcm, int offset, boolean loopRead)
{
int readTotal = 0;
int read;
do
{
int thisRemaining = this.pcmAudio.limit() - offset;
read = Math.min(pcm.remaining() - readTotal, thisRemaining);
pcm.put(pcm.position() + readTotal, this.pcmAudio, offset, read);
readTotal += read;
offset = (offset + read) % (this.getLengthInSamples() * this.channels);
}
while (loopRead && pcm.limit() - readTotal > 0);
return readTotal / this.getChannels();
}
@Override
public void close()
{
MemoryUtil.memFree(this.pcmAudio);
}
}

View File

@ -0,0 +1,36 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public abstract class RandomAccessClip extends Track implements IRandomAccessAudio, IClip
{
protected int samplesLength;
@Override
public int getLengthInSamples()
{
return this.samplesLength;
}
}

View File

@ -0,0 +1,29 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
public abstract class SeekableTrack extends Track implements ISeekableTrack
{
}

View File

@ -0,0 +1,43 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio;
abstract class Track implements IAudio
{
protected int channels;
protected int sampleRate;
@Override
public int getChannels()
{
return this.channels;
}
@Override
public int getSampleRate()
{
return this.sampleRate;
}
}

View File

@ -0,0 +1,59 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.lwjgl.openal.AL10;
import java.nio.ShortBuffer;
class AudioBuffer implements AutoCloseable
{
private final int id;
private final int format;
private final int sampleRate;
AudioBuffer(int id, int format, int sampleRate)
{
this.id = id;
this.format = format;
this.sampleRate = sampleRate;
}
public int getID()
{
return this.id;
}
public void writeData(ShortBuffer data)
{
AL10.alBufferData(this.id, this.format, data, this.sampleRate);
}
@Override
public void close()
{
AL10.alDeleteBuffers(this.id);
}
}

View File

@ -0,0 +1,68 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.plutoengine.audio.RandomAccessClip;
import java.nio.ShortBuffer;
public class AudioClipSource extends AudioDoubleBufferedSource
{
private final RandomAccessClip clip;
private final boolean looping;
private int readHead = 0;
public AudioClipSource(RandomAccessClip clip, boolean looping)
{
super(clip);
this.clip = clip;
this.looping = looping;
}
public AudioClipSource(RandomAccessClip clip)
{
this(clip, false);
}
@Override
protected int getSamples(ShortBuffer pcmTransferBuf)
{
var read = this.clip.getSamples(pcmTransferBuf, this.readHead * this.channels, this.looping);
this.readHead += read / this.channels;
if (this.looping)
this.readHead %= this.clip.getLengthInSamples();
return read;
}
public boolean isLooping()
{
return this.looping;
}
}

View File

@ -0,0 +1,176 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.joml.Matrix4x3f;
import org.joml.Matrix4x3fc;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.lwjgl.openal.*;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.Pluto;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* @author 493msi
*
*/
public class AudioContext extends PlutoLocalComponent
{
private long device = MemoryUtil.NULL;
private long context = MemoryUtil.NULL;
private ALCapabilities capabilities;
private Matrix4x3fc transformation;
AudioContext()
{
this.transformation = new Matrix4x3f();
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
if (devicePtr == MemoryUtil.NULL)
{
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device.");
// No audio device found, but the game should not crash just because we have no audio
return;
}
this.device = devicePtr;
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
if (contextPtr == MemoryUtil.NULL)
{
ALC10.alcCloseDevice(devicePtr);
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
// The game should not crash just because we have no audio
return;
}
this.context = contextPtr;
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
var alCapabilities = AL.createCapabilities(deviceCaps);
if (Pluto.DEBUG_MODE)
{
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
Logger.logf(SmartSeverity.AUDIO, "Distance model: %s\n", EnumDistanceModel.getByID(AL11.alGetInteger(AL11.AL_DISTANCE_MODEL)));
}
this.capabilities = alCapabilities;
Logger.log(SmartSeverity.AUDIO_PLUS, "Audio engine started.");
}
public void setTransformation(Matrix4x3fc transformation)
{
this.transformation = transformation;
}
public Vector3fc transform(Vector3fc vector)
{
return this.transformation.transformPosition(vector, new Vector3f());
}
public void setDistanceModel(EnumDistanceModel model)
{
AL10.alDistanceModel(model.getALID());
}
public void setSpeedOfSound(float speedOfSound)
{
AL11.alSpeedOfSound(speedOfSound);
}
public void setSpeed(Vector3fc speed)
{
var tSpeed = this.transformation.transformPosition(speed, new Vector3f());
AL10.alListener3f(AL10.AL_VELOCITY, tSpeed.x(), tSpeed.y(), tSpeed.z());
}
public void setPosition(Vector3f position)
{
var tPosition = this.transformation.transformPosition(position, new Vector3f());
AL10.alListener3f(AL10.AL_POSITION, tPosition.x(), tPosition.y(), tPosition.z());
}
public void setVolume(float volume)
{
AL10.alListenerf(AL10.AL_GAIN, volume);
}
public void setOrientation(Vector3f at, Vector3f up)
{
float[] data = new float[6];
data[0] = at.x;
data[1] = at.y;
data[2] = at.z;
data[3] = up.x;
data[4] = up.y;
data[5] = up.z;
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
}
public boolean isReady()
{
return this.capabilities != null;
}
@Override
protected void onUnmount()
{
Logger.log(SmartSeverity.AUDIO_MINUS, "Shutting down the audio engine.");
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
ALC10.alcDestroyContext(this.context);
ALC10.alcCloseDevice(this.device);
this.context = MemoryUtil.NULL;
this.device = MemoryUtil.NULL;
this.capabilities = null;
}
@Override
public boolean isUnique()
{
return true;
}
}

View File

@ -0,0 +1,215 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.SOFTDirectChannels;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.audio.IAudio;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
abstract class AudioDoubleBufferedSource extends AudioSource
{
private static final int BUFFER_SIZE_PER_CHANNEL = 8192;
private static final int DOUBLE_BUFFER = 2;
protected final int format;
protected final int channels;
protected final int sampleRate;
private final IntBuffer bufferIDs;
protected final Map<Integer, AudioBuffer> buffers;
protected final ShortBuffer pcmTransferBuf;
protected boolean audioBufferDepleted;
protected boolean closed;
protected AudioDoubleBufferedSource(IAudio audio)
{
this.format = switch (audio.getChannels()) {
case 1 -> AL10.AL_FORMAT_MONO16;
case 2 -> AL10.AL_FORMAT_STEREO16;
default -> throw new UnsupportedOperationException("Unsupported number of channels: " + audio.getChannels());
};
this.channels = audio.getChannels();
this.sampleRate = audio.getSampleRate();
int bufferSize = audio.getChannels() * BUFFER_SIZE_PER_CHANNEL;
this.pcmTransferBuf = MemoryUtil.memAllocShort(bufferSize);
this.bufferIDs = MemoryUtil.memCallocInt(DOUBLE_BUFFER);
AL10.alGenBuffers(this.bufferIDs);
this.buffers = IntStream.range(0, this.bufferIDs.limit())
.mapToObj(i -> new AudioBuffer(this.bufferIDs.get(i), this.format, this.sampleRate))
.collect(Collectors.toMap(AudioBuffer::getID, Function.identity(), (l, r) -> l));
AL10.alSourcei(this.id, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
}
public boolean play()
{
if (this.closed)
return false;
var state = this.getState();
return switch (state)
{
case AL10.AL_PLAYING -> true;
case AL10.AL_PAUSED, AL10.AL_STOPPED -> super.play();
case AL10.AL_INITIAL -> {
this.buffers.values()
.forEach(this::stream);
yield super.play();
}
default -> false; // Unexpected value, just say it didn't play
};
}
private void stream(AudioBuffer buffer)
{
if (this.audioBufferDepleted)
return;
this.fillTransferBuffer();
if (this.audioBufferDepleted)
return;
buffer.writeData(this.pcmTransferBuf);
AL10.alSourceQueueBuffers(this.id, buffer.getID());
}
private void fillTransferBuffer()
{
this.pcmTransferBuf.clear();
int samplesPerChannel = this.getSamples(this.pcmTransferBuf);
if (samplesPerChannel < BUFFER_SIZE_PER_CHANNEL)
{
this.audioBufferDepleted = true;
return;
}
var samples = samplesPerChannel * this.channels;
this.pcmTransferBuf.limit(samples);
}
protected abstract int getSamples(ShortBuffer pcmTransferBuf);
protected List<AudioBuffer> unqueueBuffers()
{
int processed = AL10.alGetSourcei(this.id, AL10.AL_BUFFERS_PROCESSED);
var unqueued = new ArrayList<AudioBuffer>(DOUBLE_BUFFER);
for (int i = 0; i < processed; i++)
{
int bufID = AL10.alSourceUnqueueBuffers(this.id);
var buffer = this.buffers.get(bufID);
unqueued.add(buffer);
}
return unqueued;
}
private int getState()
{
return AL10.alGetSourcei(this.id, AL10.AL_SOURCE_STATE);
}
public boolean update()
{
if (this.isClosed())
return false;
var unqueued = this.unqueueBuffers();
unqueued.forEach(this::stream);
var sourceState = this.getState();
if (sourceState == AL10.AL_STOPPED)
{
if (this.audioBufferDepleted)
return false;
if (unqueued.size() == DOUBLE_BUFFER)
return super.play();
return false;
}
return true;
}
public boolean updateOrClose()
{
boolean shouldClose = !this.update();
if (shouldClose)
this.close();
return shouldClose;
}
public boolean isClosed()
{
return this.closed;
}
@Override
public void close()
{
if (this.isClosed())
return;
this.closed = true;
this.stop();
this.unqueueBuffers();
super.close();
this.buffers.values()
.forEach(AudioBuffer::close);
MemoryUtil.memFree(this.pcmTransferBuf);
}
}

View File

@ -1,108 +1,112 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al; package org.plutoengine.audio.al;
import org.apache.commons.lang3.tuple.Pair;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.openal.*; import org.plutoengine.component.AbstractComponent;
import org.lwjgl.system.MemoryUtil; import org.plutoengine.component.ComponentToken;
import org.plutoengine.Pluto; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import javax.annotation.concurrent.ThreadSafe; import java.util.ArrayList;
import java.nio.ByteBuffer; import java.util.List;
import java.nio.IntBuffer;
/** public class AudioEngine extends PlutoLocalComponent
* @author 493msi
*
*/
@ThreadSafe
public class AudioEngine
{ {
private static final ThreadLocal<Long> device = ThreadLocal.withInitial(() -> MemoryUtil.NULL); public static final ComponentToken<AudioEngine> TOKEN = ComponentToken.create(AudioEngine::new);
private static final ThreadLocal<Long> context = ThreadLocal.withInitial(() -> MemoryUtil.NULL); private AudioContext context;
private static final ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>(); private final List<Pair<AudioClipSource, AudioSourceInfo>> sfx;
public static void initialize()
private AudioEngine()
{ {
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null); this.sfx = new ArrayList<>();
if (devicePtr == MemoryUtil.NULL) }
@Override
protected void onMount(AbstractComponent<PlutoLocalComponent>.ComponentDependencyManager manager)
{
this.context = manager.declareDependency(ComponentToken.create(AudioContext::new));
}
public void update()
{
for (var iterator = this.sfx.listIterator(); iterator.hasNext(); )
{ {
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device."); var data = iterator.next();
var source = data.getKey();
var info = data.getValue();
// No audio device found, but the game should not crash just because we have no audio var kaFunc = info.keepAliveFunction();
return; if (kaFunc != null && !kaFunc.getAsBoolean())
source.close();
var moveFunc = info.moveFunction();
if (!source.isClosed() && moveFunc != null)
{
var prevPos = source.getPosition();
var newPos = moveFunc.apply(prevPos);
var velocity = newPos.sub(prevPos, new Vector3f());
source.position(this.context, newPos);
source.velocity(this.context, velocity);
}
if (source.updateOrClose())
iterator.remove();
} }
device.set(devicePtr);
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
if (contextPtr == MemoryUtil.NULL)
{
ALC10.alcCloseDevice(devicePtr);
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
// The game should not crash just because we have no audio
return;
}
context.set(contextPtr);
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
var alCapabilities = AL.createCapabilities(deviceCaps);
if (Pluto.DEBUG_MODE)
{
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
}
capabilities.set(alCapabilities);
} }
public static void setSpeed(Vector3f speed) public void playSound(SoundEffect sfx)
{ {
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z); var soundEffect = new AudioClipSource(sfx.getClip());
soundEffect.volume(sfx.getVolume());
soundEffect.pitch(sfx.getPitch());
soundEffect.position(this.context, sfx.getPosition());
soundEffect.play();
var info = new AudioSourceInfo(sfx.getMovementMapper(), sfx.getKeepAliveFunction());
var data = Pair.of(soundEffect, info);
this.sfx.add(data);
} }
public static void setPosition(Vector3f position) @Override
protected void onUnmount()
{ {
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
} }
public static void setVolume(float volume) @Override
public boolean isUnique()
{ {
AL10.alListenerf(AL10.AL_GAIN, volume); return true;
} }
public static void setOrientation(Vector3f at, Vector3f up) public AudioContext getContext()
{ {
float[] data = new float[6]; return this.context;
data[0] = at.x;
data[1] = at.y;
data[2] = at.z;
data[3] = up.x;
data[4] = up.y;
data[5] = up.z;
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
}
public static boolean isReady()
{
return capabilities.get() != null;
}
public static void exit()
{
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
ALC10.alcDestroyContext(context.get());
ALC10.alcCloseDevice(device.get());
context.remove();
device.remove();
} }
} }

View File

@ -1,45 +1,97 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al; package org.plutoengine.audio.al;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.joml.Vector3fc; import org.joml.Vector3fc;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
public abstract class AudioSource public abstract class AudioSource implements AutoCloseable
{ {
protected final int source; protected final int id;
protected Vector3fc position;
protected AudioSource() protected AudioSource()
{ {
this.source = AL10.alGenSources(); this.id = AL10.alGenSources();
} }
@MustBeInvokedByOverriders
public boolean play()
{
AL10.alSourcePlay(this.id);
return true;
}
@MustBeInvokedByOverriders
public void pause()
{
AL10.alSourcePause(this.id);
}
@MustBeInvokedByOverriders
public void stop() public void stop()
{ {
AL10.alSourceStop(this.source); AL10.alSourceStop(this.id);
} }
@MustBeInvokedByOverriders
public void close() public void close()
{ {
this.stop(); AL10.alDeleteSources(this.id);
AL10.alDeleteSources(this.source);
} }
public void position(Vector3fc pos) @MustBeInvokedByOverriders
public void position(AudioContext context, Vector3fc pos)
{ {
AL10.alSource3f(this.source, AL10.AL_POSITION, pos.x(), pos.y(), pos.z()); this.position = pos;
var tPos = context.transform(pos);
AL10.alSource3f(this.id, AL10.AL_POSITION, tPos.x(), tPos.y(), tPos.z());
} }
public void velocity(Vector3fc velocity) public Vector3fc getPosition()
{ {
AL10.alSource3f(this.source, AL10.AL_VELOCITY, velocity.x(), velocity.y(), velocity.z()); return this.position;
} }
@MustBeInvokedByOverriders
public void velocity(AudioContext context, Vector3fc velocity)
{
var tVelocity = context.transform(velocity);
AL10.alSource3f(this.id, AL10.AL_VELOCITY, tVelocity.x(), tVelocity.y(), tVelocity.z());
}
@MustBeInvokedByOverriders
public void pitch(float f) public void pitch(float f)
{ {
AL10.alSourcef(this.source, AL10.AL_PITCH, f); AL10.alSourcef(this.id, AL10.AL_PITCH, f);
} }
@MustBeInvokedByOverriders
public void volume(float f) public void volume(float f)
{ {
AL10.alSourcef(this.source, AL10.AL_GAIN, f); AL10.alSourcef(this.id, AL10.AL_GAIN, f);
} }
} }

View File

@ -0,0 +1,37 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.joml.Vector3fc;
import java.util.function.BooleanSupplier;
import java.util.function.UnaryOperator;
public record AudioSourceInfo(
UnaryOperator<Vector3fc> moveFunction,
BooleanSupplier keepAliveFunction
)
{
}

View File

@ -1,118 +0,0 @@
package org.plutoengine.audio.al;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.SOFTDirectChannels;
import org.lwjgl.system.MemoryUtil;
import java.nio.ShortBuffer;
import org.plutoengine.audio.ISeekableAudioTrack;
import org.plutoengine.audio.IAudioStream;
public class AudioTrack extends AudioSource
{
private static final int BUFFER_SIZE_PER_CHANNEL = 16384;
private final IAudioStream track;
private final int format;
private static final int DOUBLE_BUFFER = 2;
private final int[] buffers;
private boolean closeOnFinish;
private final ShortBuffer pcm;
public AudioTrack(IAudioStream track)
{
this.track = track;
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;
this.pcm = MemoryUtil.memAllocShort(bufferSize);
this.buffers = new int[DOUBLE_BUFFER];
AL10.alGenBuffers(this.buffers);
AL10.alSourcei(this.source, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
}
@Override
public void close()
{
AL10.alDeleteBuffers(this.buffers);
AL10.alDeleteSources(this.source);
MemoryUtil.memFree(this.pcm);
}
public boolean play()
{
if (this.track instanceof ISeekableAudioTrack)
{
((ISeekableAudioTrack) this.track).rewind();
}
for (int buf : this.buffers)
{
this.stream(buf);
}
AL10.alSourcePlay(this.source);
return true;
}
public void setCloseOnFinish()
{
this.closeOnFinish = true;
}
private void stream(int buffer)
{
this.pcm.clear();
int samplesPerChannel = this.track.getSamples(this.pcm);
if (samplesPerChannel == 0)
{
return;
}
var samples = samplesPerChannel * this.track.getChannels();
this.pcm.limit(samples);
AL10.alBufferData(buffer, this.format, this.pcm, this.track.getSampleRate());
AL10.alSourceQueueBuffers(this.source, buffer);
}
public boolean update()
{
int processed = AL10.alGetSourcei(this.source, AL10.AL_BUFFERS_PROCESSED);
for (int i = 0; i < processed; i++)
{
int buffer = AL10.alSourceUnqueueBuffers(this.source);
this.stream(buffer);
}
if (AL10.alGetSourcei(this.source, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED)
{
if (this.closeOnFinish)
{
this.close();
}
return false;
}
return true;
}
}

View File

@ -0,0 +1,54 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.plutoengine.audio.ISeekableTrack;
import java.nio.ShortBuffer;
public class AudioTrackSource extends AudioDoubleBufferedSource
{
private final ISeekableTrack track;
public AudioTrackSource(ISeekableTrack track)
{
super(track);
this.track = track;
}
public boolean play()
{
this.track.rewind();
return super.play();
}
@Override
protected int getSamples(ShortBuffer pcmTransferBuf)
{
return this.track.getSamples(pcmTransferBuf);
}
}

View File

@ -0,0 +1,58 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.lwjgl.openal.AL11;
import java.util.Arrays;
public enum EnumDistanceModel implements IOpenALEnum
{
NONE(AL11.AL_NONE),
INVERSE_DISTANCE(AL11.AL_INVERSE_DISTANCE),
INVERSE_DISTANCE_CLAMPED(AL11.AL_INVERSE_DISTANCE_CLAMPED),
LINEAR_DISTANCE(AL11.AL_LINEAR_DISTANCE),
LINEAR_DISTANCE_CLAMPED(AL11.AL_LINEAR_DISTANCE_CLAMPED),
EXPONENT_DISTANCE(AL11.AL_EXPONENT_DISTANCE),
EXPONENT_DISTANCE_CLAMPED(AL11.AL_EXPONENT_DISTANCE_CLAMPED);
private final int alID;
EnumDistanceModel(int alID)
{
this.alID = alID;
}
public static EnumDistanceModel getByID(int id)
{
return Arrays.stream(EnumDistanceModel.values()).filter(model -> model.getALID() == id).findAny().orElse(null);
}
@Override
public int getALID()
{
return this.alID;
}
}

View File

@ -0,0 +1,30 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
public interface IOpenALEnum
{
int getALID();
}

View File

@ -0,0 +1,110 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.al;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3fc;
import org.plutoengine.audio.RandomAccessClip;
import java.util.function.BooleanSupplier;
import java.util.function.UnaryOperator;
public class SoundEffect
{
private final @NotNull RandomAccessClip clip;
private @NotNull Vector3fc position;
private float volume;
private float pitch;
private UnaryOperator<Vector3fc> movementMapper;
private BooleanSupplier keepAliveFunction;
public SoundEffect(@NotNull RandomAccessClip soundEffect, @NotNull Vector3fc position, float volume)
{
this.clip = soundEffect;
this.position = position;
this.volume = volume;
this.pitch = 1.0f;
}
public SoundEffect position(Vector3fc position)
{
this.position = position;
return this;
}
public SoundEffect volume(float volume)
{
this.volume = volume;
return this;
}
public SoundEffect pitch(float pitch)
{
this.pitch = pitch;
return this;
}
public SoundEffect movementMapper(UnaryOperator<Vector3fc> movementMapper)
{
this.movementMapper = movementMapper;
return this;
}
public SoundEffect keepAliveFunction(BooleanSupplier keepAliveFunction)
{
this.keepAliveFunction = keepAliveFunction;
return this;
}
@NotNull RandomAccessClip getClip()
{
return this.clip;
}
@NotNull Vector3fc getPosition()
{
return this.position;
}
float getVolume()
{
return this.volume;
}
float getPitch()
{
return this.pitch;
}
UnaryOperator<Vector3fc> getMovementMapper()
{
return this.movementMapper;
}
BooleanSupplier getKeepAliveFunction()
{
return this.keepAliveFunction;
}
}

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.audio.util; package org.plutoengine.audio.util;
import org.lwjgl.stb.STBVorbis; import org.lwjgl.stb.STBVorbis;

View File

@ -6,8 +6,9 @@ plugins {
description = "A module acting as glue for all PlutoEngine components." description = "A module acting as glue for all PlutoEngine components."
dependencies { dependencies {
api("org.apache.commons:commons-lang3:3.12.0") api("org.jetbrains", "annotations", "23.0.0")
api("org.apache.commons:commons-collections4:4.4")
implementation("org.apache.commons", "commons-lang3", "3.12.0")
implementation("org.apache.commons", "commons-collections4", "4.4")
api("com.google.code.findbugs:jsr305:3.0.2")
} }

View File

@ -1,33 +1,85 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.component; package org.plutoengine.component;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractComponent implements IComponent public abstract class AbstractComponent<T extends AbstractComponent<? super T>>
{ {
private static final AtomicLong ID_SOURCE = new AtomicLong(); private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id; private final long id;
private ComponentDependencyManager manager;
protected AbstractComponent() protected AbstractComponent()
{ {
this.id = ID_SOURCE.getAndIncrement(); this.id = ID_SOURCE.getAndIncrement();
} }
@Override
public long getID() public long getID()
{ {
return this.id; return this.id;
} }
@Override /**
public void onMount() throws Exception * 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
*/
public abstract boolean isUnique();
void initialize(ComponentManager<? super T> manager) throws Exception
{
this.manager = this.new ComponentDependencyManager(manager);
onMount(this.manager);
}
protected void onMount(ComponentDependencyManager manager) throws Exception
{ {
} }
@Override void destroy(ComponentManager<? super T> manager) throws Exception
public void onUnmount() throws Exception {
if (this.manager.dependencies != null)
{
this.manager.dependencies.forEach(manager::removeComponent);
this.manager.dependencies.clear();
}
this.onUnmount();
}
protected void onUnmount() throws Exception
{ {
} }
@ -37,7 +89,7 @@ public abstract class AbstractComponent implements IComponent
{ {
if (this == o) return true; if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false; if (o == null || this.getClass() != o.getClass()) return false;
AbstractComponent that = (AbstractComponent) o; AbstractComponent<?> that = (AbstractComponent<?>) o;
return this.id == that.id; return this.id == that.id;
} }
@ -46,4 +98,27 @@ public abstract class AbstractComponent implements IComponent
{ {
return Objects.hash(this.id); return Objects.hash(this.id);
} }
public class ComponentDependencyManager
{
private final ComponentManager<? super T> manager;
private Deque<T> dependencies;
private ComponentDependencyManager(ComponentManager<? super T> componentManager)
{
this.manager = componentManager;
}
public <R extends T> R declareDependency(ComponentToken<R> token)
{
if (this.dependencies == null)
this.dependencies = new ArrayDeque<>();
var dependency = this.manager.addComponent(token);
this.dependencies.push(dependency);
return dependency;
}
}
} }

View File

@ -1,24 +1,49 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.component; package org.plutoengine.component;
import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap; import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.ClassUtils;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.util.*; import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
public class ComponentManager<R extends AbstractComponent> public class ComponentManager<R extends AbstractComponent<R>>
{ {
private final Class<R> base; private final Class<R> base;
protected final MultiValuedMap<ComponentToken<? extends R>, R> components; protected final MultiValuedMap<ComponentToken<?>, R> components;
protected final Map<R, ComponentToken<? extends R>> tokens; protected final Map<R, ComponentToken<?>> tokens;
protected final MultiValuedMap<Class<?>, R> implementationProviders; protected final MultiValuedMap<Class<?>, R> implementationProviders;
protected final MultiValuedMap<R, Class<?>> implementationReceivers; protected final MultiValuedMap<R, Class<?>> implementationReceivers;
public ComponentManager(@Nonnull Class<R> base) public ComponentManager(@NotNull Class<R> base)
{ {
this.base = base; this.base = base;
this.components = new HashSetValuedHashMap<>(); this.components = new HashSetValuedHashMap<>();
@ -27,7 +52,7 @@ public class ComponentManager<R extends AbstractComponent>
this.implementationReceivers = new ArrayListValuedHashMap<>(); this.implementationReceivers = new ArrayListValuedHashMap<>();
} }
public <T extends R> T addComponent(@Nonnull ComponentToken<T> token) public <T extends R> T addComponent(@NotNull ComponentToken<T> token)
{ {
T component = token.createInstance(); T component = token.createInstance();
var clazz = component.getClass(); var clazz = component.getClass();
@ -50,16 +75,22 @@ public class ComponentManager<R extends AbstractComponent>
this.components.put(token, component); this.components.put(token, component);
this.tokens.put(component, token); this.tokens.put(component, token);
this.onComponentAdded(component);
return component;
}
@MustBeInvokedByOverriders
protected void onComponentAdded(R component)
{
try try
{ {
component.onMount(); component.initialize(this);
} }
catch (Exception e) catch (Exception e)
{ {
throw new RuntimeException("An exception has occured while mounting the component", e); throw new RuntimeException("An exception has occured while mounting the component", e);
} }
return component;
} }
public Class<R> getComponentBase() public Class<R> getComponentBase()
@ -67,33 +98,33 @@ public class ComponentManager<R extends AbstractComponent>
return this.base; return this.base;
} }
public <T extends R> Stream<T> streamComponents(@Nonnull Class<T> componentClazz) public <T extends R> Stream<T> streamComponents(@NotNull Class<T> componentClazz)
{ {
var providers = this.implementationProviders.get(componentClazz); var providers = this.implementationProviders.get(componentClazz);
return providers.stream().map(componentClazz::cast); return providers.stream().map(componentClazz::cast);
} }
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz) throws NoSuchElementException public <T extends R> T getComponent(@NotNull Class<T> componentClazz) throws NoSuchElementException
{ {
return this.streamComponents(componentClazz) return this.streamComponents(componentClazz)
.findAny() .findAny()
.orElseThrow(); .orElseThrow();
} }
public <T extends R> T getComponent(@Nonnull Class<T> componentClazz, @Nonnull Comparator<T> heuristic) throws NoSuchElementException public <T extends R> T getComponent(@NotNull Class<T> componentClazz, @NotNull Comparator<T> heuristic) throws NoSuchElementException
{ {
return this.streamComponents(componentClazz) return this.streamComponents(componentClazz)
.max(heuristic) .max(heuristic)
.orElseThrow(); .orElseThrow();
} }
public <T extends R> List<T> getComponents(@Nonnull Class<T> componentClazz) public <T extends R> List<T> getComponents(@NotNull Class<T> componentClazz)
{ {
return this.streamComponents(componentClazz).toList(); return this.streamComponents(componentClazz).toList();
} }
public void removeComponent(@Nonnull R component) throws IllegalArgumentException public void removeComponent(@NotNull R component) throws IllegalArgumentException
{ {
var token = this.tokens.remove(component); var token = this.tokens.remove(component);
@ -106,9 +137,15 @@ public class ComponentManager<R extends AbstractComponent>
classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component)); classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component));
this.onComponentRemoved(component);
}
@MustBeInvokedByOverriders
protected void onComponentRemoved(R component)
{
try try
{ {
component.onUnmount(); component.destroy(this);
} }
catch (Exception e) catch (Exception e)
{ {
@ -116,7 +153,7 @@ public class ComponentManager<R extends AbstractComponent>
} }
} }
public <T extends R> void removeComponents(@Nonnull ComponentToken<T> componentToken) public <T extends R> void removeComponents(@NotNull ComponentToken<T> componentToken)
{ {
var activeComponents = this.components.remove(componentToken); var activeComponents = this.components.remove(componentToken);
@ -127,14 +164,7 @@ public class ComponentManager<R extends AbstractComponent>
classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component)); classes.forEach(clazz -> this.implementationProviders.removeMapping(clazz, component));
try this.onComponentRemoved(component);
{
component.onUnmount();
}
catch (Exception e)
{
throw new RuntimeException("An exception has occured whiile unmounting the component", e);
}
}); });
} }
} }

View File

@ -1,11 +1,36 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.component; package org.plutoengine.component;
import javax.annotation.Nonnull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier; import java.util.function.Supplier;
public final class ComponentToken<T extends IComponent> public final class ComponentToken<T extends AbstractComponent<? super T>>
{ {
private static final AtomicLong ID_SOURCE = new AtomicLong(); private static final AtomicLong ID_SOURCE = new AtomicLong();
@ -18,7 +43,7 @@ public final class ComponentToken<T extends IComponent>
this.supplier = valueSupplier; this.supplier = valueSupplier;
} }
public static <T extends IComponent> ComponentToken<T> create(@Nonnull Supplier<T> valueSupplier) public static <G extends AbstractComponent<? super G>> ComponentToken<G> create(@NotNull Supplier<G> valueSupplier)
{ {
return new ComponentToken<>(valueSupplier); return new ComponentToken<>(valueSupplier);
} }

View File

@ -1,22 +0,0 @@
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

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine; package org.plutoengine;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@ -13,6 +37,8 @@ import org.plutoengine.l10n.PlutoL10n;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity; import org.plutoengine.logger.SmartSeverity;
import org.plutoengine.mod.ModLoader; import org.plutoengine.mod.ModLoader;
import org.plutoengine.util.color.IRGBA;
import org.plutoengine.util.color.RGBA;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Locale; import java.util.Locale;
@ -30,6 +56,11 @@ public abstract class PlutoApplication
protected abstract Class<?> getMainModule(); protected abstract Class<?> getMainModule();
protected void init()
{
}
protected abstract void loop(); protected abstract void loop();
protected PlutoApplication() protected PlutoApplication()
@ -98,6 +129,11 @@ public abstract class PlutoApplication
* <td><code>true</code></td> * <td><code>true</code></td>
* <td>Whether the window should be resizable</td> * <td>Whether the window should be resizable</td>
* </tr> * </tr>
* <tr>
* <td><code>clearColor</code></td>
* <td><code>RGBA(0f, 0.7f, 1f, 0f)</code></td>
* <td>What color to fill the screen buffer with when beginning a new frame.</td>
* </tr>
* </table> * </table>
* *
* @author 493msi * @author 493msi
@ -118,6 +154,7 @@ public abstract class PlutoApplication
private int vsync = 0; private int vsync = 0;
private boolean windowResizable = true; private boolean windowResizable = true;
private Path[] icons = null; private Path[] icons = null;
private IRGBA clearColor = new RGBA(0f, 0.7f, 1f, 0f);
public StartupConfig icons(Path... paths) public StartupConfig icons(Path... paths)
{ {
@ -176,15 +213,18 @@ public abstract class PlutoApplication
return this; return this;
} }
public StartupConfig clearColor(IRGBA color)
{
this.clearColor = new RGBA(color.red(), color.green(), color.blue(), color.alpha());
return this;
}
public StartupConfig() public StartupConfig()
{ {
} }
} }
/**
* TODO: Start the application in a new thread
* */
public final void run(String[] args, StartupConfig config) public final void run(String[] args, StartupConfig config)
{ {
if (config == null) if (config == null)
@ -194,7 +234,7 @@ public abstract class PlutoApplication
var globalComponents = PlutoGlobal.COMPONENTS; var globalComponents = PlutoGlobal.COMPONENTS;
globalComponents.addComponent(Logger.TOKEN); var logger = globalComponents.addComponent(Logger.TOKEN);
Logger.log(SmartSeverity.INFO, "Debug mode: " + (Pluto.DEBUG_MODE ? "enabled" : "disabled")); Logger.log(SmartSeverity.INFO, "Debug mode: " + (Pluto.DEBUG_MODE ? "enabled" : "disabled"));
@ -243,15 +283,9 @@ public abstract class PlutoApplication
this.display.createOpenGLCapabilities(); this.display.createOpenGLCapabilities();
components.addComponent(InputBus.TOKEN); var inputBus = components.addComponent(InputBus.fromDisplay(this.display));
var audioEngine = components.addComponent(AudioEngine.TOKEN);
AudioEngine.initialize(); var modLoader = components.addComponent(ModLoader.with(this.getMainModule()));
var modLoader = components.addComponent(ModLoader.TOKEN);
modLoader.registerMod(this.getMainModule());
modLoader.load();
if (config.icons != null) if (config.icons != null)
{ {
@ -259,31 +293,30 @@ public abstract class PlutoApplication
this.display.setIcons(icons); this.display.setIcons(icons);
} }
this.init();
while (!this.display.isClosing()) while (!this.display.isClosing())
{ {
GL33.glViewport(0, 0, this.display.getWidth(), this.display.getHeight()); GL33.glViewport(0, 0, this.display.getWidth(), this.display.getHeight());
GL33.glClearColor(0f, 0.7f, 1f, 0f); GL33.glClearColor(config.clearColor.red(), config.clearColor.green(), config.clearColor.blue(), config.clearColor.alpha());
GL33.glClear(GL33.GL_COLOR_BUFFER_BIT | GL33.GL_DEPTH_BUFFER_BIT); GL33.glClear(GL33.GL_COLOR_BUFFER_BIT | GL33.GL_DEPTH_BUFFER_BIT);
this.loop(); this.loop();
this.display.swapBuffers(); this.display.swapBuffers();
InputBus.resetStates(); audioEngine.update();
inputBus.resetStates();
this.display.pollEvents(); this.display.pollEvents();
} }
AudioEngine.exit(); components.removeComponent(audioEngine);
components.removeComponent(modLoader);
modLoader.unload();
GL.destroy(); GL.destroy();
components.removeComponent(modLoader); components.removeComponent(inputBus);
components.removeComponents(InputBus.TOKEN);
this.display.destroy(); this.display.destroy();
@ -291,7 +324,7 @@ public abstract class PlutoApplication
DisplayBuilder.destroyGLFW(); DisplayBuilder.destroyGLFW();
globalComponents.removeComponents(Logger.TOKEN); globalComponents.removeComponent(logger);
} }
public Display getDisplayInstance() public Display getDisplayInstance()

View File

@ -1,53 +0,0 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWCursorPosCallback;
public class CursorPositionCallback extends GLFWCursorPosCallback
{
private double posX;
private double posY;
private double deltaX;
private double deltaY;
@Override
public void invoke(long window, double xpos, double ypos)
{
this.deltaX = this.posX - xpos;
this.deltaY = this.posY - ypos;
this.posX = xpos;
this.posY = ypos;
}
public void reset()
{
this.deltaX = 0;
this.deltaY = 0;
}
public double getX()
{
return this.posX;
}
public double getY()
{
return this.posY;
}
public double getDeltaX()
{
return this.deltaX;
}
public double getDeltaY()
{
return this.deltaY;
}
public boolean isInside(int x, int y, int x2, int y2)
{
return this.getX() > x && this.getX() < x2 && this.getY() > y && this.getY() < y2;
}
}

View File

@ -1,100 +1,61 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input; 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.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.display.Display; import org.plutoengine.display.Display;
@ThreadSensitive(localContexts = true)
public class InputBus extends PlutoLocalComponent public class InputBus extends PlutoLocalComponent
{ {
public static final ComponentToken<InputBus> TOKEN = ComponentToken.create(InputBus::new); public static ComponentToken<InputBus> fromDisplay(Display display)
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()
{ {
return ComponentToken.create(() -> new InputBus(display));
} }
private static InputBus instance() private final Display display;
private Keyboard keyboard;
private Mouse mouse;
private InputBus(Display display)
{ {
return PlutoLocal.components().getComponent(InputBus.class); this.display = display;
} }
@Override @Override
public void onMount() protected void onMount(ComponentDependencyManager manager)
{ {
var display = PlutoLocal.components().getComponent(Display.class); this.keyboard = manager.declareDependency(ComponentToken.create(() -> new Keyboard(this.display.getWindowPointer())));
this.windowPointer = display.getWindowPointer(); this.mouse = manager.declareDependency(ComponentToken.create(() -> new Mouse(this.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);
} }
public void resetStates()
@Override
public void onUnmount()
{ {
GLFW.glfwSetKeyCallback(this.windowPointer, null); this.keyboard.resetStates();
GLFW.glfwSetMouseButtonCallback(this.windowPointer, null); this.mouse.resetStates();
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 @Override
@ -103,81 +64,4 @@ public class InputBus extends PlutoLocalComponent
return true; 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

@ -0,0 +1,102 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.input.callback.KeyboardCharInput;
import org.plutoengine.input.callback.KeyboardInputCallback;
public class Keyboard extends PlutoLocalComponent
{
private final KeyboardInputCallback keyboard = new KeyboardInputCallback();
private final KeyboardCharInput charInput = new KeyboardCharInput();
private final long windowPointer;
Keyboard(long windowPointer)
{
this.windowPointer = windowPointer;
}
public KeyboardInputCallback keyboard()
{
return this.keyboard;
}
public KeyboardCharInput charInput()
{
return this.charInput;
}
void resetStates()
{
this.keyboard.resetPressed();
this.charInput.reset();
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
GLFW.glfwSetKeyCallback(this.windowPointer, this.keyboard);
GLFW.glfwSetCharCallback(this.windowPointer, this.charInput);
}
@Override
protected void onUnmount() throws Exception
{
GLFW.glfwSetKeyCallback(this.windowPointer, null);
GLFW.glfwSetCharCallback(this.windowPointer, null);
this.keyboard.free();
this.charInput.free();
}
public boolean pressed(int key)
{
return this.keyboard.hasBeenPressed(key);
}
public boolean released(int key)
{
return this.keyboard.hasBeenReleased(key);
}
public boolean isKeyDown(int key)
{
return this.keyboard.isKeyDown(key);
}
public String getTypedText()
{
return this.charInput.getTypedText();
}
@Override
public boolean isUnique()
{
return false;
}
}

View File

@ -1,25 +0,0 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWCharCallback;
public class KeyboardCharInput extends GLFWCharCallback
{
private final StringBuilder typedText = new StringBuilder();
@Override
public void invoke(long window, int codepoint)
{
this.typedText.appendCodePoint(codepoint);
}
public String getTypedText()
{
return this.typedText.toString();
}
public void reset()
{
this.typedText.setLength(0);
}
}

View File

@ -0,0 +1,143 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.input.callback.CursorPositionCallback;
import org.plutoengine.input.callback.MouseButtonCallback;
import org.plutoengine.input.callback.ScrollInputCallback;
public class Mouse extends PlutoLocalComponent
{
private final MouseButtonCallback mouseButton = new MouseButtonCallback();
private final CursorPositionCallback cursorPosition = new CursorPositionCallback();
private final ScrollInputCallback scroll = new ScrollInputCallback();
private final long windowPointer;
Mouse(long windowPointer)
{
this.windowPointer = windowPointer;
}
public MouseButtonCallback mouseButtons()
{
return this.mouseButton;
}
public ScrollInputCallback scroll()
{
return this.scroll;
}
public CursorPositionCallback cursorPosition()
{
return this.cursorPosition;
}
void resetStates()
{
this.mouseButton.reset();
this.scroll.reset();
this.cursorPosition.reset();
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
GLFW.glfwSetMouseButtonCallback(this.windowPointer, this.mouseButton);
GLFW.glfwSetCursorPosCallback(this.windowPointer, this.cursorPosition);
GLFW.glfwSetScrollCallback(this.windowPointer, this.scroll);
}
@Override
protected void onUnmount() throws Exception
{
GLFW.glfwSetMouseButtonCallback(this.windowPointer, null);
GLFW.glfwSetCursorPosCallback(this.windowPointer, null);
GLFW.glfwSetScrollCallback(this.windowPointer, null);
this.mouseButton.free();
this.cursorPosition.free();
this.scroll.free();
}
public boolean clicked(int button)
{
return this.mouseButton.buttonClicked[button];
}
public boolean released(int button)
{
return this.mouseButton.buttonReleased[button];
}
public boolean isButtonDown(int button)
{
return this.mouseButton.buttonDown[button];
}
public double getX()
{
return this.cursorPosition.getX();
}
public double getY()
{
return this.cursorPosition.getY();
}
public boolean isInside(int x1, int y1, int x2, int y2)
{
return this.cursorPosition.isInside(x1, y1, x2, y2);
}
public double getDX()
{
return this.cursorPosition.getDeltaX();
}
public double getDY()
{
return this.cursorPosition.getDeltaY();
}
public double getScrollX()
{
return this.scroll.getXScroll();
}
public double getScrollY()
{
return this.scroll.getYScroll();
}
@Override
public boolean isUnique()
{
return false;
}
}

View File

@ -1,28 +0,0 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWMouseButtonCallback;
import java.util.Arrays;
public class MouseButtonCallback extends GLFWMouseButtonCallback
{
public boolean[] buttonClicked = new boolean[32];
public boolean[] buttonDown = new boolean[32];
public boolean[] buttonReleased = new boolean[32];
@Override
public void invoke(long window, int button, int action, int mods)
{
this.buttonClicked[button] = action == GLFW.GLFW_PRESS;
this.buttonDown[button] = action != GLFW.GLFW_RELEASE;
this.buttonReleased[button] = action == GLFW.GLFW_RELEASE;
}
public void reset()
{
Arrays.fill(this.buttonClicked, false);
Arrays.fill(this.buttonReleased, false);
}
}

View File

@ -1,36 +0,0 @@
package org.plutoengine.input;
import org.lwjgl.glfw.GLFWScrollCallback;
/**
* @author 493msi
*
*/
public class ScrollInputCallback extends GLFWScrollCallback
{
private double xScroll;
private double yScroll;
@Override
public void invoke(long window, double xoffset, double yoffset)
{
xScroll = xoffset;
yScroll = yoffset;
}
public void reset()
{
xScroll = 0;
yScroll = 0;
}
public double getYScroll()
{
return yScroll;
}
public double getXScroll()
{
return xScroll;
}
}

View File

@ -0,0 +1,77 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWCursorPosCallback;
public class CursorPositionCallback extends GLFWCursorPosCallback
{
private double posX;
private double posY;
private double deltaX;
private double deltaY;
@Override
public void invoke(long window, double xpos, double ypos)
{
this.deltaX = this.posX - xpos;
this.deltaY = this.posY - ypos;
this.posX = xpos;
this.posY = ypos;
}
public void reset()
{
this.deltaX = 0;
this.deltaY = 0;
}
public double getX()
{
return this.posX;
}
public double getY()
{
return this.posY;
}
public double getDeltaX()
{
return this.deltaX;
}
public double getDeltaY()
{
return this.deltaY;
}
public boolean isInside(int x, int y, int x2, int y2)
{
return this.getX() > x && this.getX() < x2 && this.getY() > y && this.getY() < y2;
}
}

View File

@ -0,0 +1,49 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWCharCallback;
public class KeyboardCharInput extends GLFWCharCallback
{
private final StringBuilder typedText = new StringBuilder();
@Override
public void invoke(long window, int codepoint)
{
this.typedText.appendCodePoint(codepoint);
}
public String getTypedText()
{
return this.typedText.toString();
}
public void reset()
{
this.typedText.setLength(0);
}
}

View File

@ -1,4 +1,28 @@
package org.plutoengine.input; /*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWKeyCallback; import org.lwjgl.glfw.GLFWKeyCallback;

View File

@ -0,0 +1,52 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWMouseButtonCallback;
import java.util.Arrays;
public class MouseButtonCallback extends GLFWMouseButtonCallback
{
public boolean[] buttonClicked = new boolean[32];
public boolean[] buttonDown = new boolean[32];
public boolean[] buttonReleased = new boolean[32];
@Override
public void invoke(long window, int button, int action, int mods)
{
this.buttonClicked[button] = action == GLFW.GLFW_PRESS;
this.buttonDown[button] = action != GLFW.GLFW_RELEASE;
this.buttonReleased[button] = action == GLFW.GLFW_RELEASE;
}
public void reset()
{
Arrays.fill(this.buttonClicked, false);
Arrays.fill(this.buttonReleased, false);
}
}

View File

@ -0,0 +1,60 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.input.callback;
import org.lwjgl.glfw.GLFWScrollCallback;
/**
* @author 493msi
*
*/
public class ScrollInputCallback extends GLFWScrollCallback
{
private double xScroll;
private double yScroll;
@Override
public void invoke(long window, double xoffset, double yoffset)
{
xScroll = xoffset;
yScroll = yoffset;
}
public void reset()
{
xScroll = 0;
yScroll = 0;
}
public double getYScroll()
{
return yScroll;
}
public double getXScroll()
{
return xScroll;
}
}

View File

@ -14,10 +14,13 @@ dependencies {
api("org.lwjgl", "lwjgl-glfw") api("org.lwjgl", "lwjgl-glfw")
api("org.lwjgl", "lwjgl-opengl") api("org.lwjgl", "lwjgl-opengl")
api("org.lwjgl", "lwjgl-stb") api("org.lwjgl", "lwjgl-stb")
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = Versions.lwjglNatives) org.plutoengine.Versions.lwjglNatives.forEach {
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = Versions.lwjglNatives) runtimeOnly("org.lwjgl", "lwjgl", classifier = it)
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = Versions.lwjglNatives) runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = it)
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = it)
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = it)
}
api("com.code-disaster.steamworks4j", "steamworks4j", Versions.steamworks4jVersion) api("com.code-disaster.steamworks4j", "steamworks4j", Versions.steamworks4jVersion)
api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion) api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion)

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine; package org.plutoengine;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine; package org.plutoengine;
import org.lwjgl.Version; import org.lwjgl.Version;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.buffer; package org.plutoengine.buffer;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
@ -28,7 +52,7 @@ public final class BufferHelper
* @author 493msi * @author 493msi
* @since 0.1 * @since 0.1
*/ */
public static IntBuffer flippedIntBuffer(int[] data) public static IntBuffer intBuffer(int[] data)
{ {
IntBuffer buffer = BufferUtils.createIntBuffer(data.length); IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
buffer.put(data); buffer.put(data);
@ -46,7 +70,7 @@ public final class BufferHelper
* @author 493msi * @author 493msi
* @since 0.1 * @since 0.1
*/ */
public static FloatBuffer flippedFloatBuffer(float[] data) public static FloatBuffer floatBuffer(float[] data)
{ {
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length); FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
buffer.put(data); buffer.put(data);
@ -64,7 +88,7 @@ public final class BufferHelper
* @author 493msi * @author 493msi
* @since 0.1 * @since 0.1
*/ */
public static ByteBuffer flippedByteBuffer(byte[] data) public static ByteBuffer byteBuffer(byte[] data)
{ {
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
buffer.put(data); buffer.put(data);
@ -136,8 +160,8 @@ public final class BufferHelper
* @author 493msi * @author 493msi
* @since 0.3 * @since 0.3
*/ */
public static ByteBuffer readToFlippedByteBuffer(Path path) throws IOException public static ByteBuffer readToByteBuffer(Path path) throws IOException
{ {
return flippedByteBuffer(Files.readAllBytes(path)); return byteBuffer(Files.readAllBytes(path));
} }
} }

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.buffer; package org.plutoengine.buffer;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/** /**
* Utilities for better native buffer handling. * Utilities for better native buffer handling.
* *

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.display; package org.plutoengine.display;
import org.lwjgl.glfw.*; import org.lwjgl.glfw.*;
@ -5,7 +29,7 @@ import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.plutoengine.Pluto; import org.plutoengine.Pluto;
import org.plutoengine.address.ThreadSensitive; import org.plutoengine.annotation.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent; import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.gl.GLDebugInfo; import org.plutoengine.gl.GLDebugInfo;
import org.plutoengine.logger.Logger; import org.plutoengine.logger.Logger;
@ -63,7 +87,7 @@ public class Display extends PlutoLocalComponent
throw new IllegalStateException("Failed to create a window..."); throw new IllegalStateException("Failed to create a window...");
} }
GLFWVidMode vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()); var vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
if (vidmode == null) if (vidmode == null)
{ {

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.display; package org.plutoengine.display;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@ -81,9 +105,7 @@ public class DisplayBuilder
public static void initGLFW() public static void initGLFW()
{ {
if (!GLFW.glfwInit()) if (!GLFW.glfwInit())
{
throw new IllegalStateException("Could not init GLFW!"); throw new IllegalStateException("Could not init GLFW!");
}
} }
public static void destroyGLFW() public static void destroyGLFW()

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.display; package org.plutoengine.display;
import java.util.Deque; import java.util.Deque;
@ -14,7 +38,9 @@ public class Framerate
private static double frameTime = Double.NaN; private static double frameTime = Double.NaN;
private static double FPS = Double.NaN; private static double animationTimer = 0;
private static double fps = Double.NaN;
private static int interpolatedFPS; private static int interpolatedFPS;
@ -29,7 +55,7 @@ public class Framerate
public static double getFPS() public static double getFPS()
{ {
return FPS; return fps;
} }
public static int getInterpolatedFPS() public static int getInterpolatedFPS()
@ -37,6 +63,11 @@ public class Framerate
return interpolatedFPS; return interpolatedFPS;
} }
public static float getAnimationTimer()
{
return (float) animationTimer;
}
public static void tick() public static void tick()
{ {
var now = System.nanoTime(); var now = System.nanoTime();
@ -45,7 +76,10 @@ public class Framerate
{ {
var frameTimeNs = now - lastDraw; var frameTimeNs = now - lastDraw;
frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1); frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1);
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime; animationTimer += frameTimeNs / (double) TimeUnit.SECONDS.toNanos(1);
// Maintain precision in case the engine runs for many hours
animationTimer %= TimeUnit.DAYS.toMinutes(1);
fps = TimeUnit.SECONDS.toMillis(1) / frameTime;
} }
var nowMs = System.currentTimeMillis(); var nowMs = System.currentTimeMillis();
@ -67,7 +101,7 @@ public class Framerate
} }
else else
{ {
interpolatedFPS = (int) Math.round(FPS); interpolatedFPS = (int) Math.round(fps);
} }
lastDraw = now; lastDraw = now;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/** /**
* Utilities for display handling. * Utilities for display handling.
* *

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.gl; package org.plutoengine.gl;
import org.lwjgl.opengl.ARBFramebufferObject; import org.lwjgl.opengl.ARBFramebufferObject;

View File

@ -1,13 +0,0 @@
package org.plutoengine.gl;
/**
* Denotes the implementing class is a set of OpenGL enums.
*
*
* @author 493msi
*
*/
public interface IOpenGLEnum
{
int getGLID();
}

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.math; package org.plutoengine.math;
import org.joml.Matrix4f; import org.joml.Matrix4f;
@ -24,7 +48,7 @@ public class ProjectionMatrix
} }
/** /**
* Create a centered 2D orthogonal projection Matrix3x2f based on the width and * Create a centered 2D orthogonal projection Matrix4f based on the width and
* height. * height.
* *
* @param width The ortho width * @param width The ortho width

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.math; package org.plutoengine.math;
import org.joml.Matrix3x2f; import org.joml.Matrix3x2f;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.math; package org.plutoengine.math;
import org.joml.Matrix3x2f; import org.joml.Matrix3x2f;

View File

@ -1,3 +1,27 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/** /**
* Math utility classes. * Math utility classes.
* *

View File

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

View File

@ -1,21 +0,0 @@
package org.plutoengine.graphics.gl.fbo;
import org.lwjgl.opengl.GL33;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
public class FramebufferDepthTexture extends FramebufferTexture
{
public FramebufferDepthTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV)
{
super(width, height, magFilter, minFilter, wrapU, wrapV);
}
@Override
public void writeData(long address)
{
GL33.glTexImage2D(this.type, 0, GL33.GL_DEPTH24_STENCIL8, this.width, this.height, 0, GL33.GL_DEPTH_STENCIL, GL33.GL_UNSIGNED_INT_24_8, address);
}
}

View File

@ -1,34 +0,0 @@
package org.plutoengine.graphics.gl.fbo;
import org.lwjgl.system.MemoryUtil;
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
{
public FramebufferTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV)
{
this.bind();
this.setFilteringOptions(magFilter, minFilter);
this.setWrapOptions(wrapU, wrapV);
this.resize(width, height);
}
public FramebufferTexture(int width, int height)
{
this(width, height, MagFilter.LINEAR, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}
public void resize(int width, int height)
{
this.bind();
this.width = width;
this.height = height;
this.writeData(MemoryUtil.NULL);
}
}

View File

@ -7,4 +7,16 @@ description = ""
dependencies { dependencies {
api(project(":plutoengine:plutospritesheet")) api(project(":plutoengine:plutospritesheet"))
api(project(":libra"))
api("io.reactivex.rxjava3", "rxjava", "3.1.4")
implementation("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", "2.12.3")
implementation("org.lwjgl", "lwjgl-yoga")
org.plutoengine.Versions.lwjglNatives.forEach {
runtimeOnly("org.lwjgl", "lwjgl-yoga", classifier = it)
}
implementation("org.commonmark", "commonmark", "0.18.1")
} }

View File

@ -0,0 +1,140 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics;
import org.joml.Matrix3f;
import org.joml.primitives.Rectanglef;
import org.plutoengine.graphics.gui.PlutoGUICommandParser;
import org.plutoengine.graphics.gui.font.PlutoFont;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
import org.plutoengine.libra.text.LiTextInfo;
import org.plutoengine.libra.text.font.LiFontFamily;
import org.plutoengine.libra.text.shaping.TextShaper;
import org.plutoengine.libra.text.shaping.TextStyleOptions;
import java.util.EnumSet;
public class ImmediateFontRenderer
{
public static <T extends PlutoFont<T>> void drawString(float x, float y, String text, LiFontFamily<T> fontFamily, TextStyleOptions style)
{
var font = style.pickFont(fontFamily);
var shaper = font.getDefaultShaper();
var info = shaper.shape(EnumSet.of(TextShaper.EnumFeature.KERNING), font, text, style);
draw(x, y, info, style);
}
public static <T extends PlutoFont<T>> void drawStringNoKern(float x, float y, String text, LiFontFamily<T> fontFamily, TextStyleOptions style)
{
var font = style.pickFont(fontFamily);
var shaper = font.getDefaultShaper();
var info = shaper.shape(EnumSet.noneOf(TextShaper.EnumFeature.class), font, text, style);
draw(x, y, info, style);
}
public static void draw(float x, float y, LiTextInfo info, TextStyleOptions style)
{
var transformBuf = LiCommandBuffer.uncleared();
var fitBox = style.getFitBox();
var initialScale = style.getSize() / PlutoFont.NORMALIZED_PIXEL_HEIGHT;
var scaleX = initialScale;
var scaleY = initialScale;
var bounds = info.getBoundingBox().scale(scaleX, scaleY, new Rectanglef());
if (fitBox != null)
{
// Rescale in sync in both are set to scale
if (style.getOverflowX() == TextStyleOptions.OverflowXStrategy.SCALE_TO_FIT &&
style.getOverflowY() == TextStyleOptions.OverflowYStrategy.SCALE_TO_FIT)
{
var smaller = Math.min(fitBox.lengthX() / bounds.lengthX(), fitBox.lengthY() / bounds.lengthY());
var rescale = Math.min(1.0f, smaller);
scaleX *= rescale;
bounds.scale(rescale, rescale);
}
else
{
if (style.getOverflowX() == TextStyleOptions.OverflowXStrategy.SCALE_TO_FIT)
{
var rescale = Math.min(1.0f, fitBox.lengthX() / bounds.lengthX());
scaleX *= rescale;
bounds.scale(rescale, 1.0f);
}
if (style.getOverflowY() == TextStyleOptions.OverflowYStrategy.SCALE_TO_FIT)
{
var rescale = Math.min(1.0f, fitBox.lengthY() / bounds.lengthY());
scaleY *= rescale;
bounds.scale(1.0f, rescale);
}
}
x += switch (style.getHorizontalAlign()) {
case START -> fitBox.minX - bounds.minX;
case CENTER -> (fitBox.maxX + fitBox.minX) / 2.0f - bounds.lengthX() / 2.0f;
case END -> fitBox.maxX - bounds.lengthX();
};
y += switch (style.getVerticalAlign()) {
case START -> fitBox.minY - bounds.minY;
case CENTER -> (fitBox.maxY + fitBox.minY) / 2.0f - bounds.lengthY() / 2.0f;
case END -> fitBox.maxY - bounds.lengthY();
};
}
else
{
x += switch (style.getHorizontalAlign()) {
case START -> -bounds.minX;
case CENTER -> -bounds.minX + -bounds.lengthX() / 2.0f;
case END -> -bounds.lengthX();
};
y += switch (style.getVerticalAlign()) {
case START -> -bounds.lengthY();
case CENTER -> -bounds.lengthY() / 2.0f;
case END -> 0;
};
}
transformBuf.push(new LiCommandSetTransform(new Matrix3f().scale(scaleX, scaleY, 1.0f).m20(x).m21(y)));
var buf = info.getDrawCommandBuffer();
var commandParser = new PlutoGUICommandParser();
commandParser.add(LiCommandBuffer.cleared());
commandParser.add(transformBuf);
commandParser.add(buf);
try (var drawCalls = commandParser.parse())
{
drawCalls.render();
}
}
}

View File

@ -1,17 +1,40 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics; package org.plutoengine.graphics;
import org.plutoengine.Pluto; import org.plutoengine.Pluto;
import org.plutoengine.graphics.font.FontManager; import org.plutoengine.graphics.gui.BitmapFontShader;
import org.plutoengine.graphics.font.FontShader; import org.plutoengine.graphics.gui.FontShader;
import org.plutoengine.graphics.texture.MagFilter; import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter; import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode; import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture; import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.gui.font.FontRenderer;
import org.plutoengine.mod.IModEntryPoint; import org.plutoengine.mod.IModEntryPoint;
import org.plutoengine.mod.Mod; import org.plutoengine.mod.Mod;
import org.plutoengine.mod.ModEntry; import org.plutoengine.mod.ModEntry;
import org.plutoengine.shader.RenderShaderBuilder; import org.plutoengine.graphics.shader.RenderShaderBuilder;
/** /**
* @author 493msi * @author 493msi
@ -28,7 +51,9 @@ public class PlutoGUIMod implements IModEntryPoint
public static RectangleTexture uiElementsAtlas; public static RectangleTexture uiElementsAtlas;
private static FontShader fontShader; public static FontShader fontShader;
public static BitmapFontShader bitmapFontShader;
public void onLoad(Mod mod) public void onLoad(Mod mod)
{ {
@ -36,10 +61,7 @@ public class PlutoGUIMod implements IModEntryPoint
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false); fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
// Load the default font bitmapFontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexBitmapFontShader#glsl"), mod.getResource("shaders.FragmentBitmapFontShader#glsl")).build(BitmapFontShader.class, false);
FontManager.loadFont(mod.getResource("font.default"));
FontRenderer.load(fontShader);
uiElementsAtlas = new RectangleTexture(); uiElementsAtlas = new RectangleTexture();
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
@ -47,11 +69,8 @@ public class PlutoGUIMod implements IModEntryPoint
public void onUnload() public void onUnload()
{ {
uiElementsAtlas.delete(); uiElementsAtlas.close();
FontManager.unloadAll(); fontShader.close();
FontRenderer.unload();
fontShader.dispose();
} }
} }

View File

@ -1,96 +0,0 @@
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

@ -1,37 +0,0 @@
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,117 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
import org.joml.Matrix3fc;
import org.plutoengine.graphics.vao.attrib.ReservedAttributes;
import org.plutoengine.graphics.shader.uniform.*;
import org.plutoengine.libra.paint.LiColorPaint;
import org.plutoengine.libra.paint.LiGradientPaint;
import org.plutoengine.libra.paint.LiPaint;
import org.plutoengine.graphics.shader.ShaderBase;
import org.plutoengine.graphics.shader.ShaderProgram;
import org.plutoengine.graphics.shader.VertexArrayAttribute;
import org.plutoengine.graphics.shader.uniform.auto.AutoViewportProjection;
import org.plutoengine.util.color.IRGBA;
@ShaderProgram
public final class BitmapFontShader extends ShaderBase implements IGUIShader
{
@AutoViewportProjection
@Uniform(name = "projection")
public UniformMat4 projectionMatrix;
@Uniform(name = "transformation")
public UniformMat3 transformationMatrix;
@Uniform
public UniformInt paintType;
@Uniform
public UniformRGBA paintColor;
@Uniform
public UniformInt paintGradientStopCount;
@Uniform
public UniformArrayRGBA paintGradientColors;
@Uniform
public UniformArrayFloat paintGradientPositions;
@Uniform
public UniformArrayVec2 paintGradientEnds;
@VertexArrayAttribute(ReservedAttributes.POSITION)
public int position;
@VertexArrayAttribute(ReservedAttributes.UV)
public int uvCoords;
@VertexArrayAttribute(2)
public int paintUVCoords;
@Override
public void setTransform(Matrix3fc transform)
{
this.transformationMatrix.load(transform);
}
@Override
public void setPaint(LiPaint paint)
{
switch (paint.getType())
{
case SOLID_COLOR -> {
var col = ((LiColorPaint) paint).getColor();
this.paintType.load(0);
this.paintColor.load(col.getFloatComponentsRGBA());
}
case GRADIENT -> {
var gradPaint = (LiGradientPaint) paint;
this.paintType.load(1);
this.paintGradientEnds.load(gradPaint.getStart(), gradPaint.getEnd());
var stops = gradPaint.getStops();
this.paintGradientStopCount.load(stops.length);
var colors = new IRGBA[stops.length];
var positions = new float[stops.length];
int i = 0;
for (var stop : stops)
{
var col = stop.color();
colors[i] = col.getFloatComponentsRGBA();
positions[i] = stop.position();
i++;
}
this.paintGradientColors.load(colors);
this.paintGradientPositions.load(positions);
}
}
}
}

View File

@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
import org.joml.Matrix3fc;
import org.plutoengine.graphics.vao.attrib.ReservedAttributes;
import org.plutoengine.graphics.shader.uniform.*;
import org.plutoengine.libra.paint.LiColorPaint;
import org.plutoengine.libra.paint.LiGradientPaint;
import org.plutoengine.libra.paint.LiPaint;
import org.plutoengine.graphics.shader.ShaderBase;
import org.plutoengine.graphics.shader.ShaderProgram;
import org.plutoengine.graphics.shader.VertexArrayAttribute;
import org.plutoengine.graphics.shader.uniform.auto.AutoViewportProjection;
import org.plutoengine.util.color.IRGBA;
@ShaderProgram
public final class FontShader extends ShaderBase implements ISDFTextShader
{
@AutoViewportProjection
@Uniform(name = "projection")
public UniformMat4 projectionMatrix;
@Uniform(name = "transformation")
public UniformMat3 transformationMatrix;
@Uniform
public UniformInt paintType;
@Uniform
public UniformRGBA paintColor;
@Uniform
public UniformInt paintGradientStopCount;
@Uniform
public UniformArrayRGBA paintGradientColors;
@Uniform
public UniformArrayFloat paintGradientPositions;
@Uniform
public UniformArrayVec2 paintGradientEnds;
@Uniform
public UniformFloat pxScale;
@VertexArrayAttribute(ReservedAttributes.POSITION)
public int position;
@VertexArrayAttribute(ReservedAttributes.UV)
public int uvCoords;
@VertexArrayAttribute(2)
public int page;
@VertexArrayAttribute(3)
public int paintUVCoords;
@Override
public void setTransform(Matrix3fc transform)
{
this.transformationMatrix.load(transform);
}
@Override
public void setPaint(LiPaint paint)
{
switch (paint.getType())
{
case SOLID_COLOR -> {
var col = ((LiColorPaint) paint).getColor();
this.paintType.load(0);
this.paintColor.load(col.getFloatComponentsRGBA());
}
case GRADIENT -> {
var gradPaint = (LiGradientPaint) paint;
this.paintType.load(1);
this.paintGradientEnds.load(gradPaint.getStart(), gradPaint.getEnd());
var stops = gradPaint.getStops();
this.paintGradientStopCount.load(stops.length);
var colors = new IRGBA[stops.length];
var positions = new float[stops.length];
int i = 0;
for (var stop : stops)
{
var col = stop.color();
colors[i] = col.getFloatComponentsRGBA();
positions[i] = stop.position();
i++;
}
this.paintGradientColors.load(colors);
this.paintGradientPositions.load(positions);
}
}
}
@Override
public void setPixelScale(float scale)
{
this.pxScale.load(scale);
}
}

View File

@ -0,0 +1,35 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
import org.joml.Matrix3fc;
import org.plutoengine.libra.paint.LiPaint;
public interface IGUIShader
{
void setTransform(Matrix3fc transform);
void setPaint(LiPaint paint);
}

View File

@ -0,0 +1,30 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
public interface ISDFTextShader extends IGUIShader
{
void setPixelScale(float scale);
}

View File

@ -0,0 +1,168 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
import org.plutoengine.graphics.gl.DrawMode;
import org.plutoengine.graphics.vao.VertexArray;
import org.plutoengine.graphics.vao.VertexArrayBuilder;
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMesh;
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
import org.plutoengine.libra.command.AbstractGUICommandParser;
import org.plutoengine.libra.command.IGUIRenderer;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
import org.plutoengine.libra.command.impl.LiCommandSpecial;
import org.plutoengine.graphics.shader.ShaderBase;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
public class PlutoGUICommandParser extends AbstractGUICommandParser
{
@Override
protected IGUIRenderer parse(LiCommandBuffer mergedBuffer)
{
var drawCalls = new ArrayList<Runnable>();
var closeCalls = new ArrayDeque<Runnable>();
var alreadyEnabledAttribs = new HashSet<Integer>();
var currentShader = (ShaderBase & IGUIShader) null;
for (var cmd : mergedBuffer)
{
var type = cmd.getType();
switch (type)
{
case DRAW_MESH -> {
if (!(cmd instanceof PlutoCommandDrawMesh drawCmd))
throw new IllegalStateException();
var vab = new VertexArrayBuilder();
var data = drawCmd.getData();
var attrInfo = drawCmd.getAttributeInfo();
data.forEach((attr, val) -> vab.attrib(attr, attrInfo.get(attr).dimensions(), val.flip()));
var indices = drawCmd.getIndices();
if (indices != null)
vab.indices(indices.flip());
var vao = vab.build();
var attribs = vao.getUsedAttributes();
var attribsToEnable = new HashSet<>(attribs);
attribsToEnable.removeAll(alreadyEnabledAttribs);
alreadyEnabledAttribs.addAll(attribsToEnable);
if (drawCmd instanceof PlutoCommandDrawMeshDirectBuffer dBuf)
dBuf.close();
drawCalls.add(() -> {
vao.bind();
attribsToEnable.forEach(VertexArray::enableAttribute);
vao.draw(DrawMode.TRIANGLES);
});
closeCalls.add(vao::close);
}
case SET_TRANSFORM -> {
if (!(cmd instanceof LiCommandSetTransform transformCmd))
throw new IllegalStateException();
var shaderCapture = currentShader;
assert shaderCapture != null;
drawCalls.add(() -> shaderCapture.setTransform(transformCmd.getTransform()));
}
case SET_PAINT -> {
if (!(cmd instanceof LiCommandSetPaint paintCmd))
throw new IllegalStateException();
var shaderCapture = currentShader;
assert shaderCapture != null;
drawCalls.add(() -> shaderCapture.setPaint(paintCmd.getPaint()));
}
case SWITCH_SHADER -> {
if (!(cmd instanceof PlutoCommandSwitchShader swSh))
throw new IllegalStateException();
var shaderCapture = currentShader = (ShaderBase & IGUIShader) swSh.getShader();
assert shaderCapture != null;
drawCalls.add(() -> shaderCapture.start());
}
case SWITCH_TEXTURE -> {
if (!(cmd instanceof PlutoCommandSwitchTexture swTx))
throw new IllegalStateException();
var textureCapture = swTx.getTexture();
assert textureCapture != null;
drawCalls.add(textureCapture::bind);
}
case SPECIAL -> {
if (!(cmd instanceof LiCommandSpecial cSp))
throw new IllegalStateException();
var af = cSp.getAction();
drawCalls.add(() -> af.accept(mergedBuffer));
}
}
}
return new IGUIRenderer()
{
@Override
public void render()
{
drawCalls.forEach(Runnable::run);
}
@Override
public void close()
{
var it = closeCalls.descendingIterator();
while (it.hasNext())
{
var call = it.next();
call.run();
}
}
};
}
}

View File

@ -0,0 +1,97 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL33;
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.tpl.ImageLoader;
import org.plutoengine.tpl.ImageY;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.List;
public class SDFTextureArray extends Texture
{
public SDFTextureArray()
{
super(GL33.GL_TEXTURE_2D_ARRAY, 2);
}
@Override
public boolean supportsMipMapping()
{
return false;
}
@Override
public void writeData(long address)
{
GL33.glTexImage3D(GL33.GL_TEXTURE_2D_ARRAY, 0, GL33.GL_R8, this.width, this.height, this.depth, 0, GL33.GL_RED, GL11.GL_UNSIGNED_BYTE, address);
}
public void loadImg(List<BufferedImage> imageData, int width, int height, int depth, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap)
{
var data = imageData.stream()
.map(ImageLoader::loadImageGrayscale)
.map(ImageY::getData)
.toList();
this.load(data, width, height, depth, magFilter, minFilter, wrap);
}
public void load(List<ByteBuffer> imageData, int width, int height, int depth, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap)
{
this.width = width;
this.height = height;
this.depth = imageData.size();
this.bind();
this.setFilteringOptions(magFilter, minFilter);
this.setWrapOptions(wrap);
this.writeData(0);
for (int i = 0; i < imageData.size(); i++)
{
var img = imageData.get(i);
GL33.glTexSubImage3D(GL33.GL_TEXTURE_2D_ARRAY, 0,
0, 0, i,
this.width, this.height, 1,
GL33.GL_RED, GL11.GL_UNSIGNED_BYTE, img);
}
}
@Override
public void load(ByteBuffer imageData, int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap)
{
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,128 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.command;
import org.plutoengine.graphics.vao.attrib.AttributeInfo;
import org.plutoengine.libra.command.impl.LiCommand;
import org.plutoengine.libra.command.impl.LiCommandDrawMesh;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
public abstract sealed class PlutoCommandDrawMesh extends LiCommandDrawMesh permits PlutoCommandDrawMeshDirectBuffer, PlutoCommandDrawMeshHeap
{
protected final Map<Integer, AttributeInfo> attributeInfo;
protected final Map<Integer, Buffer> data;
protected IntBuffer indices;
public PlutoCommandDrawMesh()
{
this.attributeInfo = new TreeMap<>();
this.data = new TreeMap<>();
}
public IntBuffer getIndices()
{
if (this.indices == null)
return null;
return this.indices;
}
public Map<Integer, AttributeInfo> getAttributeInfo()
{
return Collections.unmodifiableMap(this.attributeInfo);
}
public Map<Integer, Buffer> getData()
{
return Collections.unmodifiableMap(this.data);
}
public void addIndices(int[] data)
{
if (data == null)
return;
this.addIndices(IntBuffer.wrap(data));
}
public abstract void addIndices(IntBuffer data);
public void addAttribute(int attrib, float[] data, int dimensions)
{
if (data == null)
return;
this.addAttribute(attrib, FloatBuffer.wrap(data), dimensions);
}
public abstract void addAttribute(int attrib, FloatBuffer data, int dimensions);
public void addAttribute(int attrib, int[] data, int dimensions)
{
if (data == null)
return;
this.addAttribute(attrib, IntBuffer.wrap(data), dimensions);
}
public abstract void addAttribute(int attrib, IntBuffer data, int dimensions);
@Override
public boolean supportsMerge(LiCommand other)
{
if (!(other instanceof PlutoCommandDrawMesh pcdm))
return false;
return this.attributeInfo.equals(pcdm.attributeInfo);
}
@Override
public PlutoCommandDrawMesh merge(LiCommand other)
{
if (!(other instanceof PlutoCommandDrawMesh pcdm))
throw new UnsupportedOperationException();
pcdm.data.forEach((k, v) -> {
var attrInfo = this.attributeInfo.get(k);
switch (attrInfo.type())
{
case FLOAT -> this.addAttribute(k, (FloatBuffer) v, attrInfo.dimensions());
case INT -> this.addAttribute(k, (IntBuffer) v, attrInfo.dimensions());
case UNSIGNED_INT -> throw new UnsupportedOperationException();
}
});
this.addIndices(pcdm.getIndices());
return this;
}
}

View File

@ -0,0 +1,166 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.command;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.graphics.vao.attrib.AttributeInfo;
import org.plutoengine.graphics.vbo.EnumArrayBufferType;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public final class PlutoCommandDrawMeshDirectBuffer extends PlutoCommandDrawMesh implements AutoCloseable
{
public void addIndices(IntBuffer data)
{
if (data == null)
return;
if (this.indices == null)
{
if (data.isDirect())
{
this.indices = data;
var size = this.indices.remaining();
this.indices.limit(size);
this.indices.position(size);
return;
}
else
{
this.indices = MemoryUtil.memAllocInt(data.remaining());
}
}
if (this.indices.remaining() < data.remaining())
{
var newSize = Math.max(this.indices.capacity() << 1, this.indices.capacity() + data.remaining());
this.indices = MemoryUtil.memRealloc(this.indices, newSize);
}
this.indices.put(data);
if (data.isDirect())
MemoryUtil.memFree(data);
}
public void addAttribute(int attrib, FloatBuffer data, int dimensions)
{
if (data == null)
return;
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.FLOAT, attrib, dimensions));
if (meta.dimensions() != dimensions)
throw new IllegalArgumentException("Attribute dimensions mismatch!");
this.data.compute(attrib, (k, v) -> {
if (v == null)
{
if (data.isDirect())
{
var size = data.remaining();
data.limit(size);
data.position(size);
return data;
}
else
{
return MemoryUtil.memAllocFloat(data.remaining()).put(data);
}
}
if (!(v instanceof FloatBuffer fab))
throw new IllegalArgumentException();
if (data.remaining() > fab.remaining())
{
var newSize = Math.max(fab.capacity() << 1, fab.capacity() + data.remaining());
fab = MemoryUtil.memRealloc(fab, newSize);
}
fab.put(data);
if (data.isDirect())
MemoryUtil.memFree(data);
return fab;
});
}
public void addAttribute(int attrib, IntBuffer data, int dimensions)
{
if (data == null)
return;
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.INT, attrib, dimensions));
if (meta.dimensions() != dimensions)
throw new IllegalArgumentException("Attribute dimensions mismatch!");
this.data.compute(attrib, (k, v) -> {
if (v == null)
{
if (data.isDirect())
{
var size = data.remaining();
data.limit(size);
data.position(size);
return data;
}
else
{
return MemoryUtil.memAllocInt(data.remaining()).put(data);
}
}
if (!(v instanceof IntBuffer iab))
throw new IllegalArgumentException();
if (data.remaining() > iab.remaining())
{
var newSize = Math.max(iab.capacity() << 1, iab.capacity() + data.remaining());
iab = MemoryUtil.memRealloc(iab, newSize);
}
iab.put(data);
if (data.isDirect())
MemoryUtil.memFree(data);
return iab;
});
}
@Override
public void close()
{
MemoryUtil.memFree(this.indices);
this.data.values()
.forEach(MemoryUtil::memFree);
}
}

View File

@ -0,0 +1,121 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.command;
import org.plutoengine.graphics.vao.attrib.AttributeInfo;
import org.plutoengine.graphics.vbo.EnumArrayBufferType;
import org.plutoengine.libra.command.impl.LiCommand;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public final class PlutoCommandDrawMeshHeap extends PlutoCommandDrawMesh
{
public void addIndices(IntBuffer data)
{
if (data == null)
return;
if (this.indices == null)
this.indices = IntBuffer.allocate(data.remaining());
if (this.indices.remaining() < data.remaining())
this.indices = IntBuffer.allocate(Math.max(this.indices.capacity() << 1, this.indices.capacity() + data.remaining())).put(this.indices.flip());
this.indices.put(data);
}
public void addAttribute(int attrib, FloatBuffer data, int dimensions)
{
if (data == null)
return;
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.FLOAT, attrib, dimensions));
if (meta.dimensions() != dimensions)
throw new IllegalArgumentException("Attribute dimensions mismatch!");
this.data.compute(attrib, (k, v) -> {
if (v == null)
return FloatBuffer.allocate(data.remaining()).put(data);
if (!(v instanceof FloatBuffer fab))
throw new IllegalArgumentException();
if (data.remaining() <= fab.remaining())
return fab.put(data);
var newBuf = FloatBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
return newBuf.put(fab.flip()).put(data);
});
}
public void addAttribute(int attrib, IntBuffer data, int dimensions)
{
if (data == null)
return;
var meta = this.attributeInfo.computeIfAbsent(attrib, k -> new AttributeInfo(EnumArrayBufferType.INT, attrib, dimensions));
if (meta.dimensions() != dimensions)
throw new IllegalArgumentException("Attribute dimensions mismatch!");
this.data.compute(attrib, (k, v) -> {
if (v == null)
return IntBuffer.allocate(data.remaining()).put(data);
if (!(v instanceof IntBuffer iab))
throw new IllegalArgumentException();
if (data.remaining() <= iab.remaining())
return iab.put(data);
var newBuf = IntBuffer.allocate(Math.max(v.capacity() << 1, v.capacity() + data.remaining()));
return newBuf.put(iab.flip()).put(data);
});
}
@Override
public PlutoCommandDrawMeshHeap merge(LiCommand other)
{
if (!(other instanceof PlutoCommandDrawMesh pcdm))
throw new UnsupportedOperationException();
pcdm.data.forEach((k, v) -> {
var attrInfo = this.attributeInfo.get(k);
switch (attrInfo.type())
{
case FLOAT -> this.addAttribute(k, (FloatBuffer) v, attrInfo.dimensions());
case INT -> this.addAttribute(k, (IntBuffer) v, attrInfo.dimensions());
case UNSIGNED_INT -> throw new UnsupportedOperationException();
}
});
this.addIndices(pcdm.indices);
return this;
}
}

View File

@ -0,0 +1,36 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.command;
import org.plutoengine.graphics.gui.IGUIShader;
import org.plutoengine.libra.command.impl.LiCommandSwitchShader;
public class PlutoCommandSwitchShader extends LiCommandSwitchShader<IGUIShader>
{
public PlutoCommandSwitchShader(IGUIShader shader)
{
super(shader);
}
}

View File

@ -0,0 +1,36 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.command;
import org.plutoengine.graphics.texture.Texture;
import org.plutoengine.libra.command.impl.LiCommandSwitchTexture;
public class PlutoCommandSwitchTexture extends LiCommandSwitchTexture<Texture>
{
public PlutoCommandSwitchTexture(Texture texture)
{
super(texture);
}
}

View File

@ -0,0 +1,183 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font;
import org.plutoengine.graphics.texture.Texture;
import org.plutoengine.libra.text.font.GlyphMetrics;
import org.plutoengine.libra.text.font.LiFont;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import java.util.Map;
public abstract class PlutoFont<T extends PlutoFont<? super T>> extends LiFont<PlutoFont<T>.PlutoGlyphAtlas, PlutoFont<T>.PlutoGlyphMetrics> implements AutoCloseable
{
public static final int NORMALIZED_PIXEL_HEIGHT = 64;
protected int descent;
protected int ascent;
protected int lineGap;
protected int lineAdvance;
protected float scale;
protected Map<Integer, Integer> glyphIndexLookup;
protected Map<PlutoFont.KerningPair, Integer> kerningTable;
public record KerningPair(int left, int right)
{
}
protected PlutoFont(String name)
{
super(name);
}
public abstract IShapingStrategy<? extends PlutoFont<? super T>.PlutoGlyphMetrics, ? extends PlutoFont<? super T>.PlutoGlyphAtlas, T> getDefaultShaper();
public float getScale()
{
return this.scale;
}
public int getAscent()
{
return this.ascent;
}
public int getDescent()
{
return this.descent;
}
public int getLineGap()
{
return this.lineGap;
}
public int getLineAdvance()
{
return this.lineAdvance;
}
public int getKerningOffset(int left, int right)
{
var sk = new PlutoFont.KerningPair(glyphIndexLookup.getOrDefault(left, -1), glyphIndexLookup.getOrDefault(right, -1));
return this.kerningTable.getOrDefault(sk, 0);
}
@Override
public void close()
{
var tex = this.getGlyphAtlas().getGlyphAtlasTexture();
tex.close();
}
public class PlutoGlyphAtlas extends LiFont<PlutoFont<T>.PlutoGlyphAtlas, PlutoFont<T>.PlutoGlyphMetrics>.GlyphAtlas
{
private Texture glyphAtlasTexture;
public void setGlyphAtlasTexture(Texture glyphAtlasTexture)
{
this.glyphAtlasTexture = glyphAtlasTexture;
}
public Texture getGlyphAtlasTexture()
{
return this.glyphAtlasTexture;
}
}
public class PlutoGlyphMetrics extends GlyphMetrics
{
private final int codepoint;
protected int advanceX;
protected int leftSideBearing;
protected int cx0;
protected int cy0;
protected int cx1;
protected int cy1;
protected int xOrigin;
protected int yOrigin;
public PlutoGlyphMetrics(int codepoint)
{
this.codepoint = codepoint;
}
public int getAdvanceX()
{
return this.advanceX;
}
public int getLeftSideBearing()
{
return this.leftSideBearing;
}
public int getCodepoint()
{
return this.codepoint;
}
public int getKerning(int cp)
{
return PlutoFont.this.getKerningOffset(this.codepoint, cp);
}
public int getCX0()
{
return this.cx0;
}
public int getCY0()
{
return this.cy0;
}
public int getCX1()
{
return this.cx1;
}
public int getCY1()
{
return this.cy1;
}
public int getXOrigin()
{
return this.xOrigin;
}
public int getYOrigin()
{
return this.yOrigin;
}
}
}

View File

@ -0,0 +1,151 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font.bitmap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.joml.primitives.Rectanglef;
import org.plutoengine.graphics.gui.font.PlutoFont;
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.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
public class BitmapFont extends PlutoFont<BitmapFont>
{
private int padding;
private BitmapFont(String name)
{
super(name);
}
public int getPadding()
{
return this.padding;
}
@Override
public IShapingStrategy<BitmapFont.PlutoGlyphMetrics, BitmapFont.PlutoGlyphAtlas, BitmapFont> getDefaultShaper()
{
return new BitmapTextShaper();
}
public static BitmapFont load(Path path)
{
try
{
var objectMapper = new ObjectMapper(YAMLFactory.builder().build());
BitmapFontInfo info;
try (var br = Files.newBufferedReader(path))
{
info = objectMapper.readValue(br, BitmapFontInfo.class);
}
var font = new BitmapFont(info.name());
Logger.logf(SmartSeverity.ADDED, "Loading font: %s%n", font.getName());
font.atlas = font.new PlutoGlyphAtlas();
var tex = new RectangleTexture();
var dir = path.getParent();
var texPath = dir.resolve(info.atlas());
var magFilter = switch (info.filtering()) {
case NEAREST -> MagFilter.NEAREST;
case LINEAR -> MagFilter.LINEAR;
};
tex.load(texPath, magFilter, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
font.atlas.setGlyphAtlasTexture(tex);
var meta = info.meta();
font.ascent = meta.ascent();
font.descent = meta.descent();
font.lineGap = meta.lineGap();
font.lineAdvance = font.ascent - font.descent + font.lineGap;
font.scale = 1.0f / meta.scale() * PlutoFont.NORMALIZED_PIXEL_HEIGHT;
var kern = info.kern();
font.kerningTable = new HashMap<>();
kern.forEach(e -> font.kerningTable.put(new PlutoFont.KerningPair(e.left(), e.right()), e.offset()));
var glyphs = info.glyphs();
font.glyphIndexLookup = new HashMap<>();
font.padding = meta.padding();
glyphs.forEach(glyph -> {
var sprite = glyph.sprite();
var cp = glyph.cp();
var glyphMetrics = font.new PlutoGlyphMetrics(cp) {{
this.advanceX = glyph.advance();
this.leftSideBearing = glyph.leftBearing();
this.xOrigin = (this.leftSideBearing - font.padding) * PlutoFont.NORMALIZED_PIXEL_HEIGHT / meta.scale();
this.yOrigin = (glyph.topOffset() - font.ascent - font.padding) * PlutoFont.NORMALIZED_PIXEL_HEIGHT / meta.scale();
this.cx0 = this.leftSideBearing + font.padding;
this.cy0 = glyph.topOffset() - font.ascent + font.padding;
this.cx1 = this.leftSideBearing + sprite.w() - font.padding;
this.cy1 = sprite.h() - font.ascent - font.padding;
}};
var rect = new Rectanglef(
sprite.x(),
sprite.y(),
sprite.x() + sprite.w(),
sprite.y() + sprite.h()
);
var glyphInfo = new GlyphInfo<>(font.atlas, 0, rect);
font.addGlyph(cp, glyphMetrics, glyphInfo);
});
return font;
}
catch (Exception e)
{
Logger.logf(SmartSeverity.ERROR, "Failed to load font: %s%n", path);
Logger.log(e);
return null;
}
}
}

View File

@ -0,0 +1,89 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font.bitmap;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.ext.NioPathDeserializer;
import java.nio.file.Path;
import java.util.List;
record BitmapFontInfo(
String name,
FontInfoMeta meta,
@JsonDeserialize(using = NioPathDeserializer.class) Path atlas,
Filtering filtering,
@JsonDeserialize(contentAs = KerningInfo.class) List<KerningInfo> kern,
@JsonDeserialize(contentAs = GlyphInfo.class) List<GlyphInfo> glyphs
)
{
enum Filtering
{
NEAREST,
LINEAR
}
record FontInfoMeta(
int ascent,
int descent,
int lineGap,
int padding,
int scale
)
{
}
record KerningInfo(
int left,
int right,
int offset
)
{
}
record GlyphInfo(
int cp,
int glyphClass,
GlyphSprite sprite,
int leftBearing,
int advance,
int topOffset
)
{
}
record GlyphSprite(
int x,
int y,
int w,
int h
)
{
}
}

View File

@ -0,0 +1,192 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font.bitmap;
import org.joml.primitives.Rectanglef;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.graphics.PlutoGUIMod;
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
import org.plutoengine.libra.text.LiTextInfo;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.libra.text.shaping.TextShaper;
import org.plutoengine.libra.text.shaping.TextStyleOptions;
import java.util.EnumSet;
import java.util.Objects;
public class BitmapTextShaper implements IShapingStrategy<BitmapFont.PlutoGlyphMetrics, BitmapFont.PlutoGlyphAtlas, BitmapFont>
{
private LiCommandBuffer commandBuffer;
public BitmapTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
{
this.commandBuffer = commandBuffer;
return this;
}
@Override
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, BitmapFont font, String text, TextStyleOptions style)
{
var commandBuf = Objects.requireNonNullElseGet(this.commandBuffer, LiCommandBuffer::uncleared);
var atlas = font.getGlyphAtlas();
var atlasTexture = atlas.getGlyphAtlasTexture();
var texSwitch = new PlutoCommandSwitchTexture(atlasTexture);
commandBuf.push(texSwitch);
var shader = PlutoGUIMod.bitmapFontShader;
var shaderSwitch = new PlutoCommandSwitchShader(shader);
commandBuf.push(shaderSwitch);
commandBuf.push(new LiCommandSetPaint(style.getPaint()));
var cpCount = (int) text.codePoints().count();
var mesh = new PlutoCommandDrawMeshDirectBuffer();
final var quadVerts = 4;
final var twoTriVerts = 6;
var vertDim = 2;
var vertexBuf = MemoryUtil.memAllocFloat(vertDim * quadVerts * cpCount);
var uvDim = 2;
var uvBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var paintUVBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var indexBuf = MemoryUtil.memAllocInt(twoTriVerts * cpCount);
var cpIt = text.codePoints().iterator();
var indices = new int[] {
0, 1, 2,
0, 2, 3
};
float[] vertices = new float[vertDim * quadVerts];
float[] uvs = new float[uvDim * quadVerts];
float scale = font.getScale();
float x = 0;
float y = 0;
int padding = font.getPadding();
GlyphInfo<?, ?> info;
BitmapFont.PlutoGlyphMetrics metrics = null;
int cp;
float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY, minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;
while (cpIt.hasNext())
{
cp = cpIt.next();
switch (cp)
{
case '\n' -> {
x = 0;
y += font.getLineAdvance() * scale;
continue;
}
}
if (metrics != null && features.contains(TextShaper.EnumFeature.KERNING))
x += metrics.getKerning(cp) * scale;
metrics = font.getGlyphMetrics(cp);
info = atlas.getGlyph(cp);
if (metrics == null)
continue;
if (info != null)
{
float gx = x + metrics.getXOrigin();
float gy = y + metrics.getYOrigin() + font.getAscent() * scale;
float xLow = gx - padding;
float xHigh = gx + metrics.getCX1() * scale - metrics.getCX0() * scale + padding;
float yLow = gy - padding;
float yHigh = gy + metrics.getCY1() * scale - metrics.getCY0() * scale + padding;
minX = Math.min(minX, xLow);
minY = Math.min(minY, yLow);
maxX = Math.max(maxX, xHigh);
maxY = Math.max(maxY, yHigh);
vertices[6] = vertices[0] = xLow;
vertices[3] = vertices[1] = yHigh;
vertices[4] = vertices[2] = xHigh;
vertices[7] = vertices[5] = yLow;
var uvRect = info.getRect();
uvs[6] = uvs[0] = uvRect.minX;
uvs[3] = uvs[1] = atlasTexture.getHeight() - uvRect.maxY;
uvs[4] = uvs[2] = uvRect.maxX;
uvs[7] = uvs[5] = atlasTexture.getHeight() - uvRect.minY;
vertexBuf.put(vertices);
uvBuf.put(uvs);
indexBuf.put(indices);
indices[0] += quadVerts;
indices[1] += quadVerts;
indices[2] += quadVerts;
indices[3] += quadVerts;
indices[4] += quadVerts;
indices[5] += quadVerts;
}
x += metrics.getAdvanceX() * scale;
}
vertexBuf.flip();
while (vertexBuf.hasRemaining())
{
paintUVBuf.put((vertexBuf.get() - minX) / (maxX - minX));
paintUVBuf.put((vertexBuf.get() - minY) / (maxY - minY));
}
mesh.addAttribute(shader.position, vertexBuf.rewind(), vertDim);
mesh.addAttribute(shader.uvCoords, uvBuf.flip(), uvDim);
mesh.addAttribute(shader.paintUVCoords, paintUVBuf.flip(), uvDim);
mesh.addIndices(indexBuf.flip());
commandBuf.push(mesh);
return new LiTextInfo(commandBuf, new Rectanglef(minX, minY, maxX, maxY));
}
}

View File

@ -0,0 +1,263 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font.stbttf;
import org.joml.primitives.Rectanglef;
import org.lwjgl.stb.*;
import org.lwjgl.system.MemoryStack;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.graphics.gui.SDFTextureArray;
import org.plutoengine.graphics.gui.font.PlutoFont;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.WritableRaster;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.stream.IntStream;
public class STBTTFont extends PlutoFont<STBTTFont>
{
public static final int SDF_PADDING = 8;
public static final int SHEET_SIZE = 1024;
private STBTTFont(String name)
{
super(name);
}
@Override
public IShapingStrategy<STBTTFont.PlutoGlyphMetrics, STBTTFont.PlutoGlyphAtlas, STBTTFont> getDefaultShaper()
{
return new STBTTTextShaper();
}
public static STBTTFont load(Path path)
{
try (var stack = MemoryStack.stackPush())
{
var fontInfo = STBTTFontinfo.calloc(stack);
var data = BufferHelper.readToByteBuffer(path);
if (!STBTruetype.stbtt_InitFont(fontInfo, data))
{
Logger.logf(SmartSeverity.ERROR, "Failed to load font: %s%n", path);
return null;
}
var nameBuf = STBTruetype.stbtt_GetFontNameString(fontInfo,
STBTruetype.STBTT_PLATFORM_ID_MICROSOFT,
STBTruetype.STBTT_MS_EID_UNICODE_BMP,
STBTruetype.STBTT_MS_LANG_ENGLISH,
1);
if (nameBuf == null)
{
Logger.logf(SmartSeverity.ERROR, "Failed to load font: %s%n", path);
return null;
}
var name = StandardCharsets.UTF_16BE.decode(nameBuf).toString();
Logger.logf(SmartSeverity.ADDED, "Loading font: %s%n", name);
var font = new STBTTFont(name);
font.scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, NORMALIZED_PIXEL_HEIGHT);
var ascentBuf = stack.callocInt(1);
var descentBuf = stack.callocInt(1);
var lineGapBuf = stack.callocInt(1);
STBTruetype.stbtt_GetFontVMetrics(fontInfo, ascentBuf, descentBuf, lineGapBuf);
var codepoints = IntStream.rangeClosed(0x0000, 0x04ff);
var sdfWidthBuf = stack.mallocInt(1);
var sdfHeightBuf = stack.mallocInt(1);
var xOffsBuf = stack.mallocInt(1);
var yOffsBuf = stack.mallocInt(1);
var rectPacker = STBRPContext.malloc(stack);
var nodes = STBRPNode.calloc(SHEET_SIZE * 4);
STBRectPack.stbrp_init_target(rectPacker, SHEET_SIZE, SHEET_SIZE, nodes);
STBRectPack.stbrp_setup_allow_out_of_mem(rectPacker, true);
var rect = STBRPRect.malloc(1, stack);
var sheets = new ArrayList<BufferedImage>();
var atlas = new BufferedImage(SHEET_SIZE, SHEET_SIZE, BufferedImage.TYPE_BYTE_GRAY);
var graphics = atlas.getGraphics();
var colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
var colorModel = new ComponentColorModel(colorSpace, new int[] { 8 }, false,false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
font.atlas = font.new PlutoGlyphAtlas();
font.glyphIndexLookup = new HashMap<>();
var advanceWidth = stack.mallocInt(1);
var leftSideBearingBuf = stack.mallocInt(1);
var cx0Buf = stack.mallocInt(1);
var cy0Buf = stack.mallocInt(1);
var cx1Buf = stack.mallocInt(1);
var cy1Buf = stack.mallocInt(1);
var onedgeValue = 128;
var pixelDistScale = onedgeValue / (float) SDF_PADDING;
for (var cp : codepoints.toArray())
{
var buf = STBTruetype.stbtt_GetCodepointSDF(fontInfo,
font.scale,
cp,
SDF_PADDING,
(byte) onedgeValue,
pixelDistScale,
sdfWidthBuf,
sdfHeightBuf,
xOffsBuf,
yOffsBuf);
var width = sdfWidthBuf.get(0);
var height = sdfHeightBuf.get(0);
var glyphInfo = (GlyphInfo<STBTTFont.PlutoGlyphAtlas, STBTTFont.PlutoGlyphMetrics>) null;
if (buf != null)
{
rect.w(width);
rect.h(height);
if (STBRectPack.stbrp_pack_rects(rectPacker, rect) == 0)
{
sheets.add(atlas);
atlas = new BufferedImage(SHEET_SIZE, SHEET_SIZE, BufferedImage.TYPE_BYTE_GRAY);
graphics = atlas.getGraphics();
STBRectPack.stbrp_init_target(rectPacker, SHEET_SIZE, SHEET_SIZE, nodes);
STBRectPack.stbrp_setup_allow_out_of_mem(rectPacker, true);
STBRectPack.stbrp_pack_rects(rectPacker, rect);
}
var dataBuf = new DataBuffer(DataBuffer.TYPE_BYTE, width * height) {
@Override
public int getElem(int bank, int i)
{
return buf.get(i);
}
@Override
public void setElem(int bank, int i, int val)
{
buf.put(i, (byte) val);
}
};
var sampleModel = colorModel.createCompatibleSampleModel(width, height);
var raster = new WritableRaster(sampleModel, dataBuf, new Point()) {};
var image = new BufferedImage(colorModel, raster, false, null);
graphics.drawImage(image, rect.x(), rect.y(), null);
var glyphRect = new Rectanglef(
rect.x() / (float) SHEET_SIZE,
rect.y() / (float) SHEET_SIZE,
(rect.x() + rect.w()) / (float) SHEET_SIZE,
(rect.y() + rect.h()) / (float) SHEET_SIZE);
glyphInfo = new GlyphInfo<>(font.atlas, sheets.size(), glyphRect);
STBTruetype.stbtt_FreeSDF(buf);
}
STBTruetype.stbtt_GetCodepointHMetrics(fontInfo, cp, advanceWidth, leftSideBearingBuf);
var glyphMetrics = font.new PlutoGlyphMetrics(cp) {{
this.advanceX = advanceWidth.get(0);
this.leftSideBearing = leftSideBearingBuf.get(0);
this.xOrigin = xOffsBuf.get(0);
this.yOrigin = yOffsBuf.get(0);
STBTruetype.stbtt_GetCodepointBox(fontInfo, cp, cx0Buf, cy0Buf, cx1Buf, cy1Buf);
this.cx0 = cx0Buf.get(0);
this.cy0 = cy0Buf.get(0);
this.cx1 = cx1Buf.get(0);
this.cy1 = cy1Buf.get(0);
}};
font.addGlyph(cp, glyphMetrics, glyphInfo);
font.glyphIndexLookup.put(cp, STBTruetype.stbtt_FindGlyphIndex(fontInfo, cp));
}
if (!sheets.contains(atlas))
sheets.add(atlas);
nodes.free();
var kerningTableLength = STBTruetype.stbtt_GetKerningTableLength(fontInfo);
try (var kerningTable = STBTTKerningentry.malloc(kerningTableLength))
{
STBTruetype.stbtt_GetKerningTable(fontInfo, kerningTable);
font.kerningTable = new HashMap<>();
kerningTable.forEach(e -> font.kerningTable.put(new PlutoFont.KerningPair(e.glyph1(), e.glyph2()), e.advance()));
}
font.ascent = ascentBuf.get(0);
font.descent = descentBuf.get(0);
font.lineGap = lineGapBuf.get(0);
font.lineAdvance = font.ascent - font.descent + font.lineGap;
var tex = new SDFTextureArray();
tex.loadImg(sheets, SHEET_SIZE, SHEET_SIZE, sheets.size(), MagFilter.LINEAR, MinFilter.LINEAR, WrapMode.MIRROR_CLAMP_TO_EDGE, WrapMode.MIRROR_CLAMP_TO_EDGE);
font.atlas.setGlyphAtlasTexture(tex);
return font;
}
catch (Exception e)
{
Logger.logf(SmartSeverity.ERROR, "Failed to load font: %s%n", path);
Logger.log(e);
return null;
}
}
}

View File

@ -0,0 +1,202 @@
/*
* MIT License
*
* Copyright (c) 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.plutoengine.graphics.gui.font.stbttf;
import org.joml.primitives.Rectanglef;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.graphics.PlutoGUIMod;
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
import org.plutoengine.libra.command.impl.LiCommandSpecial;
import org.plutoengine.libra.text.LiTextInfo;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.libra.text.shaping.TextShaper;
import org.plutoengine.libra.text.shaping.TextStyleOptions;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Objects;
public class STBTTTextShaper implements IShapingStrategy<STBTTFont.PlutoGlyphMetrics, STBTTFont.PlutoGlyphAtlas, STBTTFont>
{
private LiCommandBuffer commandBuffer;
public STBTTTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
{
this.commandBuffer = commandBuffer;
return this;
}
@Override
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, STBTTFont font, String text, TextStyleOptions style)
{
var commandBuf = Objects.requireNonNullElseGet(this.commandBuffer, LiCommandBuffer::uncleared);
var atlas = font.getGlyphAtlas();
var atlasTexture = atlas.getGlyphAtlasTexture();
var texSwitch = new PlutoCommandSwitchTexture(atlasTexture);
commandBuf.push(texSwitch);
var shader = PlutoGUIMod.fontShader;
var shaderSwitch = new PlutoCommandSwitchShader(shader);
commandBuf.push(shaderSwitch);
commandBuf.push(new LiCommandSetPaint(style.getPaint()));
var cpCount = (int) text.codePoints().count();
var mesh = new PlutoCommandDrawMeshDirectBuffer();
final var quadVerts = 4;
final var twoTriVerts = 6;
var vertDim = 2;
var vertexBuf = MemoryUtil.memAllocFloat(vertDim * quadVerts * cpCount);
var uvDim = 2;
var uvBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var paintUVBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var pageDim = 1;
var pageBuf = MemoryUtil.memAllocInt(pageDim * quadVerts * cpCount);
var indexBuf = MemoryUtil.memAllocInt(twoTriVerts * cpCount);
var cpIt = text.codePoints().iterator();
var indices = new int[] {
0, 1, 2,
0, 2, 3
};
float[] vertices = new float[vertDim * quadVerts];
float[] uvs = new float[uvDim * quadVerts];
int[] pages = new int[pageDim * quadVerts];
float scale = font.getScale();
commandBuf.push(new LiCommandSpecial(cb -> PlutoGUIMod.fontShader.setPixelScale(style.getSize())));
float x = 0;
float y = 0;
GlyphInfo<?, ?> info;
STBTTFont.PlutoGlyphMetrics metrics = null;
int cp;
float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY, minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;
while (cpIt.hasNext())
{
cp = cpIt.next();
switch (cp)
{
case '\n' -> {
x = 0;
y += font.getLineAdvance() * scale;
continue;
}
}
if (metrics != null && features.contains(TextShaper.EnumFeature.KERNING))
x += metrics.getKerning(cp) * scale;
metrics = font.getGlyphMetrics(cp);
info = atlas.getGlyph(cp);
if (metrics == null)
continue;
if (info != null)
{
float gx = x + metrics.getXOrigin();
float gy = y + metrics.getYOrigin() + font.getAscent() * scale;
float xLow = gx - STBTTFont.SDF_PADDING;
float xHigh = gx + metrics.getCX1() * scale - metrics.getCX0() * scale + STBTTFont.SDF_PADDING;
float yLow = gy - STBTTFont.SDF_PADDING;
float yHigh = gy + metrics.getCY1() * scale - metrics.getCY0() * scale + STBTTFont.SDF_PADDING;
minX = Math.min(minX, xLow);
minY = Math.min(minY, yLow);
maxX = Math.max(maxX, xHigh);
maxY = Math.max(maxY, yHigh);
vertices[6] = vertices[0] = xLow;
vertices[3] = vertices[1] = yHigh;
vertices[4] = vertices[2] = xHigh;
vertices[7] = vertices[5] = yLow;
var uvRect = info.getRect();
uvs[6] = uvs[0] = uvRect.minX;
uvs[3] = uvs[1] = 1 - uvRect.maxY;
uvs[4] = uvs[2] = uvRect.maxX;
uvs[7] = uvs[5] = 1 - uvRect.minY;
Arrays.fill(pages, info.getPage());
vertexBuf.put(vertices);
uvBuf.put(uvs);
pageBuf.put(pages);
indexBuf.put(indices);
indices[0] += quadVerts;
indices[1] += quadVerts;
indices[2] += quadVerts;
indices[3] += quadVerts;
indices[4] += quadVerts;
indices[5] += quadVerts;
}
x += metrics.getAdvanceX() * scale;
}
vertexBuf.flip();
while (vertexBuf.hasRemaining())
{
paintUVBuf.put((vertexBuf.get() - minX) / (maxX - minX));
paintUVBuf.put((vertexBuf.get() - minY) / (maxY - minY));
}
mesh.addAttribute(shader.position, vertexBuf.rewind(), vertDim);
mesh.addAttribute(shader.uvCoords, uvBuf.flip(), uvDim);
mesh.addAttribute(shader.page, pageBuf.flip(), pageDim);
mesh.addAttribute(shader.paintUVCoords, paintUVBuf.flip(), uvDim);
mesh.addIndices(indexBuf.flip());
commandBuf.push(mesh);
return new LiTextInfo(commandBuf, new Rectanglef(minX, minY, maxX, maxY));
}
}

View File

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

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