Compare commits

..

115 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
Natty c675729996
Fixed several resource filesystem bugs 2022-04-06 23:55:49 +02:00
Natty 8313c08034
Convenience constructor for PartialTextureSprite 2022-04-06 22:19:06 +02:00
Natty efb6b3ba04
Fixed a typo 2022-04-06 21:49:37 +02:00
Natty 27b1cba881
Fixed mod load order 2022-04-06 21:47:48 +02:00
Natty 0f903820fa
Implemented FileChannel and symlink support 2022-04-06 21:21:27 +02:00
Natty b6decc8fd7
Fixed workflow name 2022-04-06 18:56:58 +02:00
Natty 49c0e88195
Added a build workflow for testing 2022-04-06 18:56:29 +02:00
Natty a930d9885f
Publishing of extensions via GitHub Actions 2022-04-06 18:52:36 +02:00
Natty 7f1cc3a7a3
Moved USS2, reimplemented RAID as GameObjectManager 2022-04-06 18:43:16 +02:00
Natty 0e5c5370b3
Fixed Javadoc 2022-04-06 15:29:18 +02:00
Natty 6cbd3133e7
getVersion in Mod.java, source publishing to Maven 2022-04-06 15:06:26 +02:00
Natty d0f250752e
Fixed a bug in a demo's build.gradle.kts 2022-04-06 14:41:12 +02:00
Natty 1dd7059b9c
Fixed Maven and version bump 2022-04-06 14:29:35 +02:00
Natty 5a0311ac74
Updated publish workflow 2022-04-06 14:06:14 +02:00
Natty 0b9f88256f
Fixed Gradle wrapper 2022-04-06 14:01:46 +02:00
Natty fea82975a0
Finalized UPDATE_NOTES.md 2022-04-06 13:56:27 +02:00
Natty e58e7f06ef Fixed backing path creation for the default filesystem and added a GitHub action 2022-04-06 11:04:32 +02:00
Natty e57cd71b0c Updated LWJGL 2022-04-06 11:04:32 +02:00
Natty 11105d435f Removed accidentally created files 2022-04-06 11:04:32 +02:00
Natty ab63e22438 Updated priorities 2022-04-06 11:04:32 +02:00
Natty ebd0ac434e SDK restructure, package reorganization, new ModLoader, new resource system 2022-04-06 11:04:32 +02:00
Natty d9a583ed8b SDK restructure, package reorganization, new ModLoader, new resource system 2022-04-06 11:04:32 +02:00
Tefek 4f141f0227 Updated copyright year 2022-04-06 11:04:32 +02:00
Tefek 5151a57f09 Updated plans and scheduled versions 2022-04-06 11:04:32 +02:00
Tefek 5e98b830ae [PlutoStatic] Added the GLFW virtual module 2022-04-06 11:04:32 +02:00
Tefek a2b4db86df [PlutoLib/PlutoStatic] Added AWT to Pluto color conversion, code cleanup and equals for ResourceAddress 2022-04-06 11:04:32 +02:00
Tefek a4d5d102f2 [PlutoUSS2/PlutoLib] Added PlutoUSS2 2022-04-06 11:04:32 +02:00
Tefek f61c47b294 [PlutoLib] Cleanup in MiniTime.java 2022-04-06 11:04:32 +02:00
Tefek af3b308adc [PlutoCore] Made PlutoApplication's constructor private 2022-04-06 11:04:32 +02:00
Tefek 798bd179bf [PlutoLib] Updated build.gradle 2022-04-06 11:04:32 +02:00
Tefek 632c792770 [PlutoLib] Added missing this keywords 2022-04-06 11:04:32 +02:00
Tefek e209da82c7 [PlutoLib] Cosmetic changes, removed EventData.java 2022-04-06 11:04:32 +02:00
Tefek 5bba5ceea2 [PlutoLib] The initial implementation of the Version API 2022-04-06 11:04:32 +02:00
493msi 262cba0000 [PlutoLib] Removed cz.tefek.pluto.event.static 2022-04-06 11:04:32 +02:00
Tefek cb613624a1 Put more emphasis on API removal in UPDATE_NOTES.md 2022-04-06 11:04:32 +02:00
Tefek 3bc8b5335c [PlutoLib/PlutoShader] The initial implementation of the color API 2022-04-06 11:04:32 +02:00
Tefek ffa84ff1f4 [PlutoLib] Color API plans 2022-04-06 11:04:32 +02:00
Tefek b1c27fbe8c [PlutoLib] Added the ConstantExpression annotation, added annotations for readability 2022-04-06 11:04:32 +02:00
Tefek ab5801d034 Changed version targeting and updated changes 2022-04-06 11:04:32 +02:00
Tefek 4da2da4482 [PlutoCore] Cut down extremely long builder lines 2022-04-06 11:04:32 +02:00
Tefek 037c532289 [PlutoLib#RAID] Fixed a major API oversight 2022-04-06 11:04:32 +02:00
Tefek 3bf0d18e88 [PlutoLib] Code cleanup 2022-04-06 11:04:32 +02:00
Tefek 5c5480d5c4 [PlutoLib] Dropped dead IO util classes 2022-04-06 11:04:32 +02:00
Tefek ffc44be94e Moved JOML to PlutoLib from PlutoStatic and updated dependencies 2022-04-06 11:04:32 +02:00
Tefek 89a2a300ba Bumped version number 2022-04-06 11:04:32 +02:00
Tefek 1974437c4e Fixed a bug where the instance was not properly set 2020-09-25 13:06:05 +02:00
Tefek c7c11d04b1 Deferred other changes to the next release 2020-09-25 13:03:34 +02:00
Tefek ce676681f9 Updated JavaDoc 2020-09-25 13:02:08 +02:00
Tefek 03f21b2d74 Removed some redundancies 2020-09-25 12:58:04 +02:00
Tefek 279d23206b Explained priorities 2020-09-25 12:53:10 +02:00
Tefek 3605254c5c Updated priorities 2020-09-25 12:36:59 +02:00
493msi b476087fcd Updated priorities 2020-09-24 03:07:15 +02:00
493msi 8fe75cf6cb Minor cleanup in Display and DisplayBuilder 2020-09-24 02:52:21 +02:00
493msi 93ef85f804 Added ThreadSensitive, InputBus cleanup 2020-09-24 02:34:33 +02:00
493msi 9c06c47ffc ModLoader requires a rewrite 2020-09-24 01:53:32 +02:00
493msi 825d87d022 Forced LF for Java files 2020-09-23 17:33:36 +02:00
493msi 2c738b88ac Updated package names in [PlutoLib] 2020-09-23 17:31:03 +02:00
493msi ee8683c037 Fixed a warning 2020-09-20 14:51:00 +02:00
493msi 51da238989 Version number 2020-09-20 03:07:36 +02:00
493msi 51e8cd2830 Updated README 2020-09-18 16:12:19 +02:00
493msi b42de03713 Removed a change from the changelist that didn't make it through 2020-09-18 16:06:28 +02:00
493msi 7128f5b055 [PlutoMesher] Renamed all occurrences of 'attrib' to 'attribute' 2020-09-18 15:42:04 +02:00
493msi d026a2b9e6 Added an explanation for deprecation 2020-09-18 14:51:27 +02:00
493msi 87cb3f54bd Maven source publishing 2020-09-18 14:50:51 +02:00
493msi 504e1884fa Added an experimental JDK wrapper 2020-09-17 20:52:35 +02:00
Tefek b341e7cfa3 Code cleanup 2020-09-17 15:37:05 +02:00
Tefek 1dd6ca2a24 Updated startup config to use setters 2020-09-11 14:57:04 +02:00
Tefek 817ef9e6f8 Updated plans for alpha 2 2020-09-10 00:46:19 +02:00
Tefek dd5992ab5e JavaDoc fixes 2020-09-08 23:32:54 +02:00
Tefek 2b065249ee TPL refactor, unified line endings, JavaDoc and full switch to SmartSeverity 2020-09-08 23:32:20 +02:00
Tefek 60fae86237 Logger refactor 2020-09-08 03:08:28 +02:00
Tefek ef714031dc Cleaned up the module versions 2020-09-04 16:21:11 +02:00
Tefek 274c052842 Moved plutocommandparser to a separate branch 2020-09-04 15:42:54 +02:00
Tefek 83f299bd9f Bumped the version and moved plutodb to a separate branch 2020-09-04 15:38:12 +02:00
Tefek 02034d25e9 Fixed a typo 2020-09-04 15:33:31 +02:00
Tefek 07aa60404a Fixed the submodule names 2020-09-04 15:31:05 +02:00
554 changed files with 25624 additions and 9812 deletions

2
.gitattributes vendored Normal file → Executable file
View File

@ -1,3 +1,5 @@
*.bat text eol=crlf
*.sh text eol=lf
*.java text eol=lf

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

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

36
.github/workflows/gradle-test-build.yml vendored Executable file
View File

@ -0,0 +1,36 @@
name: Gradle Test Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out the repository and all submodules
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Build
run: |
chmod +x ./gradlew
./gradlew build --stacktrace --info -x test

12
.gitignore vendored Normal file → Executable file
View File

@ -1,12 +1,17 @@
/*/.settings
/.vscode
# Ignore Eclipse project files
/*/.project
/*/.classpath
/.project
/.settings
# Ignore IDEA project files
/.idea
/*/.idea
.idea
*.log
@ -16,4 +21,9 @@
.gradle
# Ignore Gradle build output directory
build
/build
/*/build
/bin
/*/bin
/logs/

3
.gitmodules vendored Executable file
View File

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

2
LICENSE Normal file → Executable file
View File

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

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/

92
README.md Normal file → Executable file
View File

@ -1,7 +1,56 @@
# 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.
## 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
All submodules share a version number for simplicity reasons.
@ -21,37 +70,26 @@ version numbers.*
## 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
* **PlutoCore** - Stable
* **PlutoFrameBuffer** - Stable
* **PlutoGUI** - Stable, awaiting a rewrite
* **PlutoMesher** - Stable
* **PlutoShader** - Stable
* **PlutoSpritesheet** - Stable, some features are unfinished
* **PlutoStatic** - Stable, collision API nowhere near completition
* **PlutoCore** - Stable
* **PlutoSpritesheet** - Stable, some features are unfinished
* **PlutoDisplay** - Stable, collision API nowhere near completion
* **PlutoLib** - Mostly stable
* **PlutoRender** - Stable
* **PlutoRuntime** - Mostly stable
### Unstable submodules
* **PlutoAudio** - Somewhat usable, unfinished
* **PlutoLib** - Somewhat usable, requires further polish
* **PlutoAudio** - Very tentative, work in progress
* **PlutoGUI** - Recently rewritten, the API is highly unstable, work in progress
### Extensions
* **PlutoUSS2** - Stable
* **PlutoGameObject** - Stable
### Broken submodules, do NOT use
* **PlutoCommandParser** - Unfinished, broken, unusable
* **PlutoDB** - Broken, unusable
## Current priorities
### Very high priority
* Finishing PlutoAudio
* Further engine restructure
### High priority
* Rewriting PlutGUI
* Finishing PlutoCommandParser
* The stage system and automated asset loading
### Normal priority
* The collision system for PlutoStatic
### Low priority
* Polishing PlutoLib
* A networking API
See [issues](https://github.com/493msi/plutoengine/issues) for details.

191
UPDATE_NOTES.md Executable file
View File

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

View File

@ -1,55 +0,0 @@
import org.gradle.internal.os.OperatingSystem
wrapper {
distributionType = Wrapper.DistributionType.ALL
}
allprojects {
apply plugin: 'java'
group = "cz.tefek"
version = "20.2.0.0-alpha.0"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
subprojects {
apply plugin: 'maven-publish'
project.ext.lwjglVersion = "3.2.3"
project.ext.jomlVersion = "1.9.25"
project.ext.steamworks4jVersion = "1.8.0"
project.ext.steamworks4jServerVersion = "1.8.0"
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
}
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
repositories {
mavenCentral()
}
}

19
build.gradle.kts Executable file
View File

@ -0,0 +1,19 @@
import org.plutoengine.Versions
project.ext["isPlutoBuild"] = true;
tasks.withType<Wrapper> {
distributionType = Wrapper.DistributionType.ALL
gradleVersion = "7.4.2"
}
allprojects {
group = "org.plutoengine"
version = Versions.versionFull
}
subprojects {
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
}

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

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

View File

@ -0,0 +1,40 @@
package org.plutoengine
import org.gradle.api.JavaVersion
object Versions {
const val lwjglVersion = "3.3.1"
val lwjglNatives = listOf(
"natives-linux-arm64",
"natives-linux-arm32",
"natives-linux",
"natives-macos-arm64",
"natives-macos",
"natives-windows-arm64",
"natives-windows",
"natives-windows-x86"
)
const val jomlVersion = "1.10.2"
const val jomlPrimitivesVersion = "1.10.0"
const val steamworks4jVersion = "1.8.0"
const val steamworks4jServerVersion = "1.8.0"
const val versionYear = 22
const val versionMajor = 3
const val versionMinor = 0
const val versionPatch = 0
const val isPrerelease = true
const val prereleaseName = "alpha"
const val prerealeaseUpdate = 1
val versionFull =
if (isPrerelease)
"$versionYear.$versionMajor.$versionMinor.$versionPatch-$prereleaseName.$prerealeaseUpdate"
else
"$versionYear.$versionMajor.$versionMinor.$versionPatch"
val javaTargetVersion = JavaVersion.VERSION_17
}

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

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

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

@ -0,0 +1,70 @@
import org.plutoengine.Versions
task("publish") {
file(".").listFiles().forEach {
if (!it.isDirectory)
return@forEach
dependsOn(":plutoengine:${it.name}:publish")
}
}
subprojects {
apply(plugin = "java")
apply(plugin = "java-library")
apply(plugin = "maven-publish")
apply(plugin = "signing")
repositories {
mavenCentral()
}
configure<JavaPluginExtension> {
sourceCompatibility = Versions.javaTargetVersion
targetCompatibility = Versions.javaTargetVersion
withJavadocJar()
withSourcesJar()
}
configure<SourceSetContainer> {
named("main") {
tasks.withType<Jar> {
from(allJava)
}
}
}
configure<PublishingExtension> {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
repositories {
maven {
name = "Vega"
url = uri("https://vega.botdiril.com/releases")
credentials {
val vegaUsername: String? by project
val vegaPassword: String? by project
username = vegaUsername
password = vegaPassword
}
}
}
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
configure<SigningExtension> {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(the<PublishingExtension>().publications["maven"])
}
}

View File

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

View File

@ -0,0 +1,95 @@
/*
* 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.system.MemoryUtil;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.logger.Logger;
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
{
/**
* Loads an audio track denoted by this {@link Path} into memory
* for from-memory Vorbis decoding and streaming. Good for frequently used
* medium-sized audio files, however it is discouraged to use such a track
* in multiple audio sources at once due to the cost of seeking.
*/
public static SeekableTrack loadMemoryDecoded(Path path)
{
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
try
{
return new MemoryDecodedVorbisTrack(path);
}
catch (IOException e)
{
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", path);
e.printStackTrace();
return null;
}
}
/**
* Loads an audio track denoted by this {@link Path} into memory
* for from-memory PCM streaming. Good for frequently used small audio
* files.
*/
public static RandomAccessClip loadMemoryPCM(Path path)
{
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
try
{
return new MemoryPCMClip(path);
}
catch (IOException e)
{
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", path);
e.printStackTrace();
return null;
}
}
static ByteBuffer loadIntoMemory(Path path) throws IOException
{
var size = Files.size(path);
if (size > Integer.MAX_VALUE)
throw new IOException("File '%s' is too big to be loaded!".formatted(path));
var readData = MemoryUtil.memAlloc((int) size);
return BufferHelper.readToByteBuffer(path, readData);
}
}

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

@ -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

@ -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

@ -0,0 +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;
import org.apache.commons.lang3.tuple.Pair;
import org.joml.Vector3f;
import org.plutoengine.component.AbstractComponent;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent;
import java.util.ArrayList;
import java.util.List;
public class AudioEngine extends PlutoLocalComponent
{
public static final ComponentToken<AudioEngine> TOKEN = ComponentToken.create(AudioEngine::new);
private AudioContext context;
private final List<Pair<AudioClipSource, AudioSourceInfo>> sfx;
private AudioEngine()
{
this.sfx = new ArrayList<>();
}
@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(); )
{
var data = iterator.next();
var source = data.getKey();
var info = data.getValue();
var kaFunc = info.keepAliveFunction();
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();
}
}
public void playSound(SoundEffect sfx)
{
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);
}
@Override
protected void onUnmount()
{
}
@Override
public boolean isUnique()
{
return true;
}
public AudioContext getContext()
{
return this.context;
}
}

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.audio.al;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
import org.joml.Vector3fc;
import org.lwjgl.openal.AL10;
public abstract class AudioSource implements AutoCloseable
{
protected final int id;
protected Vector3fc position;
protected AudioSource()
{
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()
{
AL10.alSourceStop(this.id);
}
@MustBeInvokedByOverriders
public void close()
{
AL10.alDeleteSources(this.id);
}
@MustBeInvokedByOverriders
public void position(AudioContext context, Vector3fc pos)
{
this.position = pos;
var tPos = context.transform(pos);
AL10.alSource3f(this.id, AL10.AL_POSITION, tPos.x(), tPos.y(), tPos.z());
}
public Vector3fc getPosition()
{
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)
{
AL10.alSourcef(this.id, AL10.AL_PITCH, f);
}
@MustBeInvokedByOverriders
public void volume(float 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

@ -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

@ -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.audio.util;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class AudioUtil
{
public static void printInfo(long handle, STBVorbisInfo info)
{
Logger.log(SmartSeverity.AUDIO, "stream length, samples: " + STBVorbis.stb_vorbis_stream_length_in_samples(handle));
Logger.log(SmartSeverity.AUDIO, "stream length, seconds: " + STBVorbis.stb_vorbis_stream_length_in_seconds(handle));
Logger.log(SmartSeverity.AUDIO);
Logger.log(SmartSeverity.AUDIO, "channels = " + info.channels());
Logger.log(SmartSeverity.AUDIO, "sampleRate = " + info.sample_rate());
Logger.log(SmartSeverity.AUDIO, "maxFrameSize = " + info.max_frame_size());
Logger.log(SmartSeverity.AUDIO, "setupMemoryRequired = " + info.setup_memory_required());
Logger.log(SmartSeverity.AUDIO, "setupTempMemoryRequired() = " + info.setup_temp_memory_required());
Logger.log(SmartSeverity.AUDIO, "tempMemoryRequired = " + info.temp_memory_required());
}
}

View File

@ -0,0 +1,14 @@
plugins {
java
`java-library`
}
description = "A module acting as glue for all PlutoEngine components."
dependencies {
api("org.jetbrains", "annotations", "23.0.0")
implementation("org.apache.commons", "commons-lang3", "3.12.0")
implementation("org.apache.commons", "commons-collections4", "4.4")
}

View File

@ -0,0 +1,124 @@
/*
* 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;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractComponent<T extends AbstractComponent<? super T>>
{
private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id;
private ComponentDependencyManager manager;
protected AbstractComponent()
{
this.id = ID_SOURCE.getAndIncrement();
}
public long getID()
{
return this.id;
}
/**
* 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
{
}
void destroy(ComponentManager<? super T> manager) throws Exception
{
if (this.manager.dependencies != null)
{
this.manager.dependencies.forEach(manager::removeComponent);
this.manager.dependencies.clear();
}
this.onUnmount();
}
protected void onUnmount() throws Exception
{
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
AbstractComponent<?> that = (AbstractComponent<?>) o;
return this.id == that.id;
}
@Override
public int hashCode()
{
return Objects.hash(this.id);
}
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

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

View File

@ -0,0 +1,75 @@
/*
* 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;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
public final class ComponentToken<T extends AbstractComponent<? super T>>
{
private static final AtomicLong ID_SOURCE = new AtomicLong();
private final long id;
private final Supplier<T> supplier;
private ComponentToken(Supplier<T> valueSupplier)
{
this.id = ID_SOURCE.getAndIncrement();
this.supplier = valueSupplier;
}
public static <G extends AbstractComponent<? super G>> ComponentToken<G> create(@NotNull Supplier<G> valueSupplier)
{
return new ComponentToken<>(valueSupplier);
}
public T createInstance()
{
return this.supplier.get();
}
public long getID()
{
return this.id;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
ComponentToken<?> that = (ComponentToken<?>) o;
return this.id == that.id;
}
@Override
public int hashCode()
{
return Objects.hash(this.id);
}
}

View File

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

View File

@ -0,0 +1,334 @@
/*
* 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;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL33;
import org.plutoengine.audio.al.AudioEngine;
import org.plutoengine.buffer.GLFWImageUtil;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.display.Display;
import org.plutoengine.display.DisplayBuilder;
import org.plutoengine.input.InputBus;
import org.plutoengine.l10n.PlutoL10n;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import org.plutoengine.mod.ModLoader;
import org.plutoengine.util.color.IRGBA;
import org.plutoengine.util.color.RGBA;
import java.nio.file.Path;
import java.util.Locale;
/**
* The main entry point for OpenGL applications built around the Pluto framework.
*
* @author 493msi
*
* @since pre-alpha 20.1.0
* */
public abstract class PlutoApplication
{
protected Display display;
protected abstract Class<?> getMainModule();
protected void init()
{
}
protected abstract void loop();
protected PlutoApplication()
{
}
/**
* A set of values used to create a new {@link PlutoApplication}.
*
* <table>
* <caption>Configurable options</caption>
* <tr><th><b>Option name</b></th><th><b>Default value</b></th><th><b>Explanation</b></th></tr>
* <tr>
* <td><code>coreProfile</code></td>
* <td><code>true</code></td>
* <td>Whether the OpenGL core profile should be enforced.</td>
* </tr>
* <tr>
* <td><code>majorOpenGLVersion</code></td>
* <td><code>3</code></td>
* <td>The minimum major version of the OpenGL context</td>
* </tr>
* <tr>
* <td><code>minorOpenGLVersion</code></td>
* <td><code>3</code></td>
* <td>The minimum minor version of the OpenGL context</td>
* </tr>
* <tr>
* <td><code>windowName</code></td>
* <td><code>Pluto&nbsp;Engine</code></td>
* <td>The initial window title</td>
* </tr>
* <tr>
* <td><code>windowMSAA</code></td>
* <td><code>4</code></td>
* <td>The initial MSAA</td>
* </tr>
* <tr>
* <td><code>windowInitialWidth</code></td>
* <td><code>1280</code></td>
* <td>The initial window width</td>
* </tr>
* <tr>
* <td><code>windowInitialHeight</code></td>
* <td><code>720</code></td>
* <td>The initial window height</td>
* </tr>
* <tr>
* <td><code>windowMinWidth</code></td>
* <td><code>1000</code></td>
* <td>The minimum window width</td>
* </tr>
* <tr>
* <td><code>windowMinHeight</code></td>
* <td><code>1000</code></td>
* <td>The minimum window height</td>
* </tr>
* <tr>
* <td><code>vsync</code></td>
* <td><code>0</code></td>
* <td>The display buffer swap interval, 0 is disabled, 1 is vsync, 2 is vsync / 2, etc.</td>
* </tr>
* <tr>
* <td><code>windowResizable</code></td>
* <td><code>true</code></td>
* <td>Whether the window should be resizable</td>
* </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>
*
* @author 493msi
*
* @since 20.2.0.0-alpha.0
* */
protected static class StartupConfig
{
private boolean coreProfile = true;
private int majorOpenGLVersion = 3;
private int minorOpenGLVersion = 3;
private String windowName = "Pluto Engine";
private int windowMSAA = 4;
private int windowInitialWidth = 1280;
private int windowInitialHeight = 720;
private int windowMinWidth = 1000;
private int windowMinHeight = 600;
private int vsync = 0;
private boolean windowResizable = true;
private Path[] icons = null;
private IRGBA clearColor = new RGBA(0f, 0.7f, 1f, 0f);
public StartupConfig icons(Path... paths)
{
this.icons = paths;
return this;
}
public StartupConfig coreProfile(boolean coreProfile)
{
this.coreProfile = coreProfile;
return this;
}
public StartupConfig openGLVersion(int major, int minor)
{
this.majorOpenGLVersion = major;
this.minorOpenGLVersion = minor;
return this;
}
public StartupConfig windowName(String windowName)
{
this.windowName = windowName;
return this;
}
public StartupConfig windowMSAA(int msaa)
{
this.windowMSAA = msaa;
return this;
}
public StartupConfig windowInitialDimensions(int windowInitialWidth, int windowInitialHeight)
{
this.windowInitialWidth = windowInitialWidth;
this.windowInitialHeight = windowInitialHeight;
return this;
}
public StartupConfig windowMinDimensions(int windowMinWidth, int windowMinHeight)
{
this.windowMinWidth = windowMinWidth;
this.windowMinHeight = windowMinHeight;
return this;
}
public StartupConfig vsync(int vsyncInterval)
{
this.vsync = vsyncInterval;
return this;
}
public StartupConfig windowResizable(boolean windowResizable)
{
this.windowResizable = windowResizable;
return this;
}
public StartupConfig clearColor(IRGBA color)
{
this.clearColor = new RGBA(color.red(), color.green(), color.blue(), color.alpha());
return this;
}
public StartupConfig()
{
}
}
public final void run(String[] args, StartupConfig config)
{
if (config == null)
{
config = new StartupConfig();
}
var globalComponents = PlutoGlobal.COMPONENTS;
var logger = globalComponents.addComponent(Logger.TOKEN);
Logger.log(SmartSeverity.INFO, "Debug mode: " + (Pluto.DEBUG_MODE ? "enabled" : "disabled"));
PlutoL10n.init(Locale.UK);
var local = PlutoLocal.instance();
var components = local.COMPONENTS;
DisplayBuilder.initGLFW();
DisplayBuilder displayBuilder;
if (config.coreProfile)
{
displayBuilder = new DisplayBuilder()
.hintOpenGLVersion(config.majorOpenGLVersion, config.minorOpenGLVersion)
.hintDebugContext(Pluto.DEBUG_MODE)
.hintMSAA(config.windowMSAA)
.hintVisible(true)
.hintResizeable(config.windowResizable)
.setInitialSize(config.windowInitialWidth, config.windowInitialHeight);
}
else
{
displayBuilder = new DisplayBuilder()
.hintOpenGLVersionLegacy(config.majorOpenGLVersion, config.minorOpenGLVersion)
.hintDebugContext(Pluto.DEBUG_MODE)
.hintMSAA(config.windowMSAA)
.hintVisible(true)
.hintResizeable(config.windowResizable)
.setInitialSize(config.windowInitialWidth, config.windowInitialHeight);
}
var displayToken = ComponentToken.create(displayBuilder::export);
this.display = components.addComponent(displayToken);
this.display.setName(config.windowName);
this.display.create();
this.display.setWindowSizeLimits(config.windowMinWidth, config.windowMinHeight, GLFW.GLFW_DONT_CARE, GLFW.GLFW_DONT_CARE);
this.display.lockSwapInterval(config.vsync);
this.display.show();
this.display.createOpenGLCapabilities();
var inputBus = components.addComponent(InputBus.fromDisplay(this.display));
var audioEngine = components.addComponent(AudioEngine.TOKEN);
var modLoader = components.addComponent(ModLoader.with(this.getMainModule()));
if (config.icons != null)
{
var icons = GLFWImageUtil.loadIconSet(config.icons);
this.display.setIcons(icons);
}
this.init();
while (!this.display.isClosing())
{
GL33.glViewport(0, 0, this.display.getWidth(), this.display.getHeight());
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);
this.loop();
this.display.swapBuffers();
audioEngine.update();
inputBus.resetStates();
this.display.pollEvents();
}
components.removeComponent(audioEngine);
components.removeComponent(modLoader);
GL.destroy();
components.removeComponent(inputBus);
this.display.destroy();
components.removeComponents(displayToken);
DisplayBuilder.destroyGLFW();
globalComponents.removeComponent(logger);
}
public Display getDisplayInstance()
{
return this.display;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.display.Display;
public class InputBus extends PlutoLocalComponent
{
public static ComponentToken<InputBus> fromDisplay(Display display)
{
return ComponentToken.create(() -> new InputBus(display));
}
private final Display display;
private Keyboard keyboard;
private Mouse mouse;
private InputBus(Display display)
{
this.display = display;
}
@Override
protected void onMount(ComponentDependencyManager manager)
{
this.keyboard = manager.declareDependency(ComponentToken.create(() -> new Keyboard(this.display.getWindowPointer())));
this.mouse = manager.declareDependency(ComponentToken.create(() -> new Mouse(this.display.getWindowPointer())));
}
public void resetStates()
{
this.keyboard.resetStates();
this.mouse.resetStates();
}
@Override
public boolean isUnique()
{
return true;
}
}

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

@ -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

@ -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

@ -0,0 +1,82 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import static org.lwjgl.glfw.GLFW.GLFW_PRESS;
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
public class KeyboardInputCallback extends GLFWKeyCallback
{
private final Set<Integer> keyPressed = new HashSet<>();
private final Set<Integer> keyDown = new HashSet<>();
private final Set<Integer> keyReleased = new HashSet<>();
@Override
public void invoke(long window, int key, int scancode, int action, int mods)
{
if (key < 0)
{
return;
}
if (action == GLFW_PRESS)
{
this.keyDown.add(key);
this.keyPressed.add(key);
}
if (action == GLFW_RELEASE)
{
this.keyDown.remove(key);
this.keyReleased.add(key);
}
}
public void resetPressed()
{
this.keyPressed.clear();
this.keyReleased.clear();
}
public boolean hasBeenPressed(int key)
{
return this.keyPressed.contains(key);
}
public boolean hasBeenReleased(int key)
{
return this.keyReleased.contains(key);
}
public boolean isKeyDown(int key)
{
return this.keyDown.contains(key);
}
}

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

@ -0,0 +1,27 @@
import org.plutoengine.Versions
plugins {
java
`java-library`
}
description = ""
dependencies {
api(project(":plutoengine:plutoruntime"))
api("org.lwjgl", "lwjgl")
api("org.lwjgl", "lwjgl-glfw")
api("org.lwjgl", "lwjgl-opengl")
api("org.lwjgl", "lwjgl-stb")
org.plutoengine.Versions.lwjglNatives.forEach {
runtimeOnly("org.lwjgl", "lwjgl", classifier = it)
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-server", Versions.steamworks4jServerVersion)
}

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;
import org.lwjgl.glfw.GLFW;
import org.plutoengine.mod.ModEntry;
@ModEntry(modID = "glfw", version = ModGLFW.VERSION, dependencies = ModLWJGL.class)
public class ModGLFW
{
public static final String VERSION = GLFW.GLFW_VERSION_MAJOR + "." + GLFW.GLFW_VERSION_MINOR + "." + GLFW.GLFW_VERSION_REVISION;
}

View File

@ -0,0 +1,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;
import org.lwjgl.Version;
import org.plutoengine.mod.ModEntry;
@ModEntry(modID = "lwjgl",
version = ModLWJGL.version)
public class ModLWJGL
{
public static final String version = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION;
}

View File

@ -1,6 +1,29 @@
package cz.tefek.pluto.engine.buffer;
/*
* 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;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import java.io.IOException;
@ -11,8 +34,6 @@ import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
/**
* A utility class to handle primitive native buffers.
*
@ -31,7 +52,7 @@ public final class BufferHelper
* @author 493msi
* @since 0.1
*/
public static IntBuffer flippedIntBuffer(int[] data)
public static IntBuffer intBuffer(int[] data)
{
IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
buffer.put(data);
@ -49,7 +70,7 @@ public final class BufferHelper
* @author 493msi
* @since 0.1
*/
public static FloatBuffer flippedFloatBuffer(float[] data)
public static FloatBuffer floatBuffer(float[] data)
{
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
buffer.put(data);
@ -67,7 +88,7 @@ public final class BufferHelper
* @author 493msi
* @since 0.1
*/
public static ByteBuffer flippedByteBuffer(byte[] data)
public static ByteBuffer byteBuffer(byte[] data)
{
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
buffer.put(data);
@ -101,35 +122,6 @@ public final class BufferHelper
return newBuffer;
}
/**
* Loads a file denoted by the specified path and returns a
* {@link ByteBuffer} containing the read bytes.
*
* @param path The file's path.
* @return A {@link ByteBuffer} containing the file's contents.
* @throws IOException Upon standard I/O errors.
*
* @author 493msi
* @since 0.1
*/
public static ByteBuffer readToFlippedByteBuffer(String path) throws IOException
{
try (var fc = FileChannel.open(Path.of(path)))
{
var size = fc.size();
if (size > Integer.MAX_VALUE)
{
throw new IOException("File ' + pah + ' is too big to be read into a ByteBuffer!");
}
ByteBuffer buf = BufferUtils.createByteBuffer((int) size);
fc.read(buf);
buf.flip();
return buf;
}
}
/**
* Loads a file denoted by the specified {@link Path} and fills the input
* {@link ByteBuffer} with the read bytes.
@ -158,25 +150,7 @@ public final class BufferHelper
}
/**
* {@link ResourceAddress} version of
* {@link BufferHelper#readToByteBuffer(Path path, ByteBuffer buf)}.
*
* @param addr The file's {@link ResourceAddress}.
* @param buf The input buffer to be filled with data.
*
* @return The input {@link ByteBuffer}.
* @throws IOException Upon standard I/O errors.
*
* @author 493msi
* @since 0.3
*/
public static ByteBuffer readToByteBuffer(ResourceAddress addr, ByteBuffer buf) throws IOException
{
return readToByteBuffer(addr.toNIOPath(), buf);
}
/**
* Loads a file denoted by the specified {@link ResourceAddress} and returns
* Loads a file denoted by the specified {@link Path} and returns
* a {@link ByteBuffer} containing the read bytes.
*
* @param path The file's path.
@ -186,12 +160,8 @@ public final class BufferHelper
* @author 493msi
* @since 0.3
*/
public static ByteBuffer readToFlippedByteBuffer(ResourceAddress path) throws IOException
public static ByteBuffer readToByteBuffer(Path path) throws IOException
{
try (var is = Files.newInputStream(path.toNIOPath()))
{
var ba = IOUtils.toByteArray(is);
return flippedByteBuffer(ba);
}
return byteBuffer(Files.readAllBytes(path));
}
}

View File

@ -0,0 +1,90 @@
/*
* 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;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFWImage;
import org.plutoengine.tpl.ImageLoader;
import java.nio.file.Path;
/**
* A utility class to load image files for use in GLFW.
*
* @author 493msi
* @since 0.2
*
*/
public class GLFWImageUtil
{
/**
* Loads a set of image files for use in GLFW. Loads a placeholder and prints an
* error message on missing files.
*
* @param icons An array of path strings
* @return The resulting buffer struct containing loaded icons or placeholders.
*
* @author 493msi
* @since 0.2
*/
public static GLFWImage.Buffer loadIconSet(Path... icons)
{
var icon = GLFWImage.create(icons.length);
for (var iconPath : icons)
{
var img = ImageLoader.loadSpecial(iconPath, false);
var imgData = img.getData();
int imgWidth = img.getWidth();
int imgHeight = img.getHeight();
int pixelCount = imgWidth * imgHeight;
int bytesPerPixel = 4;
var byteBuf = BufferUtils.createByteBuffer(pixelCount * bytesPerPixel);
byte[] px = new byte[bytesPerPixel];
for (int i = 0; i < pixelCount; i++)
{
px[3] = imgData.get(); // A
px[2] = imgData.get(); // B
px[1] = imgData.get(); // G
px[0] = imgData.get(); // R
byteBuf.put(px);
}
byteBuf.flip();
var glfwImg = GLFWImage.create();
glfwImg.set(imgWidth, imgHeight, byteBuf);
icon.put(glfwImg);
}
icon.flip();
return icon;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.
*
* @author 493msi
*
*/
package org.plutoengine.buffer;

View File

@ -0,0 +1,253 @@
/*
* 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;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.Pluto;
import org.plutoengine.annotation.ThreadSensitive;
import org.plutoengine.component.PlutoLocalComponent;
import org.plutoengine.gl.GLDebugInfo;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
/**
* A wrapper class to provide abstraction over GLFW windows.
*
* @author 493msi
* @since 0.2
*/
@ThreadSensitive(localContexts = true)
public class Display extends PlutoLocalComponent
{
int width;
int height;
boolean debugMode;
boolean coreProfile = true;
private String name = Pluto.ENGINE_NAME;
private boolean wasResized;
private boolean openGLContext;
private long windowPointer;
private final GLFWErrorCallbackI glfwErrorCallback;
private GLFWWindowSizeCallbackI resizeCallback;
private GLDebugMessageARBCallbackI glDebugCallback;
Display()
{
this.windowPointer = MemoryUtil.NULL;
this.glfwErrorCallback = (int error, long description) -> {
Logger.logf(SmartSeverity.ERROR, "GLFW Error code %d:\n", error);
Logger.logf(GLFWErrorCallback.getDescription(description));
};
GLFW.glfwSetErrorCallback(this.glfwErrorCallback);
}
public void create()
{
GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, 4);
this.windowPointer = GLFW.glfwCreateWindow(this.width, this.height, this.name, MemoryUtil.NULL, MemoryUtil.NULL);
if (this.windowPointer == MemoryUtil.NULL)
{
this.destroy();
throw new IllegalStateException("Failed to create a window...");
}
var vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
if (vidmode == null)
{
this.destroy();
throw new IllegalStateException("Failed to detect the primary monitor.");
}
GLFW.glfwSetWindowPos(this.windowPointer, (vidmode.width() - this.width) / 2, (vidmode.height() - this.height) / 2);
GLFW.glfwMakeContextCurrent(this.windowPointer);
this.resizeCallback = (long window, int width, int height) -> {
if (width > 0 && height > 0)
{
if (this.debugMode)
{
Logger.logf(SmartSeverity.INFO, "Resized to %dx%d.\n", width, height);
}
this.width = width;
this.height = height;
this.wasResized = true;
if (this.openGLContext)
{
GL33.glViewport(0, 0, this.width, this.height);
}
}
};
GLFW.glfwSetWindowSizeCallback(this.windowPointer, this.resizeCallback);
}
public void setName(String newName)
{
this.name = newName;
if (this.windowPointer != MemoryUtil.NULL)
GLFW.glfwSetWindowTitle(this.windowPointer, this.name);
}
public void show()
{
GLFW.glfwShowWindow(this.windowPointer);
}
public void setWindowSizeLimits(int minWidth, int minHeight, int maxWidth, int maxHeight)
{
GLFW.glfwSetWindowSizeLimits(this.windowPointer, minWidth, minHeight, maxWidth, maxHeight);
}
public void setIcons(GLFWImage.Buffer iconBuffer)
{
GLFW.glfwSetWindowIcon(this.windowPointer, iconBuffer);
iconBuffer.flip();
iconBuffer.forEach(GLFWImage::free);
iconBuffer.free();
}
public void setShouldClose(boolean shouldClose)
{
GLFW.glfwSetWindowShouldClose(this.windowPointer, shouldClose);
}
public void maximize()
{
GLFW.glfwMaximizeWindow(this.windowPointer);
}
public void lockSwapInterval(int interval)
{
GLFW.glfwSwapInterval(interval);
}
public void swapBuffers()
{
GLFW.glfwSwapBuffers(this.windowPointer);
Framerate.tick();
}
public boolean isClosing()
{
return GLFW.glfwWindowShouldClose(this.windowPointer);
}
public void pollEvents()
{
this.wasResized = false;
GLFW.glfwPollEvents();
}
public void destroy()
{
if (this.glfwErrorCallback instanceof GLFWErrorCallback glfwErrorCallback)
{
glfwErrorCallback.free();
}
if (this.glDebugCallback instanceof GLDebugMessageARBCallback glDebugMessageARBCallback)
{
glDebugMessageARBCallback.free();
}
if (this.resizeCallback instanceof GLFWWindowSizeCallback windowSizeCallback)
{
windowSizeCallback.free();
}
if (this.windowPointer != MemoryUtil.NULL)
{
GLFW.glfwDestroyWindow(this.windowPointer);
}
}
public int getHeight()
{
return this.height;
}
public int getWidth()
{
return this.width;
}
public long getWindowPointer()
{
return this.windowPointer;
}
public boolean wasResized()
{
return this.wasResized;
}
public void createOpenGLCapabilities()
{
var glCapabilities = GL.createCapabilities(this.coreProfile);
GLDebugInfo.printDebugInfo(glCapabilities);
if (this.debugMode)
{
this.glDebugCallback = (int source, int type, int id, int severity, int length, long messagePtr, long userParam) -> {
var message = GLDebugMessageARBCallback.getMessage(length, messagePtr);
Logger.log(SmartSeverity.WARNING, message);
};
ARBDebugOutput.glDebugMessageCallbackARB(this.glDebugCallback, MemoryUtil.NULL);
}
GL33.glEnable(GL33.GL_CULL_FACE);
GL33.glCullFace(GL33.GL_BACK);
this.openGLContext = true;
}
@Override
public boolean isUnique()
{
return true;
}
}

View File

@ -1,10 +1,34 @@
package cz.tefek.pluto.engine.display;
/*
* 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;
import org.lwjgl.glfw.GLFW;
public class DisplayBuilder
{
private Display display;
private final Display display;
public DisplayBuilder()
{
@ -81,10 +105,8 @@ public class DisplayBuilder
public static void initGLFW()
{
if (!GLFW.glfwInit())
{
throw new IllegalStateException("Could not init GLFW!");
}
}
public static void destroyGLFW()
{

View File

@ -0,0 +1,109 @@
/*
* 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;
import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
/**
* @author 493msi
*
*/
public class Framerate
{
private static long lastDraw = 0;
private static double frameTime = Double.NaN;
private static double animationTimer = 0;
private static double fps = Double.NaN;
private static int interpolatedFPS;
private static boolean firstRemoved = false;
private static final Deque<Long> drawTimestamps = new LinkedBlockingDeque<>();
public static double getFrameTime()
{
return frameTime;
}
public static double getFPS()
{
return fps;
}
public static int getInterpolatedFPS()
{
return interpolatedFPS;
}
public static float getAnimationTimer()
{
return (float) animationTimer;
}
public static void tick()
{
var now = System.nanoTime();
if (lastDraw > 0)
{
var frameTimeNs = now - lastDraw;
frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1);
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();
drawTimestamps.add(nowMs);
Long oldestDraw;
long oneSecondAgo = nowMs - 1000;
while ((oldestDraw = drawTimestamps.peek()) != null && oldestDraw < oneSecondAgo)
{
drawTimestamps.remove();
firstRemoved = true;
}
if (firstRemoved)
{
interpolatedFPS = drawTimestamps.size();
}
else
{
interpolatedFPS = (int) Math.round(fps);
}
lastDraw = now;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.
*
* @author 493msi
*
*/
package org.plutoengine.display;

View File

@ -1,12 +1,36 @@
package cz.tefek.pluto.engine.gl;
/*
* 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;
import org.lwjgl.opengl.ARBFramebufferObject;
import org.lwjgl.opengl.ARBUniformBufferObject;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLCapabilities;
import cz.tefek.pluto.io.logger.Logger;
import cz.tefek.pluto.io.logger.SmartSeverity;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
public class GLDebugInfo
{

View File

@ -1,4 +1,28 @@
package cz.tefek.pluto.engine.math;
/*
* 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;
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.
*
* @param width The ortho width

View File

@ -1,4 +1,28 @@
package cz.tefek.pluto.engine.math;
/*
* 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;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
@ -18,7 +42,7 @@ public class TransformationMatrix
* </p>
*
* @param translation The translation vector
* @param rotation The rotation angles
* @param yxzRotation The rotation angles
* @param scale The scale vector
*
* @author 493msi
@ -48,7 +72,7 @@ public class TransformationMatrix
* @param roll The roll rotation
* @param scaleX The scale on the X axis
* @param scaleY The scale on the Y axis
* @param scaleY The scale on the Z axis
* @param scaleZ The scale on the Z axis
*
* @author 493msi
* @since 0.3

View File

@ -0,0 +1,57 @@
/*
* 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;
import org.joml.Matrix3x2f;
/**
* A class with factory methods for the most used view transformations.
*
* @author 493msi
* @since 0.3
*/
public class ViewMatrix
{
/**
* Create a 2D view matrix.
*
* @param x The X camera translation
* @param y The Y camera translation
* @param zoom The zoom
*
* @return the view matrix
*
* @author 493msi
* @since 0.3
*/
public static Matrix3x2f createView(int x, int y, float zoom)
{
var view = new Matrix3x2f();
view.scale(zoom);
view.translate(x, y);
return view;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.
*
* @author 493msi
*
*/
package org.plutoengine.math;

View File

@ -0,0 +1,22 @@
plugins {
java
`java-library`
}
description = ""
dependencies {
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

@ -0,0 +1,76 @@
/*
* 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.plutoengine.Pluto;
import org.plutoengine.graphics.gui.BitmapFontShader;
import org.plutoengine.graphics.gui.FontShader;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.mod.IModEntryPoint;
import org.plutoengine.mod.Mod;
import org.plutoengine.mod.ModEntry;
import org.plutoengine.graphics.shader.RenderShaderBuilder;
/**
* @author 493msi
*
*/
@ModEntry(modID = PlutoGUIMod.MOD_ID,
dependencies = { PlutoSpriteSheetMod.class },
version = Pluto.VERSION)
public class PlutoGUIMod implements IModEntryPoint
{
public static final String MOD_ID = "tefek.plutogui";
public static Mod instance;
public static RectangleTexture uiElementsAtlas;
public static FontShader fontShader;
public static BitmapFontShader bitmapFontShader;
public void onLoad(Mod mod)
{
instance = mod;
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
bitmapFontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexBitmapFontShader#glsl"), mod.getResource("shaders.FragmentBitmapFontShader#glsl")).build(BitmapFontShader.class, false);
uiElementsAtlas = new RectangleTexture();
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}
public void onUnload()
{
uiElementsAtlas.close();
fontShader.close();
}
}

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

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

View File

@ -0,0 +1,25 @@
module org.plutoengine.plutolib {
requires java.base;
requires java.desktop;
requires transitive org.joml;
requires transitive org.joml.primitives;
requires transitive com.fasterxml.jackson.core;
requires transitive com.fasterxml.jackson.databind;
requires transitive com.fasterxml.jackson.annotation;
requires transitive org.jetbrains.annotations;
requires org.yaml.snakeyaml;
requires org.apache.commons.lang3;
opens org.plutoengine.address;
exports org.plutoengine.address;
exports org.plutoengine.util.color;
exports org.plutoengine.io.property;
exports org.plutoengine.math;
exports org.plutoengine.event.lambda;
exports org.plutoengine.chrono;
}

View File

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

View File

@ -0,0 +1,33 @@
/*
* 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.address;
public class VirtualAddressParseException extends RuntimeException
{
public VirtualAddressParseException(String message)
{
super(message);
}
}

View File

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

View File

@ -1,4 +1,28 @@
package cz.tefek.pluto.chrono;
/*
* 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.chrono;
import java.util.concurrent.TimeUnit;
@ -7,7 +31,7 @@ import java.util.concurrent.TimeUnit;
* time span {@link String} format and vice versa. Note this action is fully
* reversible at the cost of losing millisecond precision.
*
* <h3>MiniTime format specification:</h3>
* <h2>MiniTime format specification:</h2>
*
* <pre>
* [Nw][Nd][Nh][Nm][Ns]
@ -43,20 +67,9 @@ import java.util.concurrent.TimeUnit;
*/
public class MiniTime
{
private static class MiniTimeCouldNotBeParsedException extends RuntimeException
private static class MiniTimeParseException extends RuntimeException
{
/**
*
*/
private static final long serialVersionUID = -5403949842120041373L;
public MiniTimeCouldNotBeParsedException()
{
super("Time period could not be parsed. Correct format: \\_w\\_d\\_h\\_m\\_s **without spaces** between the units. You can skip a time unit. Example: 1h15m");
}
}
private static final TimeUnit miliseconds = TimeUnit.MILLISECONDS;
private static final int DAYS_IN_WEEK = 7;
private static final int HOURS_IN_DAY = 24;
@ -71,20 +84,20 @@ public class MiniTime
* @param input The source MiniTime non-null string
* @return The resulting time span in milliseconds
*
* @throws MiniTimeParseException on malformed inputs
*
* @author 493msi
* @since 0.2
*/
public static long parse(String input)
{
if (input == null)
{
throw new IllegalArgumentException("MiniTime string cannot be null!");
}
throw new NullPointerException();
// Nothing to parse
if (input.isEmpty())
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
if (input.equalsIgnoreCase("forever"))
@ -95,25 +108,25 @@ public class MiniTime
// Follow the scheme
if (!input.matches("[0-9]*[wW]?[0-9]*[dD]?[0-9]*[hH]?[0-9]*[mM]?[0-9]*[sS]?"))
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
// 4584 of what? Potatoes?
if (input.matches("[0-9]+"))
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
// Where are the numbers?
if (input.matches("[a-zA-Z]+"))
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
// It shouldn't start with a letter
if (input.matches("^[a-zA-Z].+"))
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
var nrs = input.split("[a-zA-Z]");
@ -121,7 +134,7 @@ public class MiniTime
if (nrs.length != letters.length)
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
long time = 0;
@ -129,7 +142,7 @@ public class MiniTime
for (int i = 1; i < nrs.length; i++)
{
var type = letters[i - 1];
int number = 0;
int number;
try
{
@ -138,7 +151,7 @@ public class MiniTime
}
catch (NumberFormatException nfe)
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
var allow = 0L;
@ -146,33 +159,28 @@ public class MiniTime
switch (type.toLowerCase())
{
case "w":
case "W":
case "w" -> {
allow = Integer.MAX_VALUE;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_WEEK * MILLIS_IN_MINUTE;
break;
case "d":
case "D":
}
case "d" -> {
allow = DAYS_IN_WEEK;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * MILLIS_IN_MINUTE;
break;
case "h":
case "H":
}
case "h" -> {
allow = HOURS_IN_DAY;
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * MILLIS_IN_MINUTE;
break;
case "m":
case "M":
}
case "m" -> {
allow = MINUTES_IN_HOUR;
multiplier = SECONDS_IN_MINUTE * MILLIS_IN_MINUTE;
break;
case "s":
case "S":
}
case "s" -> {
allow = SECONDS_IN_MINUTE;
multiplier = MILLIS_IN_MINUTE;
break;
default:
break;
}
default -> {
}
}
// The top one can be more than it normally could have, for example you can
@ -184,10 +192,10 @@ public class MiniTime
if (number > allow)
{
throw new MiniTimeCouldNotBeParsedException();
throw new MiniTimeParseException();
}
time += multiplier * number;
time += (long) multiplier * number;
}
return System.currentTimeMillis() + time;
@ -196,10 +204,12 @@ public class MiniTime
/**
* Converts a time span between two Unix-epoch millisecond time points to a
* MiniTime string. <i>Note ALL time spans larger or equal than
* {@link Integer#MAX_VALUE} will be permanently converted to "forever".</i>
* {@link Integer#MAX_VALUE} weeks will be permanently converted to "forever".
* Inputting {@link Long#MAX_VALUE} for the future time point has the same effect.</i>
*
* @param before The first time point in Unix-time milliseconds
* @param after The first time point in Unix-time milliseconds
*
* @return The resulting MiniTime string
*
* @throws IllegalArgumentException on a negative time duration
@ -220,10 +230,12 @@ public class MiniTime
/**
* Converts a time span between now and a future time point in Unix-epoch
* milliseconds to a MiniTime string. <i>Note ALL time spans larger or equal
* than {@link Integer#MAX_VALUE} will be permanently converted to
* "forever".</i>
* than {@link Integer#MAX_VALUE} weeks will be permanently converted to
* "forever". Inputting {@link Long#MAX_VALUE} for the future time point
* has the same effect.</i>
*
* @param future The future time point in Unix time milliseconds
*
* @param input The source time span in milliseconds
* @return The resulting MiniTime string
*
* @throws IllegalArgumentException on a negative time duration
@ -245,7 +257,7 @@ public class MiniTime
/**
* Converts a time span milliseconds to a MiniTime string. <i>Note ALL time
* spans larger or equal than {@link Integer#MAX_VALUE} will be permanently
* spans larger or equal than {@link Integer#MAX_VALUE} weeks will be permanently
* converted to "forever".</i>
*
* @param diff The source time span in milliseconds
@ -263,17 +275,17 @@ public class MiniTime
throw new IllegalArgumentException("Negative time span cannot be converted to MiniTime.");
}
var xweeks = miliseconds.toDays(diff) / DAYS_IN_WEEK;
var xweeks = TimeUnit.MILLISECONDS.toDays(diff) / DAYS_IN_WEEK;
if (xweeks > Integer.MAX_VALUE)
{
return "forever";
}
var xdays = miliseconds.toDays(diff) % DAYS_IN_WEEK;
var xhours = miliseconds.toHours(diff) % HOURS_IN_DAY;
var xminutes = miliseconds.toMinutes(diff) % MINUTES_IN_HOUR;
var xseconds = miliseconds.toSeconds(diff) % SECONDS_IN_MINUTE;
var xdays = TimeUnit.MILLISECONDS.toDays(diff) % DAYS_IN_WEEK;
var xhours = TimeUnit.MILLISECONDS.toHours(diff) % HOURS_IN_DAY;
var xminutes = TimeUnit.MILLISECONDS.toMinutes(diff) % MINUTES_IN_HOUR;
var xseconds = TimeUnit.MILLISECONDS.toSeconds(diff) % SECONDS_IN_MINUTE;
return formatTime(xweeks, xdays, xhours, xminutes, xseconds);
}

View File

@ -0,0 +1,31 @@
/*
* 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 time manipulation and conversion.
*
* @author 493msi
*
*/
package org.plutoengine.chrono;

View File

@ -1,8 +1,32 @@
package cz.tefek.pluto.eventsystem.lambda;
/*
* 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.event.lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* A simple functional interface based event factory for objects basically
@ -25,7 +49,7 @@ public class LambdaEventFactory
*/
public static class LambdaEvent<T>
{
private List<Consumer<T>> consumers;
private final List<Predicate<T>> consumers;
private LambdaEvent()
{
@ -42,7 +66,7 @@ public class LambdaEventFactory
*
* @author 493msi
*/
public void addListener(Consumer<T> callback)
public void addListener(Predicate<T> callback)
{
this.consumers.add(callback);
}
@ -57,7 +81,7 @@ public class LambdaEventFactory
*
* @author 493msi
*/
public void removeListener(Consumer<T> callback)
public void removeListener(Predicate<T> callback)
{
this.consumers.remove(callback);
}
@ -74,7 +98,7 @@ public class LambdaEventFactory
*/
public void fire(T value)
{
this.consumers.forEach(c -> c.accept(value));
this.consumers.removeIf(c -> !c.test(value));
}
}

View File

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

View File

@ -0,0 +1,74 @@
/*
* 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;
import org.joml.Math;
public class BasicInterpolation
{
public static int roundedLerp(float value, int min, int max)
{
return min + Math.round(value * (max - min));
}
public static int roundedLerpWrap(float value, int min, int max)
{
var range = max - min;
return min + (roundedLerp(value, 0, range) % range + range) % range;
}
public static int roundedLerp(double value, int min, int max)
{
return (int) (min + Math.round(value * (max - min)));
}
public static int roundedLerpWrap(double value, int min, int max)
{
var range = max - min;
return min + (roundedLerp(value, 0, range) % range + range) % range;
}
public static int floorLerp(float value, int min, int max)
{
return (int) (min + Math.floor(value * (max - min)));
}
public static int floorLerpWrap(float value, int min, int max)
{
var range = max - min;
return min + (floorLerp(value, 0, range) % range + range) % range;
}
public static int floorLerp(double value, int min, int max)
{
return (int) (min + Math.floor(value * (max - min)));
}
public static int floorLerpWrap(double value, int min, int max)
{
var range = max - min;
return min + (floorLerp(value, 0, range) % range + range) % range;
}
}

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