From 5ffbee26f7b306c3998901595cb31d3aea4e8b62 Mon Sep 17 00:00:00 2001 From: Tefek <493msi@gmail.com> Date: Tue, 18 Aug 2020 17:36:08 +0200 Subject: [PATCH] Initial unified commit --- .gitignore | 5 + LICENSE | 21 ++ plutocommandparser/pom.xml | 22 ++ .../cz/tefek/pluto/command/CommandBase.java | 18 + .../context/CommandContextBuilder.java | 96 ++++++ .../pluto/command/parser/CommandParser.java | 252 ++++++++++++++ .../pluto/command/parser/EnumParserState.java | 17 + .../command/platform/CommandPlatform.java | 30 ++ .../command/registry/CommandRegistry.java | 69 ++++ .../resolver/AbstractResolveException.java | 24 ++ .../command/resolver/AbstractResolver.java | 6 + .../command/resolver/EnumResolveFailure.java | 13 + .../command/resolver/GenericResolver.java | 21 ++ .../command/resolver/ResolveException.java | 22 ++ .../primitive/BasicDoubleResolver.java | 23 ++ .../resolver/primitive/BasicIntResolver.java | 22 ++ .../resolver/primitive/BasicLongResolver.java | 23 ++ .../resolver/primitive/DoubleResolver.java | 26 ++ .../primitive/IntFractionResolver.java | 63 ++++ .../resolver/primitive/IntResolver.java | 26 ++ .../resolver/primitive/LongResolver.java | 26 ++ .../resolver/BasicIntResolverTest.java | 41 +++ plutodb/.gitignore | 1 + plutodb/pom.xml | 96 ++++++ .../cz/tefek/plutodb/ILMDBValueRecipe.java | 37 ++ .../java/cz/tefek/plutodb/LMDBDatabase.java | 78 +++++ .../cz/tefek/plutodb/LMDBEnvironment.java | 130 +++++++ .../java/cz/tefek/plutodb/LMDBIntegerKey.java | 29 ++ .../main/java/cz/tefek/plutodb/LMDBKey.java | 9 + .../java/cz/tefek/plutodb/LMDBLongKey.java | 29 ++ .../java/cz/tefek/plutodb/LMDBSchema.java | 23 ++ .../java/cz/tefek/plutodb/LMDBStringKey.java | 27 ++ .../cz/tefek/plutodb/LMDBTransaction.java | 108 ++++++ .../java/cz/tefek/test/plutodb/LMDBTest.java | 43 +++ .../java/cz/tefek/test/plutodb/UserData.java | 44 +++ plutoframebuffer/pom.xml | 24 ++ .../engine/graphics/gl/fbo/Framebuffer.java | 84 +++++ .../gl/fbo/FramebufferDepthTexture.java | 21 ++ .../graphics/gl/fbo/FramebufferTexture.java | 34 ++ plutogui/pom.xml | 37 ++ .../pluto/engine/graphics/PlutoGUIMod.java | 67 ++++ .../engine/graphics/font/FontManager.java | 102 ++++++ .../engine/graphics/font/FontShader.java | 41 +++ .../tefek/pluto/engine/gui/IGUIPipeline.java | 6 + .../tefek/pluto/engine/gui/IGUIRenderer.java | 6 + .../pluto/engine/gui/font/CharacterInfo.java | 30 ++ .../cz/tefek/pluto/engine/gui/font/Font.java | 59 ++++ .../pluto/engine/gui/font/FontHelper.java | 121 +++++++ .../pluto/engine/gui/font/FontRenderer.java | 311 +++++++++++++++++ .../pluto/engine/gui/font/FontRenderer2.java | 106 ++++++ .../engine/gui/pipeline/BasicGUIPipeline.java | 12 + .../gui/pipeline/EnumGUIPipelineCommand.java | 12 + plutoio2/pom.xml | 99 ++++++ .../cz/tefek/io/asl/resource/Resource.java | 33 ++ .../io/asl/resource/ResourceAddress.java | 317 ++++++++++++++++++ .../resource/ResourceAddressTypeAdapter.java | 24 ++ .../tefek/io/asl/resource/ResourceHelper.java | 12 + .../io/asl/resource/ResourceSubscriber.java | 47 +++ .../io/asl/resource/raid/IIdentifiable.java | 17 + .../cz/tefek/io/asl/resource/raid/RAID.java | 61 ++++ .../io/asl/resource/type/ResourceImage.java | 59 ++++ .../resource/type/ResourceInputStream.java | 40 +++ .../java/cz/tefek/io/asl/textio/TextIn.java | 78 +++++ .../java/cz/tefek/io/asl/textio/TextOut.java | 28 ++ .../main/java/cz/tefek/io/modloader/Mod.java | 141 ++++++++ .../cz/tefek/io/modloader/ModClassLoader.java | 152 +++++++++ .../java/cz/tefek/io/modloader/ModEntry.java | 39 +++ .../cz/tefek/io/modloader/ModInstaller.java | 117 +++++++ .../cz/tefek/io/modloader/ModLoaderCore.java | 270 +++++++++++++++ .../tefek/io/modloader/ModLoadingPhase.java | 17 + .../cz/tefek/io/modloader/event/ModLoad.java | 21 ++ .../io/modloader/event/ModLoadEvent.java | 15 + .../tefek/io/modloader/event/ModPostLoad.java | 21 ++ .../io/modloader/event/ModPostLoadEvent.java | 18 + .../tefek/io/modloader/event/ModPreLoad.java | 21 ++ .../io/modloader/event/ModPreLoadEvent.java | 19 ++ .../tefek/io/modloader/event/ModUnload.java | 20 ++ .../io/modloader/event/ModUnloadEvent.java | 15 + .../cz/tefek/io/pluto/debug/ISeverity.java | 8 + .../java/cz/tefek/io/pluto/debug/Logger.java | 98 ++++++ .../cz/tefek/io/pluto/debug/Severity.java | 35 ++ .../tefek/io/pluto/debug/SmartSeverity.java | 38 +++ .../io/pluto/debug/StdErrSplitStream.java | 53 +++ .../io/pluto/debug/StdOutSplitStream.java | 53 +++ .../pp/InvalidPlutoPackageException.java | 16 + .../cz/tefek/io/pluto/pp/PlutoPackage.java | 95 ++++++ .../main/java/cz/tefek/l10n/PlutoL10n.java | 67 ++++ .../cz/tefek/pluto/eventsystem/EventData.java | 10 + .../lambda/LambdaEventFactory.java | 95 ++++++ .../staticmode/StaticPlutoEvent.java | 25 ++ .../staticmode/StaticPlutoEventManager.java | 228 +++++++++++++ .../src/main/java/cz/tefek/tpl/TPJImage.java | 35 ++ plutoio2/src/main/java/cz/tefek/tpl/TPL.java | 187 +++++++++++ .../src/main/java/cz/tefek/tpl/TPNImage.java | 32 ++ plutomesher/pom.xml | 20 ++ .../pluto/engine/graphics/gl/DrawMode.java | 41 +++ .../engine/graphics/gl/vao/QuadPresets.java | 57 ++++ .../engine/graphics/gl/vao/VertexArray.java | 145 ++++++++ .../graphics/gl/vao/VertexArrayBuilder.java | 42 +++ .../gl/vao/attrib/ReservedAttributes.java | 9 + .../graphics/gl/vao/attrib/data/VecArray.java | 46 +++ .../engine/graphics/gl/vbo/ArrayBuffer.java | 62 ++++ .../graphics/gl/vbo/EnumArrayBufferType.java | 25 ++ .../graphics/gl/vbo/FloatArrayBuffer.java | 25 ++ .../graphics/gl/vbo/IndexArrayBuffer.java | 50 +++ plutoshader/pom.xml | 30 ++ .../pluto/engine/shader/IShaderProgram.java | 32 ++ .../pluto/engine/shader/PlutoShaderMod.java | 11 + .../engine/shader/RenderShaderBuilder.java | 252 ++++++++++++++ .../tefek/pluto/engine/shader/ShaderBase.java | 36 ++ .../pluto/engine/shader/ShaderCompiler.java | 42 +++ .../pluto/engine/shader/ShaderProgram.java | 14 + .../engine/shader/VertexArrayAttribute.java | 21 ++ .../engine/shader/type/EnumShaderType.java | 27 ++ .../engine/shader/type/FragmentShader.java | 20 ++ .../engine/shader/type/GeometryShader.java | 20 ++ .../pluto/engine/shader/type/IShader.java | 13 + .../engine/shader/type/VertexShader.java | 20 ++ .../shader/ubo/UniformBufferBindingPoint.java | 13 + .../shader/ubo/UniformBufferObject.java | 86 +++++ .../pluto/engine/shader/uniform/Uniform.java | 16 + .../shader/uniform/UniformArrayInt.java | 17 + .../shader/uniform/UniformArrayMat3x2.java | 35 ++ .../engine/shader/uniform/UniformBase.java | 16 + .../engine/shader/uniform/UniformBoolean.java | 16 + .../engine/shader/uniform/UniformFloat.java | 16 + .../engine/shader/uniform/UniformInt.java | 16 + .../engine/shader/uniform/UniformMat3.java | 24 ++ .../engine/shader/uniform/UniformMat3x2.java | 24 ++ .../engine/shader/uniform/UniformMat4.java | 24 ++ .../engine/shader/uniform/UniformVec2.java | 22 ++ .../engine/shader/uniform/UniformVec3.java | 22 ++ .../engine/shader/uniform/UniformVec4.java | 22 ++ .../uniform/auto/AutoViewportProjection.java | 18 + .../uniform/auto/AutomaticUniforms.java | 11 + plutospritesheet/pom.xml | 34 ++ .../engine/graphics/IRectangleShader2D.java | 29 ++ .../pluto/engine/graphics/IShader2D.java | 29 ++ .../engine/graphics/PlutoSpriteSheetMod.java | 70 ++++ .../engine/graphics/RectangleRenderer2D.java | 260 ++++++++++++++ .../pluto/engine/graphics/Renderer2D.java | 242 +++++++++++++ .../tefek/pluto/engine/graphics/Shader2D.java | 70 ++++ .../engine/graphics/ShaderRectangle2D.java | 70 ++++ .../graphics/skeleton/SpriteSkeleton.java | 26 ++ .../graphics/skeleton/SpriteSkeletonLimb.java | 192 +++++++++++ .../sprite/DisposablePlaceholderSprite.java | 18 + .../sprite/DisposableTextureSprite.java | 18 + .../graphics/sprite/PartialTextureSprite.java | 59 ++++ .../pluto/engine/graphics/sprite/Sprite.java | 14 + .../graphics/sprite/SpriteDisposable.java | 6 + .../engine/graphics/sprite/TileSprite.java | 85 +++++ .../BufferedImageTiledSpriteSheet.java | 114 +++++++ .../FramebufferTiledSpriteSheet.java | 154 +++++++++ .../graphics/spritesheet/SpriteSheet.java | 32 ++ .../spritesheet/TiledSpriteSheet.java | 244 ++++++++++++++ plutostatic/pom.xml | 217 ++++++++++++ .../java/cz/tefek/pluto/engine/ModLWJGL.java | 11 + .../pluto/engine/buffer/BufferHelper.java | 119 +++++++ .../pluto/engine/buffer/GLFWImageUtil.java | 61 ++++ .../pluto/engine/buffer/package-info.java | 7 + .../tefek/pluto/engine/chrono/MiniTime.java | 315 +++++++++++++++++ .../pluto/engine/chrono/package-info.java | 7 + .../tefek/pluto/engine/display/Display.java | 215 ++++++++++++ .../pluto/engine/display/DisplayBuilder.java | 78 +++++ .../engine/display/DisplayErrorCallback.java | 17 + .../tefek/pluto/engine/display/Framerate.java | 75 +++++ .../pluto/engine/display/package-info.java | 7 + .../cz/tefek/pluto/engine/event/IEvent.java | 12 + .../pluto/engine/event/package-info.java | 7 + .../cz/tefek/pluto/engine/gl/GLDebugInfo.java | 41 +++ .../cz/tefek/pluto/engine/gl/IOpenGLEnum.java | 13 + .../pluto/engine/math/ClampedSineWave.java | 72 ++++ .../tefek/pluto/engine/math/CubicBezier.java | 80 +++++ .../pluto/engine/math/CubicBezierLT.java | 65 ++++ .../pluto/engine/math/ProjectionMatrix.java | 66 ++++ .../engine/math/TransformationMatrix.java | 113 +++++++ .../tefek/pluto/engine/math/ViewMatrix.java | 33 ++ .../engine/math/collision/CollisionClass.java | 24 ++ .../math/collision/CollisionSurface.java | 83 +++++ .../collision/CollisionSurfaceCircle.java | 13 + .../math/collision/CollisionSurfaceLine.java | 18 + .../tefek/pluto/engine/math/package-info.java | 7 + plutotexturing/pom.xml | 20 ++ .../engine/graphics/texture/MagFilter.java | 24 ++ .../engine/graphics/texture/MinFilter.java | 35 ++ .../engine/graphics/texture/Texture.java | 241 +++++++++++++ .../engine/graphics/texture/WrapMode.java | 34 ++ .../graphics/texture/sampler/Sampler2D.java | 54 +++ .../graphics/texture/sampler/Sampler3D.java | 55 +++ .../texture/texture2d/RectangleTexture.java | 49 +++ .../graphics/texture/texture2d/Texture2D.java | 29 ++ 191 files changed, 10910 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 plutocommandparser/pom.xml create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/EnumParserState.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/platform/CommandPlatform.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolveException.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/EnumResolveFailure.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/GenericResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/ResolveException.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicDoubleResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicIntResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicLongResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/DoubleResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntFractionResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntResolver.java create mode 100644 plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/LongResolver.java create mode 100644 plutocommandparser/src/test/java/cz/tefek/pluto/command/resolver/BasicIntResolverTest.java create mode 100644 plutodb/.gitignore create mode 100644 plutodb/pom.xml create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/ILMDBValueRecipe.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBDatabase.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBEnvironment.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBIntegerKey.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBKey.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBLongKey.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBSchema.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBStringKey.java create mode 100644 plutodb/src/main/java/cz/tefek/plutodb/LMDBTransaction.java create mode 100644 plutodb/src/test/java/cz/tefek/test/plutodb/LMDBTest.java create mode 100644 plutodb/src/test/java/cz/tefek/test/plutodb/UserData.java create mode 100644 plutoframebuffer/pom.xml create mode 100644 plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/Framebuffer.java create mode 100644 plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferDepthTexture.java create mode 100644 plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferTexture.java create mode 100644 plutogui/pom.xml create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/graphics/PlutoGUIMod.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontManager.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontShader.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIPipeline.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIRenderer.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/CharacterInfo.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/Font.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontHelper.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer2.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/BasicGUIPipeline.java create mode 100644 plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/EnumGUIPipelineCommand.java create mode 100644 plutoio2/pom.xml create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/Resource.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddress.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddressTypeAdapter.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceHelper.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceSubscriber.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/IIdentifiable.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/RAID.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceImage.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceInputStream.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/textio/TextIn.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/asl/textio/TextOut.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/Mod.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/ModClassLoader.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/ModEntry.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/ModInstaller.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/ModLoaderCore.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/ModLoadingPhase.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoad.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoadEvent.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoad.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoadEvent.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoad.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoadEvent.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnload.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnloadEvent.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/ISeverity.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/Logger.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/Severity.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/SmartSeverity.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdErrSplitStream.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdOutSplitStream.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/pp/InvalidPlutoPackageException.java create mode 100644 plutoio2/src/main/java/cz/tefek/io/pluto/pp/PlutoPackage.java create mode 100644 plutoio2/src/main/java/cz/tefek/l10n/PlutoL10n.java create mode 100644 plutoio2/src/main/java/cz/tefek/pluto/eventsystem/EventData.java create mode 100644 plutoio2/src/main/java/cz/tefek/pluto/eventsystem/lambda/LambdaEventFactory.java create mode 100644 plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEvent.java create mode 100644 plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEventManager.java create mode 100644 plutoio2/src/main/java/cz/tefek/tpl/TPJImage.java create mode 100644 plutoio2/src/main/java/cz/tefek/tpl/TPL.java create mode 100644 plutoio2/src/main/java/cz/tefek/tpl/TPNImage.java create mode 100644 plutomesher/pom.xml create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/DrawMode.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/QuadPresets.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArray.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArrayBuilder.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/ReservedAttributes.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/data/VecArray.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/ArrayBuffer.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/EnumArrayBufferType.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/FloatArrayBuffer.java create mode 100644 plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/IndexArrayBuffer.java create mode 100644 plutoshader/pom.xml create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/IShaderProgram.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/PlutoShaderMod.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/RenderShaderBuilder.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderBase.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderCompiler.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderProgram.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/VertexArrayAttribute.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/EnumShaderType.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/FragmentShader.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/GeometryShader.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/IShader.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/VertexShader.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferBindingPoint.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferObject.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/Uniform.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayInt.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayMat3x2.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBase.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBoolean.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformFloat.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformInt.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3x2.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat4.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec2.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec3.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec4.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutoViewportProjection.java create mode 100644 plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutomaticUniforms.java create mode 100644 plutospritesheet/pom.xml create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IRectangleShader2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IShader2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/PlutoSpriteSheetMod.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/RectangleRenderer2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Renderer2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Shader2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/ShaderRectangle2D.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeleton.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeletonLimb.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposablePlaceholderSprite.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposableTextureSprite.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/PartialTextureSprite.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/Sprite.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/SpriteDisposable.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/TileSprite.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/BufferedImageTiledSpriteSheet.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/FramebufferTiledSpriteSheet.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/SpriteSheet.java create mode 100644 plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/TiledSpriteSheet.java create mode 100644 plutostatic/pom.xml create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/ModLWJGL.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/BufferHelper.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/GLFWImageUtil.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/package-info.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/MiniTime.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/package-info.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/display/Display.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayBuilder.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayErrorCallback.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/display/Framerate.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/display/package-info.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/event/IEvent.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/event/package-info.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/gl/GLDebugInfo.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/gl/IOpenGLEnum.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/ClampedSineWave.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezier.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezierLT.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/ProjectionMatrix.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/TransformationMatrix.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/ViewMatrix.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionClass.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurface.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceCircle.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceLine.java create mode 100644 plutostatic/src/main/java/cz/tefek/pluto/engine/math/package-info.java create mode 100644 plutotexturing/pom.xml create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MagFilter.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MinFilter.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/Texture.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/WrapMode.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler2D.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler3D.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/RectangleTexture.java create mode 100644 plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/Texture2D.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1dad0af --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/*/target/ +/*/.settings +/*/.project +/*/.classpath +/.project \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..27c37cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 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. diff --git a/plutocommandparser/pom.xml b/plutocommandparser/pom.xml new file mode 100644 index 0000000..555cf74 --- /dev/null +++ b/plutocommandparser/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + cz.tefek + plutocommandparser + 0.1 + plutocommandparser + Pluto Command Parser + + 11 + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutoio2 + 0.2 + + + \ No newline at end of file diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java new file mode 100644 index 0000000..709670e --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/CommandBase.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.command; + +public abstract class CommandBase +{ + public abstract String name(); + + public abstract String[] aliases(); + + public abstract String description(); + + public abstract Class commandClass(); + + @Override + public final int hashCode() + { + return this.name().hashCode(); + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java new file mode 100644 index 0000000..758b7eb --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/context/CommandContextBuilder.java @@ -0,0 +1,96 @@ +package cz.tefek.pluto.command.context; + +import cz.tefek.pluto.command.CommandBase; + +public class CommandContextBuilder +{ + private CommandContext ctx; + + public CommandContextBuilder() + { + this.ctx = new CommandContext(); + } + + public CommandContextBuilder prefix(String prefix) + { + this.ctx.usedPrefix = prefix; + + return this; + } + + public CommandContextBuilder alias(String alias) + { + this.ctx.usedAlias = alias; + + return this; + } + + public CommandContextBuilder command(CommandBase command) + { + this.ctx.command = command; + + return this; + } + + public CommandContext resolved() + { + this.ctx.resolved = true; + + return this.ctx; + } + + public CommandContext unresolved(EnumCommandParseFailure cause) + { + this.ctx.resolved = false; + + return this.ctx; + } + + public static class CommandContext + { + private boolean resolved; + private EnumCommandParseFailure failureCause; + + private String usedPrefix; + private String usedAlias; + private CommandBase command; + + private CommandContext() + { + + } + + public String getUsedPrefix() + { + return this.usedPrefix; + } + + public String getUsedAlias() + { + return this.usedAlias; + } + + public CommandBase getCommand() + { + return this.command; + } + + public boolean isResolved() + { + return this.resolved; + } + + public EnumCommandParseFailure getFailureCause() + { + return this.failureCause; + } + } + + public enum EnumCommandParseFailure + { + UNRESOLVED_PREFIX, + UNRESOLVED_COMMAND_NAME, + UNRESOLVED_PARAMETERS, + UNRESOLVED_UNEXPECTED_STATE; + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java new file mode 100644 index 0000000..b19beb8 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/CommandParser.java @@ -0,0 +1,252 @@ +package cz.tefek.pluto.command.parser; + +import java.util.PrimitiveIterator.OfInt; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import cz.tefek.pluto.command.CommandBase; +import cz.tefek.pluto.command.context.CommandContextBuilder; +import cz.tefek.pluto.command.context.CommandContextBuilder.CommandContext; +import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure; +import cz.tefek.pluto.command.registry.CommandRegistry; + +public class CommandParser +{ + private String text; + private Set prefixes; + private EnumParserState state; + + private StringBuilder prefixBuilder; + private StringBuilder commandNameBuilder; + + private CommandBase command; + + private StringBuilder parameterBuilder; + + private CommandContextBuilder ctx; + + private static final int CP_QUOTE = '"'; + + public CommandParser(String text) + { + this.text = text; + this.state = EnumParserState.BEGIN; + } + + private boolean readCodepoint(int cp) + { + switch (this.state) + { + case READING_PREFIX: + this.prefixBuilder.appendCodePoint(cp); + + this.prefixes.removeIf(ii -> ii.nextInt() != cp); + + if (this.prefixes.isEmpty()) + { + this.state = EnumParserState.END_NO_PREFIX; + return false; + } + + if (this.hasEmptyPrefix()) + { + this.ctx.prefix(this.prefixBuilder.toString()); + this.state = EnumParserState.READING_COMMAND; + } + + break; + + case READING_COMMAND: + this.commandNameBuilder.appendCodePoint(cp); + + if (Character.isWhitespace(cp)) + { + if (!this.resolveCommand()) + { + return false; + } + + this.state = EnumParserState.READ_WHITESPACE; + } + + break; + + case READ_WHITESPACE: + if (Character.isWhitespace(cp)) + { + break; + } + + if (cp == CP_QUOTE) + { + this.state = EnumParserState.READING_PARAMETER_QUOTED; + } + else + { + this.parameterBuilder.appendCodePoint(cp); + this.state = EnumParserState.READING_PARAMETER; + } + + break; + + case READING_PARAMETER_QUOTED: + if (cp == CP_QUOTE) + { + this.state = EnumParserState.READING_PARAMETER_CANDIDATE_UNQUOTE; + } + else + { + this.parameterBuilder.appendCodePoint(cp); + } + + break; + + case READING_PARAMETER: + if (Character.isWhitespace(cp)) + { + this.emitParameter(); + this.state = EnumParserState.READ_WHITESPACE; + } + else + { + this.parameterBuilder.appendCodePoint(cp); + } + + break; + + case READING_PARAMETER_CANDIDATE_UNQUOTE: + if (Character.isWhitespace(cp)) + { + this.emitParameter(); + this.state = EnumParserState.READ_WHITESPACE; + } + else + { + this.parameterBuilder.appendCodePoint(cp); + this.state = EnumParserState.READING_PARAMETER_QUOTED; + } + + break; + + case END: + this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK; + return false; + + default: + this.state = EnumParserState.UNEXPECTED_STATE_FALLBACK; + return false; + } + + return true; + } + + private boolean resolveCommand() + { + var alias = this.commandNameBuilder.toString(); + + this.ctx.alias(alias); + + this.command = CommandRegistry.getByAlias(alias); + + if (this.command == null) + { + this.state = EnumParserState.END_NO_COMMAND; + return false; + } + + return true; + } + + private void emitParameter() + { + + } + + private boolean hasEmptyPrefix() + { + return this.prefixes.stream().filter(Predicate.not(OfInt::hasNext)).findAny().isPresent(); + } + + /** + * Parse using this parser and supplied prefixes. This function also + * resolves the command context and the parameters. Yeah it does a lot of + * stuff. + * + */ + public CommandContext parse(Set prefixes) + { + if (this.state != EnumParserState.BEGIN) + { + throw new IllegalStateException("Cannot run a parser that is not in the BEGIN state."); + } + + this.prefixBuilder = new StringBuilder(); + this.ctx = new CommandContextBuilder(); + + this.prefixes = prefixes.stream().map(String::codePoints).map(IntStream::iterator).collect(Collectors.toSet()); + + if (prefixes.isEmpty() || this.hasEmptyPrefix()) + { + this.state = EnumParserState.READING_COMMAND; + } + else + { + this.state = EnumParserState.READING_PREFIX; + } + + this.text.codePoints().takeWhile(this::readCodepoint); + + // Update the state for EOF + switch (this.state) + { + case READING_PARAMETER_QUOTED: + case READ_WHITESPACE: + case READING_PARAMETER: + case READING_PARAMETER_CANDIDATE_UNQUOTE: + this.state = EnumParserState.END; + this.emitParameter(); + + break; + + case READING_COMMAND: + if (this.resolveCommand()) + { + this.state = EnumParserState.END; + } + + break; + + default: + break; + } + + // Check the end state + switch (this.state) + { + case READING_PREFIX: + case END_NO_PREFIX: + return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_PREFIX); + + case END_NO_COMMAND: + return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_COMMAND_NAME); + + case END: + break; + + default: + return this.ctx.unresolved(EnumCommandParseFailure.UNRESOLVED_UNEXPECTED_STATE); + } + + // At this point we are 100% sure the command was resolved and can validate the parameters + + /** + * + * TODO: Validate parameters here + * + */ + + return this.ctx.resolved(); + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/EnumParserState.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/EnumParserState.java new file mode 100644 index 0000000..570a02f --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/parser/EnumParserState.java @@ -0,0 +1,17 @@ +package cz.tefek.pluto.command.parser; + +public enum EnumParserState +{ + BEGIN, + READING_PREFIX, + READING_COMMAND, + READING_PARAMETER, + READING_PARAMETER_QUOTED, + READING_PARAMETER_CANDIDATE_UNQUOTE, + READ_WHITESPACE, + END_NO_PREFIX, + END_NO_COMMAND, + END_EMISSION_FAILURE, + END, + UNEXPECTED_STATE_FALLBACK; +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/platform/CommandPlatform.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/platform/CommandPlatform.java new file mode 100644 index 0000000..8249fb2 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/platform/CommandPlatform.java @@ -0,0 +1,30 @@ +package cz.tefek.pluto.command.platform; + +import cz.tefek.pluto.command.context.CommandContextBuilder.EnumCommandParseFailure; + +public abstract class CommandPlatform +{ + public abstract String getID(); + + public abstract String getName(); + + public boolean shouldWarnOn(EnumCommandParseFailure failure) + { + switch (failure) + { + case UNRESOLVED_PREFIX: + return false; + + case UNRESOLVED_COMMAND_NAME: + return false; + + case UNRESOLVED_UNEXPECTED_STATE: + return false; + + default: + return true; + } + } + + public abstract int getMessageLimit(); +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java new file mode 100644 index 0000000..bbdec84 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/registry/CommandRegistry.java @@ -0,0 +1,69 @@ +package cz.tefek.pluto.command.registry; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.command.CommandBase; + +public final class CommandRegistry +{ + private static CommandRegistry instance; + + private Set commands; + private Map aliasTable; + + static + { + instance = new CommandRegistry(); + } + + private CommandRegistry() + { + this.aliasTable = new HashMap<>(); + this.commands = new HashSet<>(); + } + + private void registerAlias(String alias, CommandBase command) + { + if (this.aliasTable.containsKey(alias)) + { + Logger.logf(SmartSeverity.ERROR, "Alias '%s' for command '%s' is already used, skipping.\n", alias, command.name()); + + return; + } + } + + public static void registerCommand(CommandBase command) + { + if (!instance.commands.add(command)) + { + Logger.logf(SmartSeverity.ERROR, "Command '%s' is already registered, skipping.\n", command.name()); + return; + } + + instance.registerAlias(command.name(), command); + + Arrays.stream(command.aliases()).forEach(alias -> instance.registerAlias(alias, command)); + } + + public static CommandBase getByAlias(String alias) + { + return instance.aliasTable.get(alias); + } + + public static Set getCommands() + { + return Collections.unmodifiableSet(instance.commands); + } + + public static void clear() + { + instance = new CommandRegistry(); + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolveException.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolveException.java new file mode 100644 index 0000000..18847fd --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolveException.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.command.resolver; + +public abstract class AbstractResolveException extends RuntimeException +{ + /** + * + */ + private static final long serialVersionUID = -8934505669078637864L; + + public AbstractResolveException(Exception exception) + { + super(exception); + } + + public AbstractResolveException(String what) + { + super(what); + } + + protected AbstractResolveException() + { + + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolver.java new file mode 100644 index 0000000..3cba691 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/AbstractResolver.java @@ -0,0 +1,6 @@ +package cz.tefek.pluto.command.resolver; + +public abstract class AbstractResolver +{ + public abstract Class getOutputType(); +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/EnumResolveFailure.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/EnumResolveFailure.java new file mode 100644 index 0000000..bdab475 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/EnumResolveFailure.java @@ -0,0 +1,13 @@ +package cz.tefek.pluto.command.resolver; + +public enum EnumResolveFailure +{ + INT_PARSE, + LONG_PARSE, + FLOAT_PARSE, + DOUBLE_PARSE, + FRAC_INVALID_PERCENTAGE, + FRAC_PERCENTAGE_OUT_OF_RANGE, + FRAC_VALUE_HIGHER_THAN_BASE, + OTHER; +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/GenericResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/GenericResolver.java new file mode 100644 index 0000000..59692b3 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/GenericResolver.java @@ -0,0 +1,21 @@ +package cz.tefek.pluto.command.resolver; + +import java.util.function.Function; + +public abstract class GenericResolver extends AbstractResolver +{ + protected Function func; + + public GenericResolver(Function func) + { + this.func = func; + } + + public R apply(String param) + { + return this.func.apply(param); + } + + @Override + public abstract Class getOutputType(); +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/ResolveException.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/ResolveException.java new file mode 100644 index 0000000..5cdda12 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/ResolveException.java @@ -0,0 +1,22 @@ +package cz.tefek.pluto.command.resolver; + +public class ResolveException extends AbstractResolveException +{ + /** + * + */ + private static final long serialVersionUID = -3098373878754649161L; + + private EnumResolveFailure failure; + + public ResolveException(EnumResolveFailure failure) + { + this.failure = failure; + } + + public EnumResolveFailure getResolveFailure() + { + return this.failure; + } + +} \ No newline at end of file diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicDoubleResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicDoubleResolver.java new file mode 100644 index 0000000..c2127f1 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicDoubleResolver.java @@ -0,0 +1,23 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import cz.tefek.pluto.command.resolver.EnumResolveFailure; +import cz.tefek.pluto.command.resolver.ResolveException; + +public class BasicDoubleResolver extends DoubleResolver +{ + public BasicDoubleResolver() + { + super(str -> + { + try + { + return Double.parseDouble(str); + } + catch (NumberFormatException e) + { + throw new ResolveException(EnumResolveFailure.DOUBLE_PARSE); + } + }); + } + +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicIntResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicIntResolver.java new file mode 100644 index 0000000..47cd2c0 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicIntResolver.java @@ -0,0 +1,22 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import cz.tefek.pluto.command.resolver.EnumResolveFailure; +import cz.tefek.pluto.command.resolver.ResolveException; + +public class BasicIntResolver extends IntResolver +{ + public BasicIntResolver() + { + super(str -> + { + try + { + return Integer.parseInt(str); + } + catch (NumberFormatException e) + { + throw new ResolveException(EnumResolveFailure.INT_PARSE); + } + }); + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicLongResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicLongResolver.java new file mode 100644 index 0000000..e46ab67 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/BasicLongResolver.java @@ -0,0 +1,23 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import cz.tefek.pluto.command.resolver.EnumResolveFailure; +import cz.tefek.pluto.command.resolver.ResolveException; + +public class BasicLongResolver extends LongResolver +{ + public BasicLongResolver() + { + super(str -> + { + try + { + return Long.parseLong(str); + } + catch (NumberFormatException e) + { + throw new ResolveException(EnumResolveFailure.LONG_PARSE); + } + }); + } + +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/DoubleResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/DoubleResolver.java new file mode 100644 index 0000000..eb3de72 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/DoubleResolver.java @@ -0,0 +1,26 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import java.util.function.ToDoubleFunction; + +import cz.tefek.pluto.command.resolver.AbstractResolver; + +public class DoubleResolver extends AbstractResolver +{ + protected ToDoubleFunction func; + + public DoubleResolver(ToDoubleFunction func) + { + this.func = func; + } + + public double apply(String param) + { + return this.func.applyAsDouble(param); + } + + @Override + public Class getOutputType() + { + return double.class; + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntFractionResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntFractionResolver.java new file mode 100644 index 0000000..7af03d7 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntFractionResolver.java @@ -0,0 +1,63 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import cz.tefek.pluto.command.resolver.EnumResolveFailure; +import cz.tefek.pluto.command.resolver.ResolveException; + +public class IntFractionResolver extends IntResolver +{ + public IntFractionResolver(int base) + { + super(str -> parseAmount(str, base)); + } + + private static int parseAmount(String amountString, int base) throws ResolveException + { + if (amountString.equalsIgnoreCase("all") || amountString.equalsIgnoreCase("everything")) + { + return base; + } + + if (amountString.equalsIgnoreCase("half")) + { + return base / 2; + } + + if (amountString.endsWith("%")) + { + try + { + float percentage = Float.parseFloat(amountString.substring(0, amountString.length() - 1)); + + if (percentage < 0 || percentage > 100) + { + throw new ResolveException(EnumResolveFailure.FRAC_PERCENTAGE_OUT_OF_RANGE); + } + else + { + return Math.round(percentage / 100.0f * base); + } + } + catch (NumberFormatException e1) + { + throw new ResolveException(EnumResolveFailure.FRAC_INVALID_PERCENTAGE); + } + } + + try + { + int amount = Integer.parseInt(amountString); + + if (amount > base) + { + throw new ResolveException(EnumResolveFailure.FRAC_VALUE_HIGHER_THAN_BASE); + } + + return amount; + } + catch (NumberFormatException e) + { + throw new ResolveException(EnumResolveFailure.INT_PARSE); + } + } + +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntResolver.java new file mode 100644 index 0000000..f958cf8 --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/IntResolver.java @@ -0,0 +1,26 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import java.util.function.ToIntFunction; + +import cz.tefek.pluto.command.resolver.AbstractResolver; + +public class IntResolver extends AbstractResolver +{ + protected ToIntFunction func; + + public IntResolver(ToIntFunction func) + { + this.func = func; + } + + public int apply(String param) + { + return this.func.applyAsInt(param); + } + + @Override + public final Class getOutputType() + { + return int.class; + } +} diff --git a/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/LongResolver.java b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/LongResolver.java new file mode 100644 index 0000000..3ba641b --- /dev/null +++ b/plutocommandparser/src/main/java/cz/tefek/pluto/command/resolver/primitive/LongResolver.java @@ -0,0 +1,26 @@ +package cz.tefek.pluto.command.resolver.primitive; + +import java.util.function.ToLongFunction; + +import cz.tefek.pluto.command.resolver.AbstractResolver; + +public class LongResolver extends AbstractResolver +{ + protected ToLongFunction func; + + public LongResolver(ToLongFunction func) + { + this.func = func; + } + + public long apply(String param) + { + return this.func.applyAsLong(param); + } + + @Override + public final Class getOutputType() + { + return long.class; + } +} diff --git a/plutocommandparser/src/test/java/cz/tefek/pluto/command/resolver/BasicIntResolverTest.java b/plutocommandparser/src/test/java/cz/tefek/pluto/command/resolver/BasicIntResolverTest.java new file mode 100644 index 0000000..e54212a --- /dev/null +++ b/plutocommandparser/src/test/java/cz/tefek/pluto/command/resolver/BasicIntResolverTest.java @@ -0,0 +1,41 @@ +package cz.tefek.pluto.command.resolver; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import cz.tefek.pluto.command.resolver.primitive.BasicIntResolver; + +class BasicIntResolverTest +{ + @Test + void parse() + { + var resolver = new BasicIntResolver(); + + Assertions.assertEquals(5, resolver.apply("5")); + + Assertions.assertEquals(-50, resolver.apply("-50")); + + Assertions.assertEquals(155, resolver.apply("155")); + + Assertions.assertEquals(0, resolver.apply("0")); + + Assertions.assertEquals(-0, resolver.apply("-0")); + } + + @Test + void exceptions() + { + var resolver = new BasicIntResolver(); + + // No foreign characters + Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("abc")); + + // No floats + Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("12.5")); + + // No empty strings + Assertions.assertThrows(NumberFormatException.class, () -> resolver.apply("")); + + } +} diff --git a/plutodb/.gitignore b/plutodb/.gitignore new file mode 100644 index 0000000..3bfc73e --- /dev/null +++ b/plutodb/.gitignore @@ -0,0 +1 @@ +/testdb \ No newline at end of file diff --git a/plutodb/pom.xml b/plutodb/pom.xml new file mode 100644 index 0000000..86971b1 --- /dev/null +++ b/plutodb/pom.xml @@ -0,0 +1,96 @@ + + 4.0.0 + cz.tefek + plutodb + 0.1 + plutodb + + 12 + 12 + UTF-8 + UTF-8 + 3.2.3 + + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + + + lwjgl-natives-linux-amd64 + + + unix + amd64 + + + + natives-linux + + + + lwjgl-natives-macos-amd64 + + + mac + amd64 + + + + natives-macos + + + + lwjgl-natives-windows-amd64 + + + windows + amd64 + + + + natives-windows + + + + lwjgl-natives-windows-x86 + + + windows + x86 + + + + natives-windows-x86 + + + + + + cz.tefek + plutoio2 + 0.2 + + + org.lwjgl + lwjgl-lmdb + + + org.lwjgl + lwjgl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-lmdb + ${lwjgl.natives} + + + \ No newline at end of file diff --git a/plutodb/src/main/java/cz/tefek/plutodb/ILMDBValueRecipe.java b/plutodb/src/main/java/cz/tefek/plutodb/ILMDBValueRecipe.java new file mode 100644 index 0000000..24e8938 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/ILMDBValueRecipe.java @@ -0,0 +1,37 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; + +public interface ILMDBValueRecipe +{ + public int sizeOf(); + + public void serialize(ByteBuffer output); + + public void deserialize(ByteBuffer input); + + public static int sizeOfUTF8(CharSequence string) + { + return Integer.BYTES + MemoryUtil.memLengthUTF8(string, false); + } + + public static void putUTF8(CharSequence string, ByteBuffer output) + { + int strLen = MemoryUtil.memUTF8(string, false, output, output.position() + Integer.BYTES); + output.putInt(strLen); + + output.position(output.position() + strLen); + } + + public static String getUTF8(ByteBuffer input) + { + var strLen = input.getInt(); + var string = MemoryUtil.memUTF8(input, strLen); + + input.position(input.position() + strLen); + + return string; + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBDatabase.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBDatabase.java new file mode 100644 index 0000000..498e8d2 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBDatabase.java @@ -0,0 +1,78 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.LMDB; +import org.lwjgl.util.lmdb.MDBVal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public class LMDBDatabase +{ + private LMDBTransaction transaction; + private int handle; + private MethodHandle valueConstructor; + + LMDBDatabase(LMDBTransaction transaction, int handle, Class valueClass) throws NoSuchMethodException, IllegalAccessException + { + this.handle = handle; + this.transaction = transaction; + this.valueConstructor = MethodHandles.lookup().findConstructor(valueClass, MethodType.methodType(void.class)); + } + + public long getHandle() + { + return this.handle; + } + + public void put(K key, V value) + { + try (var stack = MemoryStack.stackPush()) + { + var keyStruct = key.toMDBVal(stack); + + var size = value.sizeOf(); + + var data = stack.malloc(size); + + value.serialize(data); + + data.flip(); + + var valueStruct = MDBVal.mallocStack(stack).mv_data(data).mv_size(size); + + assert LMDB.mdb_put(this.transaction.getAddress(), this.handle, keyStruct, valueStruct, 0) == LMDB.MDB_SUCCESS; + } + } + + public V get(K key) + { + try + { + var valueToUpdate = (V) this.valueConstructor.invoke(); + return this.get(key, valueToUpdate); + } + catch (Throwable e) + { + e.printStackTrace(); + return null; + } + } + + public V get(K key, V valueToUpdate) + { + try (var stack = MemoryStack.stackPush()) + { + var keyStruct = key.toMDBVal(stack); + + var valueStruct = MDBVal.mallocStack(stack); + + assert LMDB.mdb_get(this.transaction.getAddress(), this.handle, keyStruct, valueStruct) == LMDB.MDB_SUCCESS; + + valueToUpdate.deserialize(valueStruct.mv_data()); + + return valueToUpdate; + } + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBEnvironment.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBEnvironment.java new file mode 100644 index 0000000..338afd3 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBEnvironment.java @@ -0,0 +1,130 @@ +package cz.tefek.plutodb; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.LMDB; + +import java.io.File; +import java.io.IOException; + +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +public class LMDBEnvironment implements AutoCloseable +{ + private static final Set envGuard = Collections.synchronizedSet(new HashSet<>()); + + private final File path; + private final long envPtr; + private final int maxDBs; + private static final int permissions = 0640; + + public LMDBEnvironment(File path, int maxDBs, long mapSize) throws IOException + { + if (!envGuard.add(path)) + { + throw new RuntimeException("Error: The LMDB Environment guard has detected a creation of an environment on an already locked file. To avoid synchronization issues, this cannot be permitted."); + } + + this.path = path; + this.maxDBs = maxDBs; + + try (var stack = MemoryStack.stackPush()) + { + var ptrBuf = stack.mallocPointer(1); + int createCode = LMDB.mdb_env_create(ptrBuf); + + if (createCode != LMDB.MDB_SUCCESS) + { + throw new RuntimeException(String.format("LMDB environment failed to create! Error code: %d", createCode)); + } + + this.envPtr = ptrBuf.get(0); + } + + int mapSizeResult = LMDB.mdb_env_set_mapsize(this.envPtr, mapSize); + + if (mapSizeResult != LMDB.MDB_SUCCESS) + { + throw new RuntimeException(String.format("Failed to set the MDB map size! Error code: %d", mapSizeResult)); + } + + int maxDBsResult = LMDB.mdb_env_set_maxdbs(this.envPtr, this.maxDBs); + + if (maxDBsResult != LMDB.MDB_SUCCESS) + { + throw new RuntimeException(String.format("Failed to set the MDB maximum database count! Error code: %d", maxDBsResult)); + } + + if (!path.isDirectory()) + { + path.mkdirs(); + } + + int openCode = LMDB.mdb_env_open(this.envPtr, path.getAbsolutePath(), 0, permissions); + + if (openCode != LMDB.MDB_SUCCESS) + { + throw new RuntimeException(String.format("LMDB environment failed to open! Error code: %d", openCode)); + } + } + + public LMDBTransaction createTransaction() + { + return this.createTransaction(false); + } + + public synchronized LMDBTransaction createTransaction(boolean readOnly) + { + try (var stack = MemoryStack.stackPush()) + { + var ptrBuf = stack.mallocPointer(1); + + int code = LMDB.mdb_txn_begin(this.envPtr, 0, readOnly ? LMDB.MDB_RDONLY : 0, ptrBuf); + + if (code != LMDB.MDB_SUCCESS) + { + String errStr; + + switch (code) + { + case LMDB.MDB_PANIC: + errStr = "MDB_PANIC"; + break; + + case LMDB.MDB_MAP_RESIZED: + errStr = "MDB_MAP_RESIZED"; + break; + + case LMDB.MDB_READERS_FULL: + errStr = "MDB_READERS_FULL"; + break; + + default: + throw new RuntimeException(String.format("Failed to create a transaction. Error code: %d", code)); + } + + throw new RuntimeException(String.format("Failed to create a transaction. Error code: %s", errStr)); + } + + long ptr = ptrBuf.get(0); + + return new LMDBTransaction(this, ptr, readOnly); + } + } + + public long getAddress() + { + return this.envPtr; + } + + @Override + public void close() throws Exception + { + LMDB.mdb_env_close(this.envPtr); + envGuard.remove(this.path); + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBIntegerKey.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBIntegerKey.java new file mode 100644 index 0000000..08d30e9 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBIntegerKey.java @@ -0,0 +1,29 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +public class LMDBIntegerKey extends LMDBKey +{ + private int key; + + private LMDBIntegerKey(int key) + { + this.key = key; + } + + public static LMDBIntegerKey from(int rawKey) + { + return new LMDBIntegerKey(rawKey); + } + + @Override + public MDBVal toMDBVal(MemoryStack stack) + { + var keyBuf = stack.malloc(Integer.BYTES); + + keyBuf.putInt(this.key); + + return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf); + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBKey.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBKey.java new file mode 100644 index 0000000..fab5184 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBKey.java @@ -0,0 +1,9 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +public abstract class LMDBKey +{ + public abstract MDBVal toMDBVal(MemoryStack stack); +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBLongKey.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBLongKey.java new file mode 100644 index 0000000..78fea6e --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBLongKey.java @@ -0,0 +1,29 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +public class LMDBLongKey extends LMDBKey +{ + private long key; + + private LMDBLongKey(long key) + { + this.key = key; + } + + public static LMDBLongKey from(long rawKey) + { + return new LMDBLongKey(rawKey); + } + + @Override + public MDBVal toMDBVal(MemoryStack stack) + { + var keyBuf = stack.malloc(Long.BYTES); + + keyBuf.putLong(this.key); + + return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf); + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBSchema.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBSchema.java new file mode 100644 index 0000000..6ef4ab8 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBSchema.java @@ -0,0 +1,23 @@ +package cz.tefek.plutodb; + +public class LMDBSchema +{ + private String name; + private Class valueType; + + public LMDBSchema(String name, Class valueType) + { + this.name = name; + this.valueType = valueType; + } + + public String getName() + { + return this.name; + } + + public Class getValueRecipe() + { + return this.valueType; + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBStringKey.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBStringKey.java new file mode 100644 index 0000000..2bb5dfd --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBStringKey.java @@ -0,0 +1,27 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.MDBVal; + +public class LMDBStringKey extends LMDBKey +{ + private String key; + + private LMDBStringKey(String key) + { + this.key = key; + } + + public static LMDBStringKey from(String rawKey) + { + return new LMDBStringKey(rawKey); + } + + @Override + public MDBVal toMDBVal(MemoryStack stack) + { + var keyBuf = stack.ASCII(this.key, false); + + return MDBVal.mallocStack(stack).mv_size(keyBuf.capacity()).mv_data(keyBuf); + } +} diff --git a/plutodb/src/main/java/cz/tefek/plutodb/LMDBTransaction.java b/plutodb/src/main/java/cz/tefek/plutodb/LMDBTransaction.java new file mode 100644 index 0000000..31142b5 --- /dev/null +++ b/plutodb/src/main/java/cz/tefek/plutodb/LMDBTransaction.java @@ -0,0 +1,108 @@ +package cz.tefek.plutodb; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.lmdb.LMDB; + +public class LMDBTransaction implements AutoCloseable +{ + private long ptr; + private LMDBEnvironment env; + private boolean readOnly; + private boolean uncommited = true; + + LMDBTransaction(LMDBEnvironment env, long ptr, boolean readOnly) + { + this.env = env; + this.ptr = ptr; + this.readOnly = readOnly; + } + + public long getAddress() + { + return this.ptr; + } + + public LMDBEnvironment getEnvironment() + { + return this.env; + } + + public boolean isReadOnly() + { + return this.readOnly; + } + + private int getDatabase(String name) + { + try (var stack = MemoryStack.stackPush()) + { + var intPtr = stack.mallocInt(1); + + int createFlag = this.readOnly ? 0 : LMDB.MDB_CREATE; + + int returnCode = LMDB.mdb_dbi_open(this.ptr, name, createFlag, intPtr); + + if (returnCode != LMDB.MDB_SUCCESS) + { + switch (returnCode) + { + case LMDB.MDB_DBS_FULL: + throw new RuntimeException("Error: mdb_dbi_open failed with the following error: DBS_FULL (too many open databases)"); + + case LMDB.MDB_NOTFOUND: + throw new RuntimeException("Error: mdb_dbi_open failed with the following error: NOTFOUND (database was not found)"); + + default: + throw new RuntimeException(String.format("Error: mdb_dbi_open failed with the following error code: %d", returnCode)); + } + } + + int dbHandle = intPtr.get(0); + + return dbHandle; + } + } + + public LMDBDatabase getDatabase(LMDBSchema schema) + { + var dbHandle = this.getDatabase(schema.getName()); + + var recipeClazz = schema.getValueRecipe(); + + try + { + return new LMDBDatabase(this, dbHandle, recipeClazz); + } + catch (NoSuchMethodException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + + return null; + } + + public void commit() + { + LMDB.mdb_txn_commit(this.ptr); + this.uncommited = false; + } + + public void abort() + { + LMDB.mdb_txn_abort(this.ptr); + this.uncommited = false; + } + + @Override + public void close() throws Exception + { + if (this.readOnly || this.uncommited) + { + this.abort(); + } + } +} diff --git a/plutodb/src/test/java/cz/tefek/test/plutodb/LMDBTest.java b/plutodb/src/test/java/cz/tefek/test/plutodb/LMDBTest.java new file mode 100644 index 0000000..ee200b2 --- /dev/null +++ b/plutodb/src/test/java/cz/tefek/test/plutodb/LMDBTest.java @@ -0,0 +1,43 @@ +package cz.tefek.test.plutodb; + +import java.io.File; + +import cz.tefek.plutodb.LMDBEnvironment; +import cz.tefek.plutodb.LMDBSchema; +import cz.tefek.plutodb.LMDBStringKey; + +public class LMDBTest +{ + public static void main(String[] args) throws Exception + { + var userSchema = new LMDBSchema("users", UserData.class); + + try (var env = new LMDBEnvironment(new File("testdb"), 80, 1024 * 1024 * 256)) + { + try (var txn = env.createTransaction()) + { + var db = txn.getDatabase(userSchema); + + var data = new UserData(); + data.money = -789; + data.keks = 123456; + data.text = "Hello world!"; + data.keys = 0x12C0FFEE; + + db.put(LMDBStringKey.from("pepega"), data); + + txn.commit(); + } + + try (var txn = env.createTransaction(true)) + { + var db = txn.getDatabase(userSchema); + + var data = db.get(LMDBStringKey.from("pepega")); + + System.out.println(data); + } + } + + } +} diff --git a/plutodb/src/test/java/cz/tefek/test/plutodb/UserData.java b/plutodb/src/test/java/cz/tefek/test/plutodb/UserData.java new file mode 100644 index 0000000..02bc8a0 --- /dev/null +++ b/plutodb/src/test/java/cz/tefek/test/plutodb/UserData.java @@ -0,0 +1,44 @@ +package cz.tefek.test.plutodb; + +import java.nio.ByteBuffer; + +import cz.tefek.plutodb.ILMDBValueRecipe; + +public class UserData implements ILMDBValueRecipe +{ + public long money; + public long keks; + public String text; + public int keys; + + @Override + public void serialize(ByteBuffer output) + { + output.putLong(this.money); + output.putLong(this.keks); + ILMDBValueRecipe.putUTF8(this.text, output); + output.putInt(this.keys); + } + + @Override + public void deserialize(ByteBuffer input) + { + this.money = input.getLong(); + this.keks = input.getLong(); + this.text = ILMDBValueRecipe.getUTF8(input); + this.keys = input.getInt(); + } + + @Override + public String toString() + { + return "UserData [money=" + this.money + ", keks=" + this.keks + ", text=" + this.text + ", keys=" + this.keys + "]"; + } + + @Override + public int sizeOf() + { + return Long.BYTES + Long.BYTES + ILMDBValueRecipe.sizeOfUTF8(this.text) + Integer.BYTES; + + } +} diff --git a/plutoframebuffer/pom.xml b/plutoframebuffer/pom.xml new file mode 100644 index 0000000..5d98a21 --- /dev/null +++ b/plutoframebuffer/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + cz.tefek + plutoframebuffer + 0.1 + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutostatic + 0.3 + + + cz.tefek + plutotexturing + 0.1 + + + \ No newline at end of file diff --git a/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/Framebuffer.java b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/Framebuffer.java new file mode 100644 index 0000000..72a3c2e --- /dev/null +++ b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/Framebuffer.java @@ -0,0 +1,84 @@ +package cz.tefek.pluto.engine.graphics.gl.fbo; + +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +public class Framebuffer +{ + private int id; + + private List textures; + + private FramebufferDepthTexture depthTexture; + + public Framebuffer() + { + this.id = GL33.glGenFramebuffers(); + this.textures = new ArrayList<>(GL33.glGetInteger(GL33.GL_MAX_COLOR_ATTACHMENTS)); + + Logger.logf(SmartSeverity.ADDED, "Framebuffer ID %d created.\n", this.id); + } + + public void bind() + { + GL33.glBindFramebuffer(GL33.GL_FRAMEBUFFER, this.id); + } + + public void unbind() + { + GL33.glBindFramebuffer(GL33.GL_FRAMEBUFFER, 0); + } + + public void delete() + { + GL33.glDeleteFramebuffers(this.id); + Logger.logf(SmartSeverity.REMOVED, "Framebuffer ID %d deleted.\n", this.id); + this.id = 0; + } + + public void addTexture(FramebufferTexture texture) + { + this.bind(); + texture.bind(); + GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_COLOR_ATTACHMENT0 + + this.textures.size(), texture.getType(), texture.getID(), 0); + this.textures.add(texture); + } + + public void removeAllTextures() + { + this.bind(); + + for (int i = this.textures.size() - 1; i > 0; --i) + { + var texture = this.textures.get(i); + + GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_COLOR_ATTACHMENT0 + i, texture.getType(), 0, 0); + } + + this.textures.clear(); + } + + public void setDepthTexture(FramebufferDepthTexture depthTexture) + { + this.bind(); + depthTexture.bind(); + GL33.glFramebufferTexture2D(GL33.GL_FRAMEBUFFER, GL33.GL_DEPTH_STENCIL_ATTACHMENT, depthTexture.getType(), depthTexture.getID(), 0); + this.depthTexture = depthTexture; + } + + public List getTextures() + { + return this.textures; + } + + public FramebufferDepthTexture getDepthTexture() + { + return this.depthTexture; + } +} diff --git a/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferDepthTexture.java b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferDepthTexture.java new file mode 100644 index 0000000..3cad363 --- /dev/null +++ b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferDepthTexture.java @@ -0,0 +1,21 @@ +package cz.tefek.pluto.engine.graphics.gl.fbo; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; + +public class FramebufferDepthTexture extends FramebufferTexture +{ + public FramebufferDepthTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV) + { + super(width, height, magFilter, minFilter, wrapU, wrapV); + } + + @Override + public void writeData(long address) + { + GL33.glTexImage2D(this.type, 0, GL33.GL_DEPTH24_STENCIL8, this.width, this.height, 0, GL33.GL_DEPTH_STENCIL, GL33.GL_UNSIGNED_INT_24_8, address); + } +} diff --git a/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferTexture.java b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferTexture.java new file mode 100644 index 0000000..5dda373 --- /dev/null +++ b/plutoframebuffer/src/main/java/cz/tefek/pluto/engine/graphics/gl/fbo/FramebufferTexture.java @@ -0,0 +1,34 @@ +package cz.tefek.pluto.engine.graphics.gl.fbo; + +import org.lwjgl.system.MemoryUtil; + +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +public class FramebufferTexture extends RectangleTexture +{ + public FramebufferTexture(int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode wrapU, WrapMode wrapV) + { + this.bind(); + this.setFilteringOptions(magFilter, minFilter); + this.setWrapOptions(wrapU, wrapV); + + this.resize(width, height); + } + + public FramebufferTexture(int width, int height) + { + this(width, height, MagFilter.LINEAR, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + } + + public void resize(int width, int height) + { + this.bind(); + + this.width = width; + this.height = height; + this.writeData(MemoryUtil.NULL); + } +} diff --git a/plutogui/pom.xml b/plutogui/pom.xml new file mode 100644 index 0000000..5c593b6 --- /dev/null +++ b/plutogui/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + cz.tefek + plutogui + 0.2 + plutogui + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutotexturing + 0.1 + + + cz.tefek + plutomesher + 0.2 + + + cz.tefek + plutoshader + 0.3 + + + cz.tefek + plutospritesheet + 0.2 + + + \ No newline at end of file diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/PlutoGUIMod.java b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/PlutoGUIMod.java new file mode 100644 index 0000000..7fff0d0 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/PlutoGUIMod.java @@ -0,0 +1,67 @@ +package cz.tefek.pluto.engine.graphics; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.asl.resource.ResourceSubscriber; +import cz.tefek.io.modloader.Mod; +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.io.modloader.ModLoaderCore; +import cz.tefek.io.modloader.event.ModPreLoad; +import cz.tefek.io.modloader.event.ModPreLoadEvent; +import cz.tefek.io.modloader.event.ModUnload; +import cz.tefek.io.modloader.event.ModUnloadEvent; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.pluto.engine.graphics.font.FontManager; +import cz.tefek.pluto.engine.graphics.font.FontShader; +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; +import cz.tefek.pluto.engine.gui.font.FontRenderer; +import cz.tefek.pluto.engine.shader.RenderShaderBuilder; + +/** + * @author 493msi + * + */ +@ModEntry(modid = PlutoGUIMod.MOD_ID, displayName = "Pluto Engine GUI Renderer", author = "Tefek", build = 2, dependencies = { PlutoSpriteSheetMod.class }, clientSupport = true, serverSupport = false, version = "0.2", description = "Everything GUI of PlutoEngine.") +public class PlutoGUIMod +{ + public static final String MOD_ID = "plutogui"; + + public static Mod instance; + public static ResourceSubscriber subscriber; + + public static RectangleTexture uiElementsAtlas; + + private static FontShader fontShader; + + @ModPreLoad + public static void preLoad(ModPreLoadEvent event) + { + instance = ModLoaderCore.getModByID(MOD_ID); + subscriber = instance.getDefaultResourceSubscriber(); + + Logger.log("Intializing " + MOD_ID + "..."); + + fontShader = new RenderShaderBuilder(subscriber, "shaders.VertexFontShader#glsl", "shaders.FragmentFontShader#glsl").build(FontShader.class, false); + + // Load the default font + FontManager.loadFont(new ResourceAddress(subscriber, "font.default")); + + FontRenderer.load(fontShader); + + uiElementsAtlas = new RectangleTexture(); + uiElementsAtlas.load(new ResourceAddress(subscriber, "gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + } + + @ModUnload + public static void unload(ModUnloadEvent unloadEvent) + { + uiElementsAtlas.delete(); + + FontManager.unloadAll(); + + FontRenderer.unload(); + fontShader.dispose(); + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontManager.java b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontManager.java new file mode 100644 index 0000000..b2d37ee --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontManager.java @@ -0,0 +1,102 @@ +package cz.tefek.pluto.engine.graphics.font; + +import java.util.HashMap; +import java.util.Map; + +import java.io.BufferedReader; +import java.nio.file.Files; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.Texture; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; +import cz.tefek.pluto.engine.gui.font.CharacterInfo; +import cz.tefek.pluto.engine.gui.font.Font; + +public class FontManager +{ + private static Map fonts = new HashMap<>(); + + public static void loadFont(ResourceAddress address) + { + String fontname = null; + int width = 0; + int height = 0; + var def = new HashMap(); + + int row = 0; + + try (BufferedReader br = Files.newBufferedReader(address.copy().branch("definitions").fileExtension("txt").toNIOPath())) + { + String line; + while ((line = br.readLine()) != null) + { + if (line.startsWith("//")) + { + continue; + } + + if (row == 0) + { + String[] fontinfo; + fontinfo = line.split(","); + + fontname = fontinfo[0]; + + String[] dim = fontinfo[1].split("x"); + + width = Integer.parseInt(dim[0]); + height = Integer.parseInt(dim[1]); + } + + if (row > 0) + { + String[] offs = null; + + offs = line.split(" ")[1].split(";"); + + def.put(line.charAt(0), new CharacterInfo(row - 1, Integer.parseInt(offs[0]), Integer.parseInt(offs[1]))); + } + + row++; + } + + br.close(); + } + catch (Exception e) + { + Logger.log(SmartSeverity.ERROR, "Could not load font: " + address.toString()); + e.printStackTrace(); + } + + Font font = new Font(fontname, width, height, def); + RectangleTexture texture = new RectangleTexture(); + texture.load(address.copy().branch("tex").fileExtension("png"), MagFilter.NEAREST, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + font.setTexture(texture); + + fonts.put(fontname, font); + } + + public static void unloadAll() + { + fonts.values().stream().map(Font::getTexture).forEach(Texture::delete); + fonts.clear(); + } + + public static Font getFontByName(String fontname) + { + var font = fonts.get(fontname); + + if (font == null) + { + Logger.log(SmartSeverity.WARNING, "Font with name " + fontname + " could not be found, using the default one instead (if there is one)."); + return fonts.get("default"); + } + + return font; + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontShader.java b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontShader.java new file mode 100644 index 0000000..09298b3 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/graphics/font/FontShader.java @@ -0,0 +1,41 @@ +package cz.tefek.pluto.engine.graphics.font; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes; +import cz.tefek.pluto.engine.shader.ShaderBase; +import cz.tefek.pluto.engine.shader.ShaderProgram; +import cz.tefek.pluto.engine.shader.VertexArrayAttribute; +import cz.tefek.pluto.engine.shader.uniform.Uniform; +import cz.tefek.pluto.engine.shader.uniform.UniformBoolean; +import cz.tefek.pluto.engine.shader.uniform.UniformMat4; +import cz.tefek.pluto.engine.shader.uniform.UniformVec2; +import cz.tefek.pluto.engine.shader.uniform.UniformVec4; +import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection; + +@ShaderProgram +public final class FontShader extends ShaderBase +{ + @AutoViewportProjection + @Uniform(name = "projection") + public UniformMat4 projectionMatrix; + + @Uniform(name = "transformation") + public UniformMat4 transformationMatrix; + + @Uniform + public UniformVec2 uvBase; + + @Uniform + public UniformVec2 uvDelta; + + @Uniform + public UniformVec4 recolor; + + @Uniform + public UniformBoolean italic; + + @VertexArrayAttribute(ReservedAttributes.POSITION) + public int position; + + @VertexArrayAttribute(ReservedAttributes.UV) + public int uvCoords; +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIPipeline.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIPipeline.java new file mode 100644 index 0000000..32e7256 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIPipeline.java @@ -0,0 +1,6 @@ +package cz.tefek.pluto.engine.gui; + +public interface IGUIPipeline +{ + void flush(); +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIRenderer.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIRenderer.java new file mode 100644 index 0000000..de48820 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/IGUIRenderer.java @@ -0,0 +1,6 @@ +package cz.tefek.pluto.engine.gui; + +public interface IGUIRenderer +{ + void flush(); +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/CharacterInfo.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/CharacterInfo.java new file mode 100644 index 0000000..5155544 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/CharacterInfo.java @@ -0,0 +1,30 @@ +package cz.tefek.pluto.engine.gui.font; + +public class CharacterInfo +{ + int leftOffs; + private int rightOffs; + int number; + + public CharacterInfo(int number, int leftOffs, int rightOffs) + { + this.number = number; + this.leftOffs = leftOffs; + this.rightOffs = rightOffs; + } + + public int getLeftOffset() + { + return this.leftOffs; + } + + public int getNumber() + { + return this.number; + } + + public int getRightOffset() + { + return this.rightOffs; + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/Font.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/Font.java new file mode 100644 index 0000000..ce72c11 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/Font.java @@ -0,0 +1,59 @@ +package cz.tefek.pluto.engine.gui.font; + +import java.util.HashMap; + +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +public class Font +{ + private String name; + private int width; + private int height; + private HashMap definitions; + private RectangleTexture texture; + + public Font(String name, int width, int height, HashMap def) + { + this.name = name; + this.width = width; + this.height = height; + this.definitions = def; + } + + public String getName() + { + return this.name; + } + + public void setName(String name) + { + this.name = name; + } + + public int getTextureWidth() + { + return this.width; + } + + public int getTextureHeight() + { + return this.height; + } + + public RectangleTexture getTexture() + { + return this.texture; + } + + public void setTexture(RectangleTexture texture) + { + this.texture = texture; + this.width = texture.getWidth(); + this.height = texture.getHeight(); + } + + public HashMap getDefinitions() + { + return this.definitions; + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontHelper.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontHelper.java new file mode 100644 index 0000000..0e65eab --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontHelper.java @@ -0,0 +1,121 @@ +package cz.tefek.pluto.engine.gui.font; + +import cz.tefek.pluto.engine.graphics.font.FontManager; + +public class FontHelper +{ + public static int calcStringWidth(Object string, String fontname, float relativeSize) + { + Font font = FontManager.getFontByName(fontname); + + if (font == null) + { + System.out.println("Font doesn't exist: " + fontname); + return -1; + } + + float absoluteCharWidth = 16; + String text = string.toString(); + + int maxW = 0; + int totalSpacing = 0; + + for (int i = 0; i < text.length(); i++) + { + if (text.length() > i + 1) + { + if (text.charAt(i) == '\\' && text.charAt(i + 1) == '&') + { + continue; + } + } + + // &c[0xff770077] + if (text.length() > i + 13) + { + if (text.charAt(i) == '&' && text.charAt(i + 1) == 'c' && text.charAt(i + 2) == '[' && text.charAt(i + 13) == ']') + { + char c = '0'; + char cBef = '0'; + + if (i > 0) + { + c = text.charAt(i - 1); + } + + if (i > 1) + { + cBef = text.charAt(i - 2); + } + + if (c != '\\' || cBef == '\\' && c == '\\') + { + i += 13; + + continue; + } + } + } + + if (text.length() > i + 2) + { + if (text.charAt(i) == '&' && text.charAt(i + 1) == 'i') + { + char c = '0'; + char cBef = '0'; + + if (i > 0) + { + c = text.charAt(i - 1); + } + + if (i > 1) + { + cBef = text.charAt(i - 2); + } + + if (c != '\\' || cBef == '\\' && c == '\\') + { + i += 2; + + continue; + } + } + } + + if (text.charAt(i) == '\n') + { + totalSpacing = 0; + } + else if (text.charAt(i) == ' ') + { + totalSpacing += 12 * relativeSize; + } + else + { + CharacterInfo charInf = font.getDefinitions().get(text.charAt(i)); + + if (charInf == null) + { + charInf = font.getDefinitions().get('?'); + } + + totalSpacing -= charInf.getLeftOffset() * relativeSize - relativeSize; + totalSpacing += absoluteCharWidth * relativeSize; + totalSpacing -= charInf.getRightOffset() * relativeSize - 1; + } + + maxW = Math.max(maxW, totalSpacing); + } + + return maxW; + } + + public static int calcStringHeight(Object string, float relSize) + { + int size = (int) (30f * relSize * string.toString().split("\n").length); + + return size; + } + +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer.java new file mode 100644 index 0000000..4e6f67b --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer.java @@ -0,0 +1,311 @@ +package cz.tefek.pluto.engine.gui.font; + +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; + +import cz.tefek.pluto.engine.graphics.font.FontManager; +import cz.tefek.pluto.engine.graphics.font.FontShader; +import cz.tefek.pluto.engine.graphics.gl.DrawMode; +import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets; +import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray; +import cz.tefek.pluto.engine.math.TransformationMatrix; + +public class FontRenderer +{ + private static final int LINE_HEIGHT = 30; + private static final int SPACE_WIDTH = 12; + + private static final int CHAR_WIDTH = 16; + private static final int CHAR_HEIGHT = 24; + + private static VertexArray charVAO; + private static FontShader fontShader; + + public static void load(FontShader defaultFontShaderIn) + { + charVAO = QuadPresets.basicNoNeg(); + fontShader = defaultFontShaderIn; + } + + public static void unload() + { + if (charVAO != null) + { + charVAO.delete(); + } + } + + public static void prepareInstance(Font font) + { + GL11.glEnable(GL11.GL_BLEND); + fontShader.start(); + charVAO.bind(); + charVAO.enableAllAttributes(); + font.getTexture().bind(); + } + + public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname, boolean isShadow) + { + Font font = FontManager.getFontByName(fontname); + + if (font == null) + { + System.err.println("Font doesn't exist: " + fontname); + return; + } + + float charWidth = CHAR_WIDTH * relativeSize; + float charHeight = CHAR_HEIGHT * relativeSize; + + float lineHeight = LINE_HEIGHT * relativeSize; + float spaceWidth = SPACE_WIDTH * relativeSize; + + prepareInstance(font); + + color(color); + + String text = String.valueOf(string); + + float drawX = xPos; + float drawY = yPos; + + for (int characterIndex = 0; characterIndex < text.length(); characterIndex++) + { + int column = 0; + int row = 0; + + var currentChar = text.charAt(characterIndex); + + if (text.length() > characterIndex + 1) + { + var nextChar = text.charAt(characterIndex + 1); + + if (currentChar == '\\' && nextChar == '&') + { + continue; + } + + // Inline coloring (tm) :) -> &c[0xff770077] + if (text.length() > characterIndex + 13) + { + if (currentChar == '&' && nextChar == 'c' && text.charAt(characterIndex + 2) == '[' && text.charAt(characterIndex + 13) == ']') + { + char c = '0'; + char cBef = '0'; + + if (characterIndex > 0) + { + c = text.charAt(characterIndex - 1); + } + + if (characterIndex > 1) + { + cBef = text.charAt(characterIndex - 2); + } + + if (c != '\\' || cBef == '\\' && c == '\\') + { + if (!isShadow) + { + char[] col = new char[10]; + + text.getChars(characterIndex + 3, characterIndex + 13, col, 0); + + String clr = String.valueOf(col); + + color(clr); + } + + characterIndex += 13; + + continue; + } + } + } + + if (text.length() > characterIndex + 2) + { + if (currentChar == '&' && nextChar == 'i' && (text.charAt(characterIndex + 2) == '1' || text.charAt(characterIndex + 2) == '0')) + { + char c = '0'; + char cBef = '0'; + + if (characterIndex > 0) + { + c = text.charAt(characterIndex - 1); + } + + if (characterIndex > 1) + { + cBef = text.charAt(characterIndex - 2); + } + + if (c != '\\' || cBef == '\\' && c == '\\') + { + if (text.charAt(characterIndex + 2) == '1') + { + italic(true); + } + else + { + italic(false); + } + + characterIndex += 2; + + continue; + } + } + } + } + + float shift = 0; + + switch (currentChar) + { + case '\n': + color(color); + drawX = xPos; + drawY += lineHeight; + continue; + + case ' ': + drawX += spaceWidth; + continue; + + case 'g': + case 'y': + case 'p': + case 'j': + shift = 6 * relativeSize; + + break; + + default: + break; + } + + var fontDefs = font.getDefinitions(); + var charInf = fontDefs.get(currentChar); + + if (charInf == null) + { + charInf = fontDefs.get('?'); + } + + var atlasIndex = charInf.getNumber(); + + row = atlasIndex / CHAR_WIDTH; + column = atlasIndex % CHAR_WIDTH; + + // Position of the current character in the texture atlas in pixels + float u = column * CHAR_WIDTH; + float v = row * CHAR_HEIGHT; + + // Offset from the left + drawX -= charInf.getLeftOffset() * relativeSize; + + float posY = shift + drawY; + + fontShader.uvBase.load(u, font.getTextureHeight() - v - CHAR_HEIGHT); + fontShader.uvDelta.load(CHAR_WIDTH, CHAR_HEIGHT); + + Matrix4f transformation = TransformationMatrix.create(new Vector3f(drawX, posY, 0), new Vector3f(0, 0, 0), new Vector3f(charWidth, charHeight, 0)); + + fontShader.transformationMatrix.load(transformation); + + charVAO.draw(DrawMode.TRIANGLES); + + drawX += charWidth; + drawX -= charInf.getRightOffset() * relativeSize; + drawX += relativeSize; + } + + italic(false); + } + + public static void color(Object color) + { + color(color, false); + } + + public static void color(Object color, boolean darker) + { + float dark = 0; + + if (darker) + { + dark = 0.35f; + } + + if (color instanceof float[]) + { + float[] c = (float[]) color; + + if (c.length == 4) + { + recolor(c[0] - dark, c[1] - dark, c[2] - dark, c[3]); + } + else if (c.length == 3) + { + recolor(c[0] - dark, c[1] - dark, c[2] - dark, 1); + } + } + + if (color instanceof String) + { + String col = (String) color; + + if (col.length() == 7) + { + recolor(Integer.valueOf(col.substring(1, 3), 16) / 256f - dark, Integer.valueOf(col.substring(3, 5), 16) / 256f - dark, Integer.valueOf(col.substring(5, 7), 16) / 256f - dark, 1); + } + + if (col.length() == 10) + { + recolor(Integer.valueOf(col.substring(4, 6), 16) / 256f - dark, Integer.valueOf(col.substring(6, 8), 16) / 256f - dark, Integer.valueOf(col.substring(8, 10), 16) / 256f - dark, Integer.valueOf(col.substring(2, 4), 16) / 256f); + } + } + } + + private static void recolor(float r, float g, float b, float a) + { + fontShader.recolor.load(r, g, b, a); + } + + private static void italic(boolean useItalic) + { + fontShader.italic.load(useItalic); + } + + public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size) + { + drawString(x, y, text, new float[] { r, g, b, a }, size, "default", false); + } + + public static void drawString(float x, float y, Object text) + { + drawString(x, y, text, new float[] { 0, 0, 0, 1 }, 1, "default", false); + } + + public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, boolean isShadow) + { + drawString(x, y, text, new float[] { r, g, b, a }, size, "default", isShadow); + } + + public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname, boolean isShadow) + { + drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, isShadow); + } + + public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname) + { + drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, false); + } + + public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname) + { + drawString(xPos, yPos, string, color, relativeSize, fontname, false); + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer2.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer2.java new file mode 100644 index 0000000..da3a55e --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/font/FontRenderer2.java @@ -0,0 +1,106 @@ +package cz.tefek.pluto.engine.gui.font; + +import org.joml.Vector2fc; + +import cz.tefek.pluto.engine.gui.IGUIPipeline; +import cz.tefek.pluto.engine.gui.IGUIRenderer; + +public class FontRenderer2 implements IGUIRenderer +{ + private static final FontRenderer2 INSTANCE_IMMEDIATE = new FontRenderer2(); + private static final FontRenderer2 INSTANCE_DEFERRED = new FontRenderer2(); + + private final IGUIPipeline deferPipeline; + + private static final float DEFAULT_SIZE = 24; + + private float x; + private float y; + private String drawnText; + private float size = DEFAULT_SIZE; + + private FontRenderer2(IGUIPipeline deferPipeline) + { + this.deferPipeline = deferPipeline; + } + + private FontRenderer2() + { + this.deferPipeline = null; + } + + public FontRenderer2 at(float x, float y) + { + this.x = x; + this.y = y; + + return this; + } + + public FontRenderer2 at(Vector2fc pos) + { + this.x = pos.x(); + this.y = pos.y(); + + return this; + } + + public FontRenderer2 size(float size) + { + this.size = size; + + return this; + } + + public FontRenderer2 string(Object text) + { + this.drawnText = String.valueOf(text); + + return this; + } + + public FontRenderer2 fstring(String format, Object... items) + { + this.drawnText = String.format(format, items); + + return this; + } + + public FontRenderer2 reset() + { + this.size = DEFAULT_SIZE; + this.drawnText = ""; + this.x = 0; + this.y = 0; + + return this; + } + + @Override + public void flush() + { + if (this.deferPipeline != null) + { + // Defer rendering to the pipeline + } + else + { + // Draw in immediate mode + } + } + + public static FontRenderer2 immediate() + { + return INSTANCE_IMMEDIATE.reset(); + } + + public static FontRenderer2 deferred() + { + return INSTANCE_DEFERRED.reset(); + } + + public static FontRenderer2 draw() + { + return deferred(); + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/BasicGUIPipeline.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/BasicGUIPipeline.java new file mode 100644 index 0000000..3c50cde --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/BasicGUIPipeline.java @@ -0,0 +1,12 @@ +package cz.tefek.pluto.engine.gui.pipeline; + +import cz.tefek.pluto.engine.gui.IGUIPipeline; + +public class BasicGUIPipeline implements IGUIPipeline +{ + @Override + public void flush() + { + + } +} diff --git a/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/EnumGUIPipelineCommand.java b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/EnumGUIPipelineCommand.java new file mode 100644 index 0000000..c79db60 --- /dev/null +++ b/plutogui/src/main/java/cz/tefek/pluto/engine/gui/pipeline/EnumGUIPipelineCommand.java @@ -0,0 +1,12 @@ +package cz.tefek.pluto.engine.gui.pipeline; + +public enum EnumGUIPipelineCommand +{ + MOVE_XY, + SCALE_XY, + MOVE_UV, + SCALE_UV, + ROTATE, + SET_TEXTURE, + DRAW; +} diff --git a/plutoio2/pom.xml b/plutoio2/pom.xml new file mode 100644 index 0000000..e4f5673 --- /dev/null +++ b/plutoio2/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + cz.tefek + plutoio2 + 0.2 + plutoio2 + + 12 + 12 + UTF-8 + UTF-8 + 3.2.3 + + + + lwjgl-natives-linux-amd64 + + + unix + amd64 + + + + natives-linux + + + + lwjgl-natives-macos-amd64 + + + mac + amd64 + + + + natives-macos + + + + lwjgl-natives-windows-amd64 + + + windows + amd64 + + + + natives-windows + + + + lwjgl-natives-windows-x86 + + + windows + x86 + + + + natives-windows-x86 + + + + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + + + commons-io + commons-io + 2.6 + + + com.google.code.gson + gson + 2.8.5 + + + + com.google.guava + guava + 28.0-jre + + + + org.apache.commons + commons-lang3 + 3.9 + + + \ No newline at end of file diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/Resource.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/Resource.java new file mode 100644 index 0000000..2b205c5 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/Resource.java @@ -0,0 +1,33 @@ +package cz.tefek.io.asl.resource; + +/** + * Allows loading a resource from the file system location defined by the + * supplied {@link ResourceAddress}. + * + * For example Resource<String> means you have a + * Resource that will output a String when loaded. + * + * @author 493msi + * + * @param R The type of the loaded Resource. + */ +public abstract class Resource +{ + protected ResourceAddress address; + protected boolean virtual; + + protected R value; + + public Resource(ResourceAddress raddress, boolean virtual) + { + this.address = raddress; + this.virtual = virtual; + } + + public R load() + { + return this.virtual ? this.value : this.loadFromFile(); + } + + protected abstract R loadFromFile(); +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddress.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddress.java new file mode 100644 index 0000000..74f9112 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddress.java @@ -0,0 +1,317 @@ +package cz.tefek.io.asl.resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import java.nio.file.FileSystems; +import java.nio.file.Path; + +import cz.tefek.io.modloader.ModLoaderCore; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; + +/** + * Resource address is a universal key for all resource and file loading. You + * just need a {@link ResourceSubscriber} (which holds the root folder location) + * and a {@link String} containing the address. The address itself works like a + * Java package. For example "sample.textures.test" formats as + * [root_folder]/sample/textures/test when converted using + * toPath(). To define a file extension for your address, use the + * fileExtension(String) method. To remove the file extension use + * fileExtension(null). + * + * @author 493msi + * + * @see ResourceSubscriber + */ +public class ResourceAddress +{ + public static final int LIMIT = 32; + public static final int MAX_BRANCH_STRING_LENGTH = 32; + + public static final String BRANCH_STRING_PATTERN = "[a-zA-Z0-9_]+"; + public static final String FILE_EXTENSION_PATTERN = "[a-zA-Z0-9_]+"; + + public static final String RESOURCE_ADDRESS_STRING_PATTERN = "^(" + ModLoaderCore.MOD_ID_STRING_PATTERN + ")\\$((?:" + BRANCH_STRING_PATTERN + "\\.)*?" + BRANCH_STRING_PATTERN + "?)(?:#([a-zA-Z0-9_]+))?$"; + public static final Pattern RESOURCE_ADDRESS_PATTERN = Pattern.compile(RESOURCE_ADDRESS_STRING_PATTERN, Pattern.CASE_INSENSITIVE); + + protected final List subAddress = new ArrayList<>(LIMIT); + protected ResourceSubscriber resSubscriber; + + protected String fileExtension = null; + + /** + * Copy constructor for internal use. + */ + protected ResourceAddress(ResourceSubscriber subscriber, List subAddr, String fileExtension) + { + this.resSubscriber = subscriber; + + this.subAddress.addAll(subAddr); + + this.fileExtension(fileExtension); + } + + public ResourceAddress(ResourceSubscriber sub, String address) + { + if (sub == null) + { + throw new IllegalArgumentException("Empty resource subscriber is not allowed."); + } + + this.resSubscriber = sub; + + var parts = address.split("#"); + + if (parts.length == 2) + { + address = parts[0]; + this.fileExtension(parts[1]); + } + else if (parts.length > 2) + { + throw new IllegalArgumentException("Illegal ResourceAddress component count!"); + } + + if (address == null) + { + throw new IllegalArgumentException("Empty address is not allowed."); + } + + String[] as = address.split("\\."); + + if (as.length == 0) + { + throw new IllegalArgumentException("Zero length address is not allowed."); + } + + if (as.length >= LIMIT) + { + throw new IllegalArgumentException("Address can't branch deeper more than " + (LIMIT - 1) + " times."); + } + + if (as.length == 1) + { + Logger.log(Severity.WARNING, "Please do not use tier 1 addresses, so it doesn't conflict with core assets."); + } + + for (int i = 0; i < as.length; i++) + { + var branch = as[i]; + + if (branch.length() < 1 || branch.length() > MAX_BRANCH_STRING_LENGTH) + { + throw new IllegalArgumentException("Length of branch must be higher than 0 and lower than 33, this is not an essay."); + } + + if (!branch.matches("^[a-zA-Z0-9_]+$")) + { + throw new IllegalArgumentException("Wrong address branch format: " + branch); + } + + this.subAddress.add(branch); + } + } + + public static ResourceAddress parse(String strVal) + { + if (strVal == null) + { + return null; + } + + if (!strVal.matches(RESOURCE_ADDRESS_STRING_PATTERN)) + { + throw new IllegalArgumentException("Malformed resource address: " + strVal); + } + + var addressComponents = strVal.split("[$#]"); + + if (addressComponents.length < 2 || addressComponents.length > 3) + { + throw new IllegalArgumentException("Illegal ResourceAddress component count!"); + } + + var mod = ModLoaderCore.getModByID(addressComponents[0]); + + if (mod == null) + { + throw new IllegalArgumentException(String.format("Mod with ID %s not found!", addressComponents[0])); + } + + var raddress = new ResourceAddress(mod.getDefaultResourceSubscriber(), addressComponents[1]); + + if (addressComponents.length == 3) + { + raddress.fileExtension(addressComponents[2]); + } + + return raddress; + } + + public ResourceAddress fileExtension(String ext) + { + if (ext == null || ext == "") + { + this.fileExtension = null; + return this; + } + + if (!ext.matches(FILE_EXTENSION_PATTERN)) + { + throw new IllegalArgumentException("@ResourceAddress.fileExtension: Wrong file extension format. Only english alphabet, numbers and underscore are permitted."); + } + + this.fileExtension = ext; + return this; + } + + public boolean hasFileExtension() + { + if (this.fileExtension == null) + { + return false; + } + + return !this.fileExtension.isBlank(); + } + + public String getFileExtension() + { + return this.fileExtension; + } + + public ResourceAddress branch(String branch) + { + if (branch == null) + { + throw new NullPointerException("@ResourceAddress.branch: INPUT = NULL!"); + } + + if (branch.length() < 1 || branch.length() > MAX_BRANCH_STRING_LENGTH) + { + throw new IllegalArgumentException("@ResourceAddress.branch: Length of branch must be higher than 0 and lower than " + (MAX_BRANCH_STRING_LENGTH + 1) + "."); + } + + if (!branch.matches(BRANCH_STRING_PATTERN)) + { + throw new IllegalArgumentException("@ResourceAddress.branch: Wrong branch format. Only english alphabet, numbers and underscore are permitted."); + } + + if (this.subAddress.size() >= LIMIT) + { + throw new IllegalArgumentException("@ResourceAddress.branch: Address can't branch deeper more than " + (LIMIT - 1) + " times."); + } + + this.subAddress.add(branch); + + return this; + } + + @Override + public String toString() + { + StringBuilder sbPath = new StringBuilder(this.resSubscriber.getMod().getModID()); + + sbPath.append("$"); + + sbPath.append(this.subAddressToString()); + + String path = sbPath.toString(); + + return path; + } + + public ResourceAddress copy() + { + return new ResourceAddress(this.resSubscriber, this.subAddress, this.fileExtension); + } + + public String toPath() + { + StringBuilder sbPath = new StringBuilder(this.resSubscriber.getRootPath()); + + for (String branch : this.subAddress) + { + if (branch == null) + { + break; + } + + sbPath.append("/"); + sbPath.append(branch); + } + + if (this.hasFileExtension()) + { + sbPath.append("." + this.fileExtension); + } + + String path = sbPath.toString(); + + return path; + } + + public String subAddressToString() + { + StringBuilder sbPath = new StringBuilder(); + + boolean firstLoop = true; + + for (String branch : this.subAddress) + { + if (branch == null) + { + break; + } + + if (!firstLoop) + { + sbPath.append("."); + } + else + { + firstLoop = false; + } + + sbPath.append(branch); + } + + if (this.hasFileExtension()) + { + sbPath.append("#"); + sbPath.append(this.fileExtension); + } + + return sbPath.toString(); + } + + public Path toNIOPath() + { + var pathBuilder = new StringBuilder(this.resSubscriber.getRootPath()); + final var separator = FileSystems.getDefault().getSeparator(); + pathBuilder.append(separator); + pathBuilder.append(this.subAddress.stream().collect(Collectors.joining(separator))); + + if (this.hasFileExtension()) + { + pathBuilder.append('.'); + pathBuilder.append(this.fileExtension); + } + + return Path.of(pathBuilder.toString()); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.fileExtension == null) ? 0 : this.fileExtension.hashCode()); + result = prime * result + this.resSubscriber.hashCode(); + result = prime * result + this.subAddress.hashCode(); + return result; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddressTypeAdapter.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddressTypeAdapter.java new file mode 100644 index 0000000..b785d92 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceAddressTypeAdapter.java @@ -0,0 +1,24 @@ +package cz.tefek.io.asl.resource; + +import java.io.IOException; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +public class ResourceAddressTypeAdapter extends TypeAdapter +{ + @Override + public void write(JsonWriter out, ResourceAddress value) throws IOException + { + out.value(String.valueOf(value)); + } + + @Override + public ResourceAddress read(JsonReader in) throws IOException + { + var strVal = in.nextString(); + return "null".equalsIgnoreCase(strVal) ? null : ResourceAddress.parse(strVal); + } + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceHelper.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceHelper.java new file mode 100644 index 0000000..75b2609 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceHelper.java @@ -0,0 +1,12 @@ +package cz.tefek.io.asl.resource; + +/** + * Doesn't do much right now. Just holds the default resource location. + * + * @author 493msi + */ +public class ResourceHelper +{ + public static final String GLOBAL_ROOT = ""; + public static final String DEFAULT_RESOURCE_ROOT = GLOBAL_ROOT + "data"; +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceSubscriber.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceSubscriber.java new file mode 100644 index 0000000..bf801b5 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/ResourceSubscriber.java @@ -0,0 +1,47 @@ +package cz.tefek.io.asl.resource; + +import cz.tefek.io.modloader.Mod; + +/** + * Allows access to resources using {@link ResourceAddress}. Requires a + * {@link Mod} instance to operate. + * + * @author 493msi + */ +public class ResourceSubscriber +{ + private Mod owner; + private String rootFolder; + + public ResourceSubscriber(Mod mod) + { + this(mod, ResourceHelper.DEFAULT_RESOURCE_ROOT); + } + + public ResourceSubscriber(Mod mod, String root) + { + if (mod == null) + { + throw new IllegalArgumentException("Mod can't be null!"); + } + + this.owner = mod; + this.rootFolder = root; + } + + public Mod getMod() + { + return this.owner; + } + + public String getRootPath() + { + return this.rootFolder; + } + + @Override + public int hashCode() + { + return this.owner.getModID().hashCode(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/IIdentifiable.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/IIdentifiable.java new file mode 100644 index 0000000..492109b --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/IIdentifiable.java @@ -0,0 +1,17 @@ +package cz.tefek.io.asl.resource.raid; + +import cz.tefek.io.asl.resource.ResourceAddress; + +public interface IIdentifiable +{ + ResourceAddress getID(); + + default String getStringID() + { + return this.getID().toString(); + } + + int getRAID(); + + void onRegister(int raid); +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/RAID.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/RAID.java new file mode 100644 index 0000000..5455e66 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/raid/RAID.java @@ -0,0 +1,61 @@ +package cz.tefek.io.asl.resource.raid; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Runtime Assigned ID (or Resource Address ID) + */ +public class RAID implements Iterable +{ + private static final int INITIAL_SIZE = 512; + + private List raid; + + private Map reverseRaid; + + public RAID() + { + this.raid = new ArrayList<>(INITIAL_SIZE); + this.reverseRaid = new HashMap<>(); + } + + public void register(E item) + { + var address = item.getStringID(); + + if (this.reverseRaid.containsKey(address)) + { + throw new IllegalArgumentException("Cannot register two items with the same resource ID!"); + } + + var pos = this.raid.size(); + this.raid.add(item); + this.reverseRaid.put(address, pos); + item.onRegister(pos); + } + + public E getByIntID(int id) + { + if (id < 0 || id >= this.raid.size()) + { + return null; + } + + return this.raid.get(id); + } + + public int getIDOf(E item) + { + return this.reverseRaid.get(item.getStringID()); + } + + @Override + public Iterator iterator() + { + return this.raid.iterator(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceImage.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceImage.java new file mode 100644 index 0000000..0c2f16a --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceImage.java @@ -0,0 +1,59 @@ +package cz.tefek.io.asl.resource.type; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import cz.tefek.io.asl.resource.Resource; +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.asl.resource.ResourceHelper; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; + +/** + * {@link ResourceAddress} in, {@link BufferedImage} out. + * + * @author 493msi + */ +public class ResourceImage extends Resource +{ + public ResourceImage(ResourceAddress raddress, boolean virtual) + { + super(raddress, virtual); + } + + public ResourceImage(ResourceAddress raddress) + { + super(raddress, false); + } + + @Override + public BufferedImage loadFromFile() + { + try + { + return ImageIO.read(new File(this.address.toPath())); + } + catch (IOException e) + { + Logger.log(Severity.ERROR, "Could not load BufferedImage: " + this.address.toString() + ", will load placeholder."); + Logger.logException(e); + + try + { + return ImageIO.read(new File(ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png")); + } + catch (IOException e1) + { + Logger.log(Severity.ERROR, "Placeholder BufferedImage not found: " + ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png"); + Logger.log("This is not good! :C"); + + Logger.logException(e1); + } + + return null; + } + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceInputStream.java b/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceInputStream.java new file mode 100644 index 0000000..8cdc98b --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/resource/type/ResourceInputStream.java @@ -0,0 +1,40 @@ +package cz.tefek.io.asl.resource.type; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import cz.tefek.io.asl.resource.Resource; +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; + +/** + * {@link ResourceAddress} in, {@link InputStream} out. + * + * @author 493msi + */ +public class ResourceInputStream extends Resource +{ + public ResourceInputStream(ResourceAddress raddress) + { + super(raddress, false); + } + + @Override + protected InputStream loadFromFile() + { + try + { + return new FileInputStream(this.address.toPath()); + } + catch (IOException e) + { + Logger.log(Severity.EXCEPTION, "Failed to open " + this.address + "!"); + Logger.log(Severity.EXCEPTION, e); + } + + return null; + } + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextIn.java b/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextIn.java new file mode 100644 index 0000000..6815155 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextIn.java @@ -0,0 +1,78 @@ +package cz.tefek.io.asl.textio; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.pluto.debug.Logger; + +/** + * A simple text file reader. Apart from generic methods of loading, you can use + * a {@link ResourceAddress}. For writing use {@link TextOut}. + * + * @author 493msi + */ +public class TextIn +{ + public static String load(URL url) + { + try + { + load(url.toURI()); + } + catch (URISyntaxException e) + { + Logger.logException(e); + } + + return null; + } + + public static String load(URI uri) + { + try + { + return Files.readString(Paths.get(uri)); + } + catch (Exception e) + { + Logger.logException(e); + } + + return null; + } + + public static String load(Path path) + { + try + { + return Files.readString(path); + } + catch (Exception e) + { + Logger.logException(e); + } + + return null; + } + + public static String loadInternal(String filename) + { + return load(TextIn.class.getResource("/" + filename)); + } + + public static String loadExternal(String filename) + { + return load(new File(filename).toURI()); + } + + public static String fromAddress(ResourceAddress address) + { + return load(address.toNIOPath()); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextOut.java b/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextOut.java new file mode 100644 index 0000000..2b8dd9e --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/asl/textio/TextOut.java @@ -0,0 +1,28 @@ +package cz.tefek.io.asl.textio; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +/** + * Simplifies text writer creation. For reading use {@link TextIn}. + * + * @author 493msi + */ + +public class TextOut +{ + public static PrintStream createPrintStream(String filePath) throws IOException + { + PrintStream printstream = new PrintStream(createFOStream(filePath)); + + return printstream; + } + + public static FileOutputStream createFOStream(String filePath) throws IOException + { + FileOutputStream fos = new FileOutputStream(filePath); + + return fos; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/Mod.java b/plutoio2/src/main/java/cz/tefek/io/modloader/Mod.java new file mode 100644 index 0000000..a627db2 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/Mod.java @@ -0,0 +1,141 @@ +package cz.tefek.io.modloader; + +import cz.tefek.io.asl.resource.ResourceSubscriber; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.pp.PlutoPackage; + +/** + * Mod object. Can be used to create a {@link ResourceSubscriber}. + * {@link ModLoaderCore} automatically creates a Mod object for each class + * annotated by {@link ModEntry @ModEntry} (assuming it is registered or class + * loaded by ModClassLoader, which auto-detects and registers {@link ModEntry + * ModEntries}). + * + * @see PlutoModLoader tutorial for + * more information. + * + * @author 493msi + */ +public class Mod extends PlutoPackage +{ + private Class mainClass; + private Object instance; + + private boolean clientSupport; + private boolean serverSupport; + + private ResourceSubscriber defaultResourceSubscriber; + + private String rootDataFolder; + + Mod(Class mainClass, String rootDataFolder) + { + super(extractModID(mainClass)); + + if (mainClass != null) + { + ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class); + + if (modInterface != null) + { + this.mainClass = mainClass; + this.name = modInterface.displayName().isBlank() ? modInterface.modid() : modInterface.displayName(); + this.author = modInterface.author(); + this.version = modInterface.version(); + this.build = modInterface.build(); + this.earliestCompatibleBuild = modInterface.earliestCompatibleBuild(); + this.dependencies = modInterface.dependencies(); + this.description = modInterface.description(); + this.iconURL = modInterface.iconURL(); + + this.rootDataFolder = rootDataFolder; + this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder); + + this.clientSupport = modInterface.clientSupport(); + this.serverSupport = modInterface.serverSupport(); + } + } + } + + private static String extractModID(Class mainClass) + { + if (mainClass != null) + { + ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class); + + if (modInterface != null) + { + return modInterface.modid(); + } + } + + return null; + } + + Class getMainClass() + { + return this.mainClass; + } + + Object getClassInstance() throws ReflectiveOperationException + { + if (this.instance == null) + { + Logger.log("|Pluto Mod Loader| Loading mod instance: " + this.name); + this.instance = this.mainClass.getDeclaredConstructor().newInstance(); + } + + return this.instance; + } + + public String getModID() + { + return this.id; + } + + public String getModName() + { + return this.name; + } + + public String getModAuthor() + { + return this.author; + } + + public String getModVersion() + { + return this.version; + } + + public int getModBuild() + { + return this.build; + } + + public boolean isClientSupported() + { + return this.clientSupport; + } + + public boolean isServerSupported() + { + return this.serverSupport; + } + + public ResourceSubscriber getDefaultResourceSubscriber() + { + return this.defaultResourceSubscriber; + } + + public void setRootDataFolder(String rootDataFolder) + { + this.rootDataFolder = rootDataFolder; + this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder); + } + + public String getRootDataFolder() + { + return this.rootDataFolder; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/ModClassLoader.java b/plutoio2/src/main/java/cz/tefek/io/modloader/ModClassLoader.java new file mode 100644 index 0000000..d37c067 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/ModClassLoader.java @@ -0,0 +1,152 @@ +package cz.tefek.io.modloader; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import cz.tefek.io.asl.resource.ResourceHelper; +import cz.tefek.io.pluto.debug.Logger; + +/** + * Class-loads all valid mods. The only requirement for the mod is to have a + * mod.jar file inside the base folder (for example + * mods/spaghetti/mod.jar). + * + * @author 493msi + */ +public class ModClassLoader +{ + public static ArrayList mods; + + public static void loadJars() + { + Logger.log("[Pluto Mod Loader] Looking for installed mods."); + + File modDir = new File(ResourceHelper.GLOBAL_ROOT + "mods/"); + + if (!modDir.exists()) + { + modDir.mkdir(); + + Logger.log("[Pluto Mod Loader] No mod found."); + + return; + } + + mods = new ArrayList<>(Arrays.asList(modDir.list())); + + if (mods.size() == 0) + { + Logger.log("[Pluto Mod Loader] No mod found."); + } + else + { + Logger.log("[Pluto Mod Loader] Found " + mods.size() + " mod(s) to load."); + + try + { + loadAll(); + } + catch (Exception e) + { + Logger.logException(e); + } + } + } + + private static void loadAll() throws Exception + { + int i = 0; + + if (mods.size() > 0) + { + if (mods.size() == 1) + { + Logger.log("[Pluto Mod Loader] There is one mod to load."); + } + else + { + Logger.log("[Pluto Mod Loader] There are " + mods.size() + " mods to load."); + } + + for (String modname : mods) + { + var modFolder = ResourceHelper.GLOBAL_ROOT + "mods/" + modname; + var dataDir = modFolder + "/data"; + + if (new File(modFolder + "/mod.jar").exists()) + { + var dataDirFile = new File(dataDir); + + if (!dataDirFile.isDirectory()) + { + dataDirFile.mkdirs(); + } + + String pathToJar = modFolder + "/mod.jar"; + + JarFile jarFile = new JarFile(pathToJar); + Enumeration e = jarFile.entries(); + + URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; + + URLClassLoader sysLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader()); + + while (e.hasMoreElements()) + { + JarEntry je = e.nextElement(); + + if (je.isDirectory()) + { + continue; + } + + // Not sure what to do with non-java files. + // They are ignored (for now). + if (!je.getName().endsWith(".class")) + { + continue; + } + + String className = je.getName().replaceAll("\\.class$", "").replace('/', '.'); + + Class modClass = sysLoader.loadClass(className); + + if (modClass.getDeclaredAnnotation(ModEntry.class) != null) + { + ModLoaderCore.registerMod(modClass, dataDir); + } + } + + jarFile.close(); + + Logger.log("[Pluto Mod Loader] Loaded mod jar file: " + modname + "/mod.jar"); + i++; + } + else + { + Logger.log("[Pluto Mod Loader] Failed to load mod jar file: " + modname + "."); + Logger.log("[Pluto Mod Loader] Reason: Missing mod.jar file."); + } + } + + if (i < 1 || i == 0) + { + System.out.println("[Pluto Mod Loader] Loading mods complete, loaded " + i + " mods."); + } + else + { + System.out.println("[Pluto Mod Loader] Loading mods complete, loaded " + i + " mod."); + } + } + else + { + Logger.log("[Pluto Mod Loader] There aren't any mods, skipping initialising phase."); + } + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/ModEntry.java b/plutoio2/src/main/java/cz/tefek/io/modloader/ModEntry.java new file mode 100644 index 0000000..0e84a60 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/ModEntry.java @@ -0,0 +1,39 @@ +package cz.tefek.io.modloader; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The heart of any Pluto mod. Annotate your class with this, so the + * PlutoModLoader can load it. The class must be directly registered or + * processed by {@link ModClassLoader} (as an external mod). + * + * @see PlutoModLoader tutorial for + * more information. + * + * @author 493msi + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface ModEntry { + String modid(); + + String displayName() default ""; + + String author() default "anonymous author"; + + String version() default "unknown version"; + + Class[] dependencies() default {}; + + String iconURL() default ""; + + String description() default "No description available"; + + int build() default 0; + + int earliestCompatibleBuild() default 0; + + boolean clientSupport() default true; + + boolean serverSupport() default false; +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/ModInstaller.java b/plutoio2/src/main/java/cz/tefek/io/modloader/ModInstaller.java new file mode 100644 index 0000000..94ef7f3 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/ModInstaller.java @@ -0,0 +1,117 @@ +package cz.tefek.io.modloader; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import cz.tefek.io.asl.resource.ResourceHelper; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; + +/** + * Unzips mod packages from the packages folder into the mods folder. WIP + * + * @author 493msi + */ +public class ModInstaller +{ + public static void unpackNewMods() + { + Logger.log("[Pluto Mod Loader] Looking for new mod packages to install."); + + File f = new File(ResourceHelper.GLOBAL_ROOT + "packages/"); + if (!f.exists()) + { + f.mkdir(); + + Logger.log("[Pluto Mod Loader] Package folder does not exist, creating it and aborting unpacking."); + + return; + } + + ArrayList files = new ArrayList(Arrays.asList(f.list())); + + if (files.size() == 0) + { + Logger.log("[Pluto Mod Loader] No mod package found."); + } + else + { + Logger.log("[Pluto Mod Loader] Found " + files.size() + " mod packages."); + + for (String file : files) + { + Logger.log("[Pluto Mod Loader] Mod package found: " + file + ", installing it."); + + try + { + extract(file); + } + catch (IOException e) + { + Logger.log(Severity.ERROR, "Unpacking of " + file + " failed!"); + Logger.logException(e); + } + + new File(ResourceHelper.GLOBAL_ROOT + "packages/" + file).delete(); + } + } + } + + private static void extract(String filepath) throws IOException + { + byte[] buffer = new byte[2048]; + + InputStream fileInput = null; + fileInput = new FileInputStream(ResourceHelper.GLOBAL_ROOT + "packages/" + filepath); + + ZipInputStream stream = new ZipInputStream(fileInput); + String outdir = ResourceHelper.GLOBAL_ROOT + "mods/"; + + if (!new File(outdir).exists()) + { + new File(outdir).mkdir(); + } + + try + { + String filename = filepath.replaceAll("\\.zip$", ""); + + new File(outdir + filename).mkdir(); + + ZipEntry entry; + while ((entry = stream.getNextEntry()) != null) + { + String outpath = outdir + filename + "/" + entry.getName(); + FileOutputStream output = null; + try + { + output = new FileOutputStream(outpath); + int len = 0; + while ((len = stream.read(buffer)) > 0) + { + output.write(buffer, 0, len); + } + } + finally + { + if (output != null) + { + output.close(); + } + } + } + } + finally + { + stream.close(); + } + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoaderCore.java b/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoaderCore.java new file mode 100644 index 0000000..3f1d704 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoaderCore.java @@ -0,0 +1,270 @@ +package cz.tefek.io.modloader; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import cz.tefek.io.asl.resource.ResourceHelper; +import cz.tefek.io.modloader.event.ModLoad; +import cz.tefek.io.modloader.event.ModLoadEvent; +import cz.tefek.io.modloader.event.ModPostLoad; +import cz.tefek.io.modloader.event.ModPostLoadEvent; +import cz.tefek.io.modloader.event.ModPreLoad; +import cz.tefek.io.modloader.event.ModPreLoadEvent; +import cz.tefek.io.modloader.event.ModUnload; +import cz.tefek.io.modloader.event.ModUnloadEvent; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEventManager; + +public class ModLoaderCore +{ + public static final String MOD_ID_STRING_PATTERN = "[a-z0-9_]+"; + public static final String FULL_MOD_ID_STRING_PATTERN = "^" + MOD_ID_STRING_PATTERN + "$"; + + static ModLoadingPhase loadingPhase = ModLoadingPhase.WAITING; + + private static final List modArchive = new ArrayList<>(); + + private static final LinkedList loadBuffer = new LinkedList<>(); + + public static void registerMod(Class modClass, String modDataRoot) + { + if (loadingPhase != ModLoadingPhase.WAITING && loadingPhase != ModLoadingPhase.CLASSLOADING) + { + Logger.log(SmartSeverity.ERROR, "Cannot register mod during loading phase " + loadingPhase.name() + "!"); + return; + } + + if (getModByMainClass(modClass) != null) + { + Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Mod class " + modClass.getCanonicalName() + " is already registered, skipping it."); + return; + } + + if (modDataRoot == null) + { + modDataRoot = ResourceHelper.DEFAULT_RESOURCE_ROOT; + } + + var registeredMod = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(modClass)).findAny(); + + if (registeredMod.isPresent()) + { + if (modDataRoot != null) + { + var mod = registeredMod.get(); + + mod.setRootDataFolder(modDataRoot); + } + + return; + } + + Mod mod = new Mod(modClass, modDataRoot); + + if (!mod.getModID().matches(FULL_MOD_ID_STRING_PATTERN)) + { + Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Modid " + mod.getModID() + " contains invalid characters (or none at all), mod will not be loaded."); + Logger.log(SmartSeverity.WARNING, "[Pluto Mod Loader] Only lowercase letters (a to z) and numbers (0 to 9) are allowed characters."); + return; + } + + var deps = mod.getDependencies(); + + for (var dependency : deps) + { + var registeredDependency = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(dependency)).findAny(); + + if (registeredDependency.isPresent()) + { + continue; + } + + registerMod(dependency); + } + + loadBuffer.add(mod); + } + + public static void registerMod(Class modClass) + { + registerMod(modClass, null); + } + + public static List getAllMods() + { + return Collections.unmodifiableList(modArchive); + } + + public static Mod getModByID(String id) + { + for (Mod mod : modArchive) + { + if (mod.getModID().equals(id)) + { + return mod; + } + } + + return null; + } + + private static Mod getModByMainClass(Class modClass) + { + for (Mod mod : modArchive) + { + if (modClass.equals(mod.getMainClass())) + { + return mod; + } + } + + return null; + } + + public static void loadProcedure() + { + loadingPhase = ModLoadingPhase.PREPARING; + + StaticPlutoEventManager.registerEvent(ModPreLoad.class); + StaticPlutoEventManager.registerEvent(ModLoad.class); + StaticPlutoEventManager.registerEvent(ModPostLoad.class); + StaticPlutoEventManager.registerEvent(ModUnload.class); + + Logger.log("[Pluto Mod Loader] Number of manually added mods: " + modArchive.size()); + loadingPhase = ModLoadingPhase.UPACKING; + ModInstaller.unpackNewMods(); + loadingPhase = ModLoadingPhase.CLASSLOADING; + ModClassLoader.loadJars(); + loadingPhase = ModLoadingPhase.INITIALIZING; + + while (loadBuffer.peek() != null) + { + var mod = loadBuffer.removeFirst(); + StaticPlutoEventManager.registerEventHandler(mod.getMainClass()); + modArchive.add(mod); + } + + if (!modArchive.isEmpty()) + { + Logger.log("[Pluto Mod Loader] Initializing mod(s)..."); + initMods(); + + if (loadingPhase == ModLoadingPhase.FINISHING) + { + Logger.log("[Pluto Mod Loader] Initializing mod(s) finished."); + } + else + { + Logger.log("[Pluto Mod Loader] Initializing mod(s) was canceled due to error(s)."); + } + } + } + + static void initMods() + { + while (loadingPhase != ModLoadingPhase.CANCELED && loadingPhase != ModLoadingPhase.FINISHING) + { + switch (loadingPhase) + { + case INITIALIZING: + preLoadMods(); + break; + case PRELOADING: + loadMods(); + break; + case LOADING: + postLoadMods(); + break; + case POSTLOADING: + complete(); + break; + default: + break; + } + } + } + + public static void unloadProcedure() + { + Logger.log("[Pluto Mod Loader] Unloading all mods."); + StaticPlutoEventManager.fireEvent(ModUnload.class, new ModUnloadEvent()); + modArchive.clear(); + } + + private static void preLoadMods() + { + loadingPhase = ModLoadingPhase.PRELOADING; + + try + { + ModPreLoadEvent preLoadData = new ModPreLoadEvent(); + preLoadData.mods = modArchive; + + StaticPlutoEventManager.fireEvent(ModPreLoad.class, preLoadData); + } + catch (Exception e) + { + Logger.log("[Pluto Mod Loader] Problem encountered while preloading mods."); + Logger.log("[Pluto Mod Loader] Mod loading stopped."); + + Logger.logException(e); + + cancelLoading(); + } + } + + private static void loadMods() + { + loadingPhase = ModLoadingPhase.LOADING; + + try + { + ModLoadEvent loadData = new ModLoadEvent(); + + StaticPlutoEventManager.fireEvent(ModLoad.class, loadData); + } + catch (Exception e) + { + Logger.log("[Pluto Mod Loader] Problem encountered while loading mods."); + Logger.log("[Pluto Mod Loader] Mod loading stopped."); + + Logger.logException(e); + + cancelLoading(); + } + } + + private static void postLoadMods() + { + loadingPhase = ModLoadingPhase.POSTLOADING; + + try + { + ModPostLoadEvent postLoadData = new ModPostLoadEvent(); + + StaticPlutoEventManager.fireEvent(ModPostLoad.class, postLoadData); + } + catch (Exception e) + { + Logger.log("[Pluto Mod Loader] Problem encountered while loading mods."); + Logger.log("[Pluto Mod Loader] Mod loading stopped."); + + Logger.logException(e); + + cancelLoading(); + } + } + + private static void complete() + { + loadingPhase = ModLoadingPhase.FINISHING; + } + + private static void cancelLoading() + { + loadingPhase = ModLoadingPhase.CANCELED; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoadingPhase.java b/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoadingPhase.java new file mode 100644 index 0000000..daa3145 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/ModLoadingPhase.java @@ -0,0 +1,17 @@ +package cz.tefek.io.modloader; + +public enum ModLoadingPhase +{ + UPACKING, + PREPARING, + INITIALIZING, + WAITING, + PRELOADING, + LOADING, + POSTLOADING, + FINISHING, + CANCELED, + INSTANTIATING, + CLASSLOADING, + UNLOADING; +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoad.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoad.java new file mode 100644 index 0000000..d43cf1f --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoad.java @@ -0,0 +1,21 @@ +package cz.tefek.io.modloader.event; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent; + +@Retention(RUNTIME) +@Target(METHOD) +@StaticPlutoEvent(passingParamClass = ModLoadEvent.class) +/** + * @author 493msi + * + */ +public @interface ModLoad +{ + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoadEvent.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoadEvent.java new file mode 100644 index 0000000..30cd90c --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModLoadEvent.java @@ -0,0 +1,15 @@ +package cz.tefek.io.modloader.event; + +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.pluto.eventsystem.EventData; + +/** + * Instances are passed to {@link ModEntry ModEntries} on load. Currently does + * nothing. + * + * @author 493msi + * + */ +public class ModLoadEvent extends EventData +{ +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoad.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoad.java new file mode 100644 index 0000000..c8bf14d --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoad.java @@ -0,0 +1,21 @@ +package cz.tefek.io.modloader.event; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent; + +@Retention(RUNTIME) +@Target(METHOD) +@StaticPlutoEvent(passingParamClass = ModPostLoadEvent.class) +/** + * @author 493msi + * + */ +public @interface ModPostLoad +{ + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoadEvent.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoadEvent.java new file mode 100644 index 0000000..93995f5 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPostLoadEvent.java @@ -0,0 +1,18 @@ +package cz.tefek.io.modloader.event; + +import java.util.ArrayList; +import java.util.List; + +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.pluto.eventsystem.EventData; + +/** + * Instances are passed to {@link ModEntry ModEntries} on post-load. + * + * @author 493msi + */ +public class ModPostLoadEvent extends EventData +{ + // TODO Cross-mod messaging + public final List crossMessages = new ArrayList(); +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoad.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoad.java new file mode 100644 index 0000000..a2ef67e --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoad.java @@ -0,0 +1,21 @@ +package cz.tefek.io.modloader.event; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent; + +@Retention(RUNTIME) +@Target(METHOD) +@StaticPlutoEvent(passingParamClass = ModPreLoadEvent.class) +/** + * @author 493msi + * + */ +public @interface ModPreLoad +{ + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoadEvent.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoadEvent.java new file mode 100644 index 0000000..701a57c --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModPreLoadEvent.java @@ -0,0 +1,19 @@ +package cz.tefek.io.modloader.event; + +import java.util.List; + +import cz.tefek.io.modloader.Mod; +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.pluto.eventsystem.EventData; + +/** + * Instances are passed to {@link ModEntry ModEntries} on load. Carries a list + * of all {@link Mod} objects. + * + * @author 493msi + * + */ +public class ModPreLoadEvent extends EventData +{ + public List mods; +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnload.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnload.java new file mode 100644 index 0000000..30bea11 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnload.java @@ -0,0 +1,20 @@ +package cz.tefek.io.modloader.event; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent; + +@Retention(RUNTIME) +@Target(METHOD) +@StaticPlutoEvent(passingParamClass = ModUnloadEvent.class) +/** + * @author 493msi + * + */ +public @interface ModUnload { + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnloadEvent.java b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnloadEvent.java new file mode 100644 index 0000000..f73aec8 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/modloader/event/ModUnloadEvent.java @@ -0,0 +1,15 @@ +package cz.tefek.io.modloader.event; + +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.pluto.eventsystem.EventData; + +/** + * Instances are passed to {@link ModEntry ModEntries} on mod unload. + * + * @author 493msi + * + */ +public class ModUnloadEvent extends EventData +{ + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/ISeverity.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/ISeverity.java new file mode 100644 index 0000000..ff0cd57 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/ISeverity.java @@ -0,0 +1,8 @@ +package cz.tefek.io.pluto.debug; + +public interface ISeverity +{ + String getDisplayName(); + + boolean isStdErr(); +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Logger.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Logger.java new file mode 100644 index 0000000..f1eca71 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Logger.java @@ -0,0 +1,98 @@ +package cz.tefek.io.pluto.debug; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import cz.tefek.io.asl.resource.ResourceHelper; +import cz.tefek.io.asl.textio.TextOut; + +/** + * Logger. 'nuff said. + * + * @author 493msi + * + */ +public class Logger +{ + static OutputStream stdout; + static OutputStream stderr; + + static FileOutputStream file_log; + + static PrintStream stdoutStream; + static PrintStream stderrStream; + + public static void setup() + { + stdout = new PrintStream(System.out); + stderr = new PrintStream(System.err); + + setupFileStream(); + + stdoutStream = new PrintStream(new StdOutSplitStream()); + stderrStream = new PrintStream(new StdErrSplitStream()); + + System.setOut(stdoutStream); + System.setErr(stderrStream); + } + + private static void setupFileStream() + { + try + { + if (!new File(ResourceHelper.GLOBAL_ROOT + "logs").exists() || !new File(ResourceHelper.GLOBAL_ROOT + "logs").isDirectory()) + { + new File(ResourceHelper.GLOBAL_ROOT + "logs").mkdirs(); + } + + file_log = TextOut.createFOStream(ResourceHelper.GLOBAL_ROOT + "logs/log" + System.currentTimeMillis() + ".txt"); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public static void close() + { + System.out.close(); + } + + public static void log(Object string) + { + log(Severity.NONE, string); + } + + public static void logf(String string, Object... o) + { + System.out.printf(string, o); + } + + public static void logNoLine(Object string) + { + System.out.print(string); + } + + public static void logException(Exception e) + { + e.printStackTrace(); + } + + public static void log(ISeverity s, Object string) + { + (s.isStdErr() ? System.err : System.out).println(s.getDisplayName() + string); + } + + public static void logf(ISeverity s, String string, Object... o) + { + (s.isStdErr() ? System.err : System.out).printf(s.getDisplayName() + string, o); + } + + public static void logNoLine(ISeverity s, Object string) + { + (s.isStdErr() ? System.err : System.out).print(s.getDisplayName() + string); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Severity.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Severity.java new file mode 100644 index 0000000..106a0cc --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/Severity.java @@ -0,0 +1,35 @@ +package cz.tefek.io.pluto.debug; + +/** + * Message severity. + * + * @author 493msi + */ +public enum Severity implements ISeverity +{ + INFO("[INFO] ", false), + WARNING("[WARNING] ", true), + ERROR("[ERROR] ", true), + EXCEPTION("[EXCEPTION] ", true), + NONE("", false); + + private String displayName; + private boolean usesStdErr; + + Severity(String name, boolean usesStdErr) + { + this.displayName = name; + } + + @Override + public String getDisplayName() + { + return this.displayName; + } + + @Override + public boolean isStdErr() + { + return this.usesStdErr; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/SmartSeverity.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/SmartSeverity.java new file mode 100644 index 0000000..cefe00b --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/SmartSeverity.java @@ -0,0 +1,38 @@ +package cz.tefek.io.pluto.debug; + +/** + * A more visual way to denote what's actually happening. + * + * @author 493msi + */ +public enum SmartSeverity implements ISeverity +{ + ADDED("[+] ", false), + REMOVED("[-] ", false), + ZERO("[0] ", false), + INFO("[i] ", false), + WARNING("[!] ", true), + ERROR("[X] ", true); + + private String displayName; + private boolean usesStdErr; + + SmartSeverity(String name, boolean usesStdErr) + { + this.displayName = name; + this.usesStdErr = usesStdErr; + } + + @Override + public String getDisplayName() + { + return this.displayName; + } + + @Override + public boolean isStdErr() + { + return this.usesStdErr; + } + +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdErrSplitStream.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdErrSplitStream.java new file mode 100644 index 0000000..977f34c --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdErrSplitStream.java @@ -0,0 +1,53 @@ +package cz.tefek.io.pluto.debug; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Splits one {@link OutputStream} into two to allow writing to both log file + * and console output. + * + * @author 493msi + * + */ +class StdErrSplitStream extends OutputStream +{ + public StdErrSplitStream() + { + + } + + @Override + public void write(int b) throws IOException + { + Logger.file_log.write(b); + Logger.stderr.write(b); + } + + @Override + public void write(byte[] b) throws IOException + { + Logger.file_log.write(b); + Logger.stderr.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + Logger.file_log.write(b, off, len); + Logger.stderr.write(b, off, len); + } + + @Override + public void flush() throws IOException + { + Logger.file_log.flush(); + Logger.stderr.flush(); + } + + @Override + public void close() throws IOException + { + Logger.file_log.close(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdOutSplitStream.java b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdOutSplitStream.java new file mode 100644 index 0000000..67f9c2b --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/debug/StdOutSplitStream.java @@ -0,0 +1,53 @@ +package cz.tefek.io.pluto.debug; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Splits one {@link OutputStream} into two to allow writing to both log file + * and console output. + * + * @author 493msi + * + */ +class StdOutSplitStream extends OutputStream +{ + public StdOutSplitStream() + { + + } + + @Override + public void write(int b) throws IOException + { + Logger.file_log.write(b); + Logger.stdout.write(b); + } + + @Override + public void write(byte[] b) throws IOException + { + Logger.file_log.write(b); + Logger.stdout.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + Logger.file_log.write(b, off, len); + Logger.stdout.write(b, off, len); + } + + @Override + public void flush() throws IOException + { + Logger.file_log.flush(); + Logger.stdout.flush(); + } + + @Override + public void close() throws IOException + { + Logger.file_log.close(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/pp/InvalidPlutoPackageException.java b/plutoio2/src/main/java/cz/tefek/io/pluto/pp/InvalidPlutoPackageException.java new file mode 100644 index 0000000..ac66e44 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/pp/InvalidPlutoPackageException.java @@ -0,0 +1,16 @@ +package cz.tefek.io.pluto.pp; + +/** + * Thrown when a provided package definition is for some reason not valid. + * + * @author 493msi + */ +public class InvalidPlutoPackageException extends Exception +{ + private static final long serialVersionUID = 7853852981742059946L; + + public InvalidPlutoPackageException(String message, Throwable e) + { + super(message, e); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/io/pluto/pp/PlutoPackage.java b/plutoio2/src/main/java/cz/tefek/io/pluto/pp/PlutoPackage.java new file mode 100644 index 0000000..310163f --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/io/pluto/pp/PlutoPackage.java @@ -0,0 +1,95 @@ +package cz.tefek.io.pluto.pp; + +public class PlutoPackage +{ + public final String id; + + protected String name; + protected String version; + protected int build; + protected String description; + protected String iconURL; + protected String author; + protected Class[] dependencies; + protected int earliestCompatibleBuild; + + public PlutoPackage(String id) + { + this.id = id; + } + + public String getName() + { + return this.name; + } + + void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return this.version; + } + + void setVersion(String version) + { + this.version = version; + } + + public int getBuild() + { + return this.build; + } + + void setBuild(int build) + { + this.build = build; + } + + public String getDescription() + { + return this.description; + } + + void setDescription(String description) + { + this.description = description; + } + + public String getIconURL() + { + return this.iconURL; + } + + void setIconURL(String iconURL) + { + this.iconURL = iconURL; + } + + public String getAuthor() + { + return this.author; + } + + void setAuthor(String author) + { + this.author = author; + } + + public Class[] getDependencies() + { + return this.dependencies; + } + + public boolean isBackwardsCompatibleWithBuild(int build) + { + return build > this.earliestCompatibleBuild; + } + + public boolean isMod() + { + return false; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/l10n/PlutoL10n.java b/plutoio2/src/main/java/cz/tefek/l10n/PlutoL10n.java new file mode 100644 index 0000000..fd76a6f --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/l10n/PlutoL10n.java @@ -0,0 +1,67 @@ +package cz.tefek.l10n; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +public class PlutoL10n +{ + private static Locale defaultLocale; + + private static Map> localizations = new HashMap<>(); + + public static void init(Locale locale) + { + Locale.setDefault(locale); + defaultLocale = locale; + + setLocale(defaultLocale); + + Logger.logf(SmartSeverity.INFO, "Default locale: %s\n", defaultLocale.toString()); + } + + public static void registerLocale(Locale locale) + { + if (localizations.containsKey(locale)) + { + return; + } + + localizations.put(locale, new HashMap<>()); + } + + public static void setLocale(Locale locale) + { + registerLocale(locale); + Locale.setDefault(locale); + } + + public static void map(Locale locale, String src, String dest) + { + registerLocale(locale); + localizations.get(locale).put(src, dest); + } + + public static String get(Locale locale, String src) + { + if (!localizations.containsKey(locale)) + { + if (!localizations.containsKey(defaultLocale)) + { + return src; + } + + return localizations.get(defaultLocale).getOrDefault(src, src); + } + + return localizations.get(locale).getOrDefault(src, src); + } + + public static String get(String src) + { + return get(Locale.getDefault(), src); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/EventData.java b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/EventData.java new file mode 100644 index 0000000..e67af4e --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/EventData.java @@ -0,0 +1,10 @@ +package cz.tefek.pluto.eventsystem; + +/** + * @author 493msi + * + */ +public class EventData +{ + +} diff --git a/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/lambda/LambdaEventFactory.java b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/lambda/LambdaEventFactory.java new file mode 100644 index 0000000..f878d1f --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/lambda/LambdaEventFactory.java @@ -0,0 +1,95 @@ +package cz.tefek.pluto.eventsystem.lambda; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A simple functional interface based event factory for objects basically + * implementing the Observer design pattern. + * + * @since 0.2 + * + * @author 493msi + */ +public class LambdaEventFactory +{ + /** + * A class representing a list of observing consumers. + * + * @param the type of the event data + * + * @since 0.2 + * + * @author 493msi + */ + public static class LambdaEvent + { + private List> consumers; + + private LambdaEvent() + { + this.consumers = new ArrayList<>(); + } + + /** + * Adds a new listener to observe this object. + * + * @param callback a functional interface representing a callback + * function + * + * @since 0.2 + * + * @author 493msi + */ + public void addListener(Consumer callback) + { + this.consumers.add(callback); + } + + /** + * Removes a calback from the list of observers. + * + * @param callback A functional interface representing a callback + * function + * + * @since 0.2 + * + * @author 493msi + */ + public void removeListener(Consumer callback) + { + this.consumers.remove(callback); + } + + /** + * Notifies all observers by invoking their callbacks with the specified + * value. + * + * @param value the data to distribute to all observers + * + * @since 0.2 + * + * @author 493msi + */ + public void fire(T value) + { + this.consumers.forEach(c -> c.accept(value)); + } + } + + /** + * + * @return A new observable {@link LambdaEvent} object + * + * @param the data type this event object will work with + * + * @since 0.2 + * + * @author 493msi + */ + public static LambdaEvent createEvent() + { + return new LambdaEvent<>(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEvent.java b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEvent.java new file mode 100644 index 0000000..4f498c4 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEvent.java @@ -0,0 +1,25 @@ +package cz.tefek.pluto.eventsystem.staticmode; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import cz.tefek.pluto.eventsystem.EventData; + +@Retention(RUNTIME) +@Target(ANNOTATION_TYPE) +/** + * @author 493msi + * + */ +public @interface StaticPlutoEvent +{ + /** + * This actually does nothing. ¯\_(ツ)_/¯ Well, you can use it for improved + * code readability. + * + */ + Class passingParamClass() default EventData.class; +} diff --git a/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEventManager.java b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEventManager.java new file mode 100644 index 0000000..56e818c --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/pluto/eventsystem/staticmode/StaticPlutoEventManager.java @@ -0,0 +1,228 @@ +package cz.tefek.pluto.eventsystem.staticmode; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; +import cz.tefek.pluto.eventsystem.EventData; + +/** + * A universal event manager. Register an event {@link Annotation} of your + * choice (must be annotated with {@link StaticPlutoEvent @Event}), then + * annotate some public static method and you are done! Now you can trigger the + * callbacks with ease. Multiple per-method events are possible! Note that + * event callbacks require a data-passing parameter + * (extends {@link EventData}). The method will not be invoked + * otherwise! + * + * @author 493msi + * + */ +public class StaticPlutoEventManager +{ + private static Map, List> eventRegistry = new HashMap, List>(); + private static List orphans = new ArrayList(); + + public static void registerEventHandler(Class clazz) + { + Method[] methods = clazz.getMethods(); + + int methodsFound = 0; + + for (Method method : methods) + { + // The callback method must be static and public! + if (!(Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers()))) + { + continue; + } + + for (Annotation annotation : method.getDeclaredAnnotations()) + { + if (annotation.annotationType().getAnnotation(StaticPlutoEvent.class) != null) + { + methodsFound++; + + var parentAnnotation = eventRegistry.get(annotation.annotationType()); + + // Find a parent annotation. + if (parentAnnotation != null) + { + parentAnnotation.add(method); + } + // No parent annotation and fix for methods with 2+ event + // annotations. + else if (!orphans.contains(method)) + { + orphans.add(method); + } + } + } + } + + Logger.log(Severity.INFO, "Event handler " + clazz.getCanonicalName() + " scan found " + methodsFound + " method callback(s)."); + } + + public static void registerEvent(Class annotation) + { + // @Event is necessary. + if (annotation.getAnnotation(StaticPlutoEvent.class) != null) + { + if (eventRegistry.containsKey(annotation)) + { + Logger.log(Severity.ERROR, "Annotation " + annotation.getCanonicalName() + " is already registered!"); + return; + } + else + { + eventRegistry.put(annotation, new ArrayList()); + + Logger.log(Severity.INFO, "Event " + annotation.getCanonicalName() + " successfully registered!"); + + short retroactivelyFound = 0; + + // Let's check all existing event Methods for this event. + for (Entry, List> entry : eventRegistry.entrySet()) + { + // Checking the Method list for this event would make no + // sense. + if (annotation.equals(entry.getKey())) + { + continue; + } + + for (Method method : entry.getValue()) + { + // Just in case. + if (method.isAnnotationPresent(annotation)) + { + eventRegistry.get(annotation).add(method); + retroactivelyFound++; + } + } + } + + Logger.log(Severity.INFO, "Retroactive method checking found " + retroactivelyFound + " item(s)."); + + // Let's check the Method orphanage for some potential + // candidates. + + short orphansFound = 0; + + int orphansBefore = orphans.size(); + + List foundParents = new ArrayList(); + + for (Method method : orphans) + { + if (method.isAnnotationPresent(annotation)) + { + foundParents.add(method); + + // No duplicates. + if (!eventRegistry.get(annotation).contains(method)) + { + eventRegistry.get(annotation).add(method); + orphansFound++; + } + } + } + + orphans.removeAll(foundParents); + + Logger.log(Severity.INFO, orphansFound + " orphan method(s) was/were bound and " + (orphansBefore - orphans.size()) + " removed from the storage!"); + } + } + else + { + Logger.log(Severity.ERROR, "Annotation " + annotation.getCanonicalName() + " is not annotated with @Event, can't register it."); + } + } + + public static void fireEvent(Class event, EventData data) + { + if (event.getAnnotation(StaticPlutoEvent.class) != null) + { + List methodList = eventRegistry.get(event); + + if (methodList != null) + { + for (Method m : methodList) + { + // If a method contains more than one parameter, the most + // viable one will be chosen. Also, methods with no + // parameters are not valid. + + Class[] params = m.getParameterTypes(); + + Class mostSuitableParam = null; + EventData[] paramOut = new EventData[params.length]; + + if (params.length == 0) + { + Logger.log(Severity.WARNING, "Method " + m.toGenericString() + " has no parameters, will not be invoked by event!"); + } + + for (int i = 0; i < params.length; i++) + { + Class parameter = params[i]; + + if (!EventData.class.isAssignableFrom(parameter)) + { + Logger.log(Severity.ERROR, "Method " + m.toGenericString() + " contains invalid parameters. Only EventData instances are permitted."); + mostSuitableParam = null; + break; + } + + if (mostSuitableParam == null && parameter.isInstance(data)) + { + mostSuitableParam = parameter; + paramOut[i] = data; + } + + if (parameter.isInstance(data) && mostSuitableParam.isAssignableFrom(parameter)) + { + mostSuitableParam = parameter; + paramOut = new EventData[params.length]; + paramOut[i] = data; + } + } + + if (mostSuitableParam != null) + { + try + { + m.invoke(null, (Object[]) paramOut); + } + catch (Exception e) + { + Logger.logException(e); + } + } + } + } + else + { + Logger.log(Severity.ERROR, "There is no event like " + event.getCanonicalName() + " registered."); + } + } + else + { + Logger.log(Severity.ERROR, event.getCanonicalName() + " is not an event!"); + } + } + + public static void unregisterAll() + { + eventRegistry.clear(); + orphans.clear(); + } +} diff --git a/plutoio2/src/main/java/cz/tefek/tpl/TPJImage.java b/plutoio2/src/main/java/cz/tefek/tpl/TPJImage.java new file mode 100644 index 0000000..04e141a --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/tpl/TPJImage.java @@ -0,0 +1,35 @@ +package cz.tefek.tpl; + +public class TPJImage +{ + int[] data; + int width; + int height; + + public TPJImage(int[] pixels, int width, int height) + { + this.data = pixels; + this.width = width; + this.height = height; + } + + public int getWidth() + { + return this.width; + } + + public int getHeight() + { + return this.height; + } + + public int[] getData() + { + return this.data; + } + + public int pixelAt(int x, int y) + { + return this.data[x + y * this.width]; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/tpl/TPL.java b/plutoio2/src/main/java/cz/tefek/tpl/TPL.java new file mode 100644 index 0000000..6c4d473 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/tpl/TPL.java @@ -0,0 +1,187 @@ +package cz.tefek.tpl; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import javax.imageio.ImageIO; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +/** + * Quick ABGR (8-bit per channel, 32 bits per pixel) texture loader for OpenGL + * use. Color component swizzling may be needed. + * + * @author 493msi + */ +public class TPL +{ + private static final int PLACEHOLDER_SIZE = 16; + private static final int PLACEHOLDER_CHECKEDBOARD = 16; + private static final int PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE = PLACEHOLDER_SIZE / PLACEHOLDER_CHECKEDBOARD; + + public static TPNImage load(ResourceAddress file) + { + return file == null ? loadImage(null) : load(file.toPath()); + } + + public static TPNImage load(String file) + { + if (file == null) + { + return loadImage(null); + } + + try + { + return loadImage(ImageIO.read(new File(file))); + } + catch (Exception e) + { + Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file); + Logger.logException(e); + + return loadImage(null); + } + } + + public static TPNImage loadImage(BufferedImage image) + { + boolean remake = false; + int width = 0; + int height = 0; + + if (image == null) + { + Logger.log(SmartSeverity.WARNING, "[TPL] Null BufferedImage supplied, generating a placeholder."); + + remake = true; + } + else + { + width = image.getWidth(); + height = image.getHeight(); + + if (width > 16384 || height > 16384 || width < 1 || height < 1) + { + Logger.log(SmartSeverity.ERROR, "[TPL] BufferedImage size is invalid (< 1 or > 16384), generating a placeholder."); + + remake = true; + } + } + + if (remake) + { + width = PLACEHOLDER_SIZE; + height = PLACEHOLDER_SIZE; + + Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image..."); + + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4); + buffer.order(ByteOrder.nativeOrder()); + + for (int i = 0; i < width * height; i++) + { + int x = i % width; + int y = i / width; + boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2; + + buffer.put((byte) 0xff); // A + buffer.put((byte) 0x00); // B + buffer.put((byte) 0x00); // G + buffer.put((byte) (checker ? 0xff : 0x00)); // R + } + + buffer.flip(); + return new TPNImage(buffer, width, height); + } + + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder()); + + BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D imgGraphics = copy.createGraphics(); + imgGraphics.drawImage(image, 0, copy.getHeight(), copy.getWidth(), 0, 0, 0, image.getWidth(), image.getHeight(), null); // I wonder if this is pixel-perfect + imgGraphics.dispose(); + + Raster data = copy.getData(); + DataBuffer dataBuffer = data.getDataBuffer(); + DataBufferByte byteBuffer = (DataBufferByte) dataBuffer; + byte[] byteData = byteBuffer.getData(); + buffer.put(byteData); + buffer.flip(); + + return new TPNImage(buffer, width, height); + } + + public static TPJImage loadPixels(String file) + { + TPJImage tImg = null; + BufferedImage image = null; + + boolean remake = false; + + int width = 0; + int height = 0; + + try + { + image = ImageIO.read(new File(file)); + width = image.getWidth(); + height = image.getHeight(); + } + catch (Exception e) + { + Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file); + Logger.logException(e); + + remake = true; + } + + if (width > 16384 || height > 16384 || width < 1 || height < 1) + { + Logger.log(SmartSeverity.ERROR, "[TPL] Image size is invalid (< 1 or > 16384): " + file); + Logger.log(SmartSeverity.ERROR, "[TPL] A replacement will be generated."); + + remake = true; + } + + if (remake) + { + width = PLACEHOLDER_SIZE; + height = PLACEHOLDER_SIZE; + + tImg = new TPJImage(new int[width * height], width, height); + + Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image..."); + + for (int i = 0; i < width * height; i++) + { + int x = i % width; + int y = i / width; + boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2; + + tImg.data[i] = checker ? 0xffff0000 : 0xff000000; + } + + return tImg; + } + + tImg = new TPJImage(new int[width * height], width, height); + + for (int i = 0; i < width * height; i++) + { + int pixel = image.getRGB(i % width, i / width); + + tImg.data[i] = pixel; + } + + return tImg; + } +} diff --git a/plutoio2/src/main/java/cz/tefek/tpl/TPNImage.java b/plutoio2/src/main/java/cz/tefek/tpl/TPNImage.java new file mode 100644 index 0000000..89ec5e2 --- /dev/null +++ b/plutoio2/src/main/java/cz/tefek/tpl/TPNImage.java @@ -0,0 +1,32 @@ +package cz.tefek.tpl; + +import java.nio.ByteBuffer; + +public class TPNImage +{ + ByteBuffer data; + int width; + int height; + + public TPNImage(ByteBuffer bfr, int width, int height) + { + this.data = bfr; + this.width = width; + this.height = height; + } + + public int getWidth() + { + return this.width; + } + + public int getHeight() + { + return this.height; + } + + public ByteBuffer getData() + { + return this.data; + } +} diff --git a/plutomesher/pom.xml b/plutomesher/pom.xml new file mode 100644 index 0000000..1857a10 --- /dev/null +++ b/plutomesher/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + cz.tefek + plutomesher + 0.2 + plutomesher + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutostatic + 0.3 + + + \ No newline at end of file diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/DrawMode.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/DrawMode.java new file mode 100644 index 0000000..5492999 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/DrawMode.java @@ -0,0 +1,41 @@ +package cz.tefek.pluto.engine.graphics.gl; + +import org.lwjgl.opengl.GL33; +import org.lwjgl.opengl.GL40; + +import cz.tefek.pluto.engine.gl.IOpenGLEnum; + +public enum DrawMode implements IOpenGLEnum +{ + POINTS(GL33.GL_POINTS), + LINES(GL33.GL_LINES), + LINE_LOOP(GL33.GL_LINE_LOOP), + LINE_STRIP(GL33.GL_LINE_STRIP), + TRIANGLES(GL33.GL_TRIANGLES), + TRIANGLE_STRIP(GL33.GL_TRIANGLE_STRIP), + TRIANGLE_FAN(GL33.GL_TRIANGLE_FAN), + @Deprecated + QUADS(GL33.GL_QUADS), + @Deprecated + QUAD_STRIP(GL33.GL_QUAD_STRIP), + @Deprecated + POLYGON(GL33.GL_POLYGON), + LINES_ADJACENCY(GL33.GL_LINES_ADJACENCY), + LINE_STRIP_ADJACENCY(GL33.GL_LINE_STRIP_ADJACENCY), + TRIANGLES_ADJACENCY(GL33.GL_TRIANGLES_ADJACENCY), + TRIANGLE_STRIP_ADJACENCY(GL33.GL_TRIANGLE_STRIP_ADJACENCY), + PATCHES(GL40.GL_PATCHES); + + private int glID; + + private DrawMode(int id) + { + this.glID = id; + } + + @Override + public int getGLID() + { + return this.glID; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/QuadPresets.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/QuadPresets.java new file mode 100644 index 0000000..fc6aa93 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/QuadPresets.java @@ -0,0 +1,57 @@ +package cz.tefek.pluto.engine.graphics.gl.vao; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray; + +/** + * @author 493msi + * + */ +public class QuadPresets +{ + public static VertexArray basicQuad() + { + float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 }; + + float[] positions = { -1, 1, 1, 1, 1, -1, -1, -1 }; + + int[] indices = { 0, 1, 2, 0, 2, 3 }; + + VertexArrayBuilder vab = new VertexArrayBuilder(); + vab.vertices(new VecArray<>(positions, 2)); + vab.uvs(new VecArray<>(uvs, 2)); + vab.indices(new VecArray<>(indices, 1)); + + return vab.export(); + } + + public static VertexArray halvedSize() + { + float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 }; + + float[] positions = { -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f }; + + int[] indices = { 0, 1, 2, 0, 2, 3 }; + + VertexArrayBuilder vab = new VertexArrayBuilder(); + vab.vertices(new VecArray<>(positions, 2)); + vab.uvs(new VecArray<>(uvs, 2)); + vab.indices(new VecArray<>(indices, 1)); + return vab.export(); + } + + public static VertexArray basicNoNeg() + { + float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 }; + + float[] positions = { 0, 1, 1, 1, 1, 0, 0, 0 }; + + int[] indices = { 0, 1, 2, 0, 2, 3 }; + + VertexArrayBuilder vab = new VertexArrayBuilder(); + vab.vertices(new VecArray<>(positions, 2)); + vab.uvs(new VecArray<>(uvs, 2)); + vab.indices(new VecArray<>(indices, 1)); + + return vab.export(); + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArray.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArray.java new file mode 100644 index 0000000..8470cb8 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArray.java @@ -0,0 +1,145 @@ +package cz.tefek.pluto.engine.graphics.gl.vao; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Vector; + +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryUtil; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.engine.graphics.gl.DrawMode; +import cz.tefek.pluto.engine.graphics.gl.vbo.ArrayBuffer; +import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer; + +public class VertexArray +{ + protected final List usedAttribs; + protected final Vector> vertexAttribs; + + protected IndexArrayBuffer indices; + + private int vertexCount; + protected int glID = 0; + + public VertexArray() + { + int maxAttribs = GL33.glGetInteger(GL33.GL_MAX_VERTEX_ATTRIBS); + + this.usedAttribs = new ArrayList<>(maxAttribs); + this.vertexAttribs = new Vector>(maxAttribs); + this.vertexAttribs.setSize(maxAttribs); + + this.glID = GL33.glGenVertexArrays(); + + Logger.logf(SmartSeverity.ADDED, "Vertex array ID %d created...\n", this.glID); + } + + public void createArrayAttrib(ArrayBuffer buffer, int attribID) + { + this.bind(); + buffer.bind(); + GL33.glVertexAttribPointer(attribID, buffer.getVertexDimensions(), buffer.getType().getGLID(), false, 0, 0); + + this.vertexAttribs.set(attribID, buffer); + this.usedAttribs.add(attribID); + + if (!this.hasIndices()) + { + this.vertexCount = buffer.getVertexCount(); + } + } + + public List> getVertexAttribs() + { + return Collections.unmodifiableList(this.vertexAttribs); + } + + public int getVertexCount() + { + return this.vertexCount; + } + + public void enableAllAttributes() + { + this.usedAttribs.stream().forEach(GL33::glEnableVertexAttribArray); + } + + public void bindIndices(IndexArrayBuffer buffer) + { + this.bind(); + buffer.bind(); + this.indices = buffer; + this.vertexCount = buffer.getVertexCount(); + } + + public void bind() + { + GL33.glBindVertexArray(this.glID); + } + + public void unbind() + { + GL33.glBindVertexArray(0); + } + + public void draw(DrawMode mode) + { + if (this.hasIndices()) + { + GL33.glDrawElements(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL); + } + else + { + GL33.glDrawArrays(mode.getGLID(), 0, this.vertexCount); + } + } + + public void drawInstanced(DrawMode mode, int count) + { + if (this.hasIndices()) + { + GL33.glDrawElementsInstanced(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL, count); + } + else + { + GL33.glDrawArraysInstanced(mode.getGLID(), 0, this.vertexCount, count); + } + } + + public IndexArrayBuffer getIndices() + { + return this.indices; + } + + public boolean hasIndices() + { + return this.indices != null; + } + + public void delete() + { + this.usedAttribs.stream().map(this.vertexAttribs::get).forEach(ArrayBuffer::delete); + this.vertexAttribs.clear(); + this.usedAttribs.clear(); + + if (this.indices != null) + { + this.indices.delete(); + this.indices = null; + } + + Logger.logf(SmartSeverity.REMOVED, "Vertex array ID %d deleted...\n", this.glID); + + GL33.glDeleteVertexArrays(this.glID); + + this.glID = 0; + } + + public int getID() + { + return this.glID; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArrayBuilder.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArrayBuilder.java new file mode 100644 index 0000000..a6d2f3b --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/VertexArrayBuilder.java @@ -0,0 +1,42 @@ +package cz.tefek.pluto.engine.graphics.gl.vao; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes; +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray; +import cz.tefek.pluto.engine.graphics.gl.vbo.FloatArrayBuffer; +import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer; + +public class VertexArrayBuilder +{ + protected VertexArray va; + + public VertexArrayBuilder() + { + this.va = new VertexArray(); + } + + public VertexArrayBuilder vertices(VecArray vertices) + { + this.va.createArrayAttrib(new FloatArrayBuffer(vertices), ReservedAttributes.POSITION); + + return this; + } + + public VertexArrayBuilder uvs(VecArray uvs) + { + this.va.createArrayAttrib(new FloatArrayBuffer(uvs), ReservedAttributes.UV); + + return this; + } + + public VertexArrayBuilder indices(VecArray indices) + { + this.va.bindIndices(new IndexArrayBuffer(indices)); + + return this; + } + + public VertexArray export() + { + return this.va; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/ReservedAttributes.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/ReservedAttributes.java new file mode 100644 index 0000000..354222a --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/ReservedAttributes.java @@ -0,0 +1,9 @@ +package cz.tefek.pluto.engine.graphics.gl.vao.attrib; + +public class ReservedAttributes +{ + public static final int POSITION = 0; + public static final int UV = 1; + public static final int NORMAL = 2; + public static final int COLOR = 3; +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/data/VecArray.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/data/VecArray.java new file mode 100644 index 0000000..fbdff1b --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vao/attrib/data/VecArray.java @@ -0,0 +1,46 @@ +package cz.tefek.pluto.engine.graphics.gl.vao.attrib.data; + +import java.lang.reflect.Array; + +public class VecArray +{ + private final T data; + private final int vecDimensions; + private final int vertexCount; + + public VecArray(T data, int vecDimensions) + { + var dataType = data.getClass(); + + if (!dataType.isArray()) + { + throw new IllegalStateException("Input data must be of an array type!"); + } + + this.data = data; + this.vecDimensions = vecDimensions; + var dataLength = Array.getLength(data); + + if (dataLength % vecDimensions != 0) + { + throw new IllegalArgumentException("Data size must be divisible by the amount of vector dimensions!"); + } + + this.vertexCount = dataLength / vecDimensions; + } + + public T getData() + { + return this.data; + } + + public int getVecDimensions() + { + return this.vecDimensions; + } + + public int getVertexCount() + { + return this.vertexCount; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/ArrayBuffer.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/ArrayBuffer.java new file mode 100644 index 0000000..dd04790 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/ArrayBuffer.java @@ -0,0 +1,62 @@ +package cz.tefek.pluto.engine.graphics.gl.vbo; + +import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.glBindBuffer; +import static org.lwjgl.opengl.GL15.glDeleteBuffers; +import static org.lwjgl.opengl.GL15.glGenBuffers; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray; + +public abstract class ArrayBuffer> +{ + protected int glID = 0; + + private final int vertexDimensions; + private final int vertexCount; + + public ArrayBuffer(T data) + { + this.glID = glGenBuffers(); + this.bind(); + this.bindData(data); + + this.vertexDimensions = data.getVecDimensions(); + this.vertexCount = data.getVertexCount(); + } + + public abstract EnumArrayBufferType getType(); + + protected abstract void bindData(T data); + + public void bind() + { + glBindBuffer(GL_ARRAY_BUFFER, this.glID); + } + + public void unbind() + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + public void delete() + { + glDeleteBuffers(this.glID); + + this.glID = 0; + } + + public int getID() + { + return this.glID; + } + + public int getVertexDimensions() + { + return this.vertexDimensions; + } + + public int getVertexCount() + { + return this.vertexCount; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/EnumArrayBufferType.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/EnumArrayBufferType.java new file mode 100644 index 0000000..6e53745 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/EnumArrayBufferType.java @@ -0,0 +1,25 @@ +package cz.tefek.pluto.engine.graphics.gl.vbo; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.gl.IOpenGLEnum; + +public enum EnumArrayBufferType implements IOpenGLEnum +{ + FLOAT(GL33.GL_FLOAT), + INT(GL33.GL_INT), + UNSIGNED_INT(GL33.GL_UNSIGNED_INT); + + private int glID; + + private EnumArrayBufferType(int glEnum) + { + this.glID = glEnum; + } + + @Override + public int getGLID() + { + return this.glID; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/FloatArrayBuffer.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/FloatArrayBuffer.java new file mode 100644 index 0000000..cefd33a --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/FloatArrayBuffer.java @@ -0,0 +1,25 @@ +package cz.tefek.pluto.engine.graphics.gl.vbo; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray; + +public class FloatArrayBuffer extends ArrayBuffer> +{ + public FloatArrayBuffer(VecArray data) + { + super(data); + } + + @Override + protected void bindData(VecArray vertexData) + { + GL33.glBufferData(GL33.GL_ARRAY_BUFFER, vertexData.getData(), GL33.GL_STATIC_DRAW); + } + + @Override + public EnumArrayBufferType getType() + { + return EnumArrayBufferType.FLOAT; + } +} diff --git a/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/IndexArrayBuffer.java b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/IndexArrayBuffer.java new file mode 100644 index 0000000..5fa8450 --- /dev/null +++ b/plutomesher/src/main/java/cz/tefek/pluto/engine/graphics/gl/vbo/IndexArrayBuffer.java @@ -0,0 +1,50 @@ +package cz.tefek.pluto.engine.graphics.gl.vbo; + +import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW; +import static org.lwjgl.opengl.GL15.glBindBuffer; +import static org.lwjgl.opengl.GL15.glBufferData; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray; + +public class IndexArrayBuffer extends ArrayBuffer> +{ + public IndexArrayBuffer(int[] data) + { + super(new VecArray<>(data, 1)); + } + + public IndexArrayBuffer(VecArray data) + { + super(data); + + if (data.getVecDimensions() != 1) + { + throw new IllegalArgumentException("Index buffers must have exactly one vertex dimension!"); + } + } + + @Override + public void bind() + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.glID); + } + + @Override + protected void bindData(VecArray data) + { + glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.getData(), GL_STATIC_DRAW); + } + + @Override + public void unbind() + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + @Override + public EnumArrayBufferType getType() + { + return EnumArrayBufferType.UNSIGNED_INT; + } +} diff --git a/plutoshader/pom.xml b/plutoshader/pom.xml new file mode 100644 index 0000000..c86789e --- /dev/null +++ b/plutoshader/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + cz.tefek + plutoshader + 0.3 + plutoshader + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutostatic + 0.3 + + + cz.tefek + plutotexturing + 0.1 + + + cz.tefek + plutomesher + 0.2 + + + \ No newline at end of file diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/IShaderProgram.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/IShaderProgram.java new file mode 100644 index 0000000..cd9eb1b --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/IShaderProgram.java @@ -0,0 +1,32 @@ +package cz.tefek.pluto.engine.shader; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.shader.type.IShader; + +public interface IShaderProgram +{ + int getID(); + + default void attach(IShader shader) + { + GL33.glAttachShader(this.getID(), shader.getID()); + } + + default void detach(IShader shader) + { + GL33.glDetachShader(this.getID(), shader.getID()); + } + + default void start() + { + GL33.glUseProgram(this.getID()); + } + + default void stop() + { + GL33.glUseProgram(0); + } + + void dispose(); +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/PlutoShaderMod.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/PlutoShaderMod.java new file mode 100644 index 0000000..8ff540c --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/PlutoShaderMod.java @@ -0,0 +1,11 @@ +package cz.tefek.pluto.engine.shader; + +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.pluto.engine.ModLWJGL; + +@ModEntry(modid = PlutoShaderMod.MOD_ID, displayName = "PlutoShader", dependencies = { ModLWJGL.class }, version = "0.3", description = "Automated shader loader and manager.") +public class PlutoShaderMod +{ + public static final String MOD_ID = "plutoshader"; + +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/RenderShaderBuilder.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/RenderShaderBuilder.java new file mode 100644 index 0000000..7a8c558 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/RenderShaderBuilder.java @@ -0,0 +1,252 @@ +package cz.tefek.pluto.engine.shader; + +import org.lwjgl.opengl.GL33; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.asl.resource.ResourceSubscriber; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.engine.shader.type.FragmentShader; +import cz.tefek.pluto.engine.shader.type.VertexShader; +import cz.tefek.pluto.engine.shader.uniform.Uniform; +import cz.tefek.pluto.engine.shader.uniform.UniformBase; +import cz.tefek.pluto.engine.shader.uniform.UniformMat4; +import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection; +import cz.tefek.pluto.engine.shader.uniform.auto.AutomaticUniforms; + +public class RenderShaderBuilder +{ + private final boolean tempShaders; + + private VertexShader vertexShader; + private FragmentShader fragmentShader; + + public RenderShaderBuilder(ResourceSubscriber subscriber, String vtx, String frg) + { + this(new ResourceAddress(subscriber, vtx), new ResourceAddress(subscriber, frg)); + } + + public RenderShaderBuilder(ResourceAddress vtx, ResourceAddress frg) + { + this.tempShaders = true; + + this.vertexShader = new VertexShader(vtx); + this.fragmentShader = new FragmentShader(frg); + } + + public RenderShaderBuilder(VertexShader vtx, FragmentShader frg) + { + this.tempShaders = false; + + this.vertexShader = vtx; + this.fragmentShader = frg; + } + + // TODO Geometry shader + + public T build(Class shaderClass, boolean manualAttributeLayout) + { + try + { + var programAnnotation = shaderClass.getDeclaredAnnotation(ShaderProgram.class); + + if (programAnnotation == null) + { + Logger.logf(SmartSeverity.ERROR, "Shader program class '%s' is not properly annotated with '@%s', it will NOT be loaded.\n", shaderClass.getCanonicalName(), ShaderProgram.class.getName()); + + return null; + } + + if ((shaderClass.getModifiers() & Modifier.FINAL) == 0) + { + Logger.logf(SmartSeverity.WARNING, "Shader program class '%s' is not final, this is not enforced, but generally shader program classes should be final.\n", shaderClass.getCanonicalName()); + } + + var programID = GL33.glCreateProgram(); + + var shaderConstructor = shaderClass.getConstructor(); + + var fields = shaderClass.getFields(); + + var program = shaderConstructor.newInstance(); + + var programIDField = ShaderBase.class.getDeclaredField("programID"); + programIDField.setAccessible(true); + programIDField.setInt(program, programID); + programIDField.setAccessible(false); + + program.attach(this.vertexShader); + program.attach(this.fragmentShader); + + for (var f : fields) + { + var vaa = f.getAnnotation(VertexArrayAttribute.class); + + var vertexAttributeName = f.getName(); + + if (vaa == null) + { + continue; + } + + var vaaName = vaa.name(); + + if (!vaaName.isBlank()) + { + vertexAttributeName = vaaName; + } + + if (!manualAttributeLayout) + { + int attribID = vaa.value(); + program.bindAttribute(attribID, vertexAttributeName); + f.setInt(program, attribID); + } + else + { + f.setInt(program, GL33.glGetAttribLocation(programID, vertexAttributeName)); + } + } + + GL33.glLinkProgram(programID); + + if (GL33.glGetProgrami(programID, GL33.GL_LINK_STATUS) != GL33.GL_TRUE) + { + Logger.log(SmartSeverity.ERROR, "Shader program could not be linked: " + programID); + Logger.log(GL33.glGetProgramInfoLog(programID)); + + program.detach(this.vertexShader); + program.detach(this.fragmentShader); + + if (this.tempShaders) + { + this.vertexShader.dispose(); + this.fragmentShader.dispose(); + } + + return null; + } + + GL33.glValidateProgram(programID); + + if (GL33.glGetProgrami(programID, GL33.GL_VALIDATE_STATUS) != GL33.GL_TRUE) + { + Logger.log(SmartSeverity.ERROR, "Shader program could not be validated: " + programID); + Logger.log(GL33.glGetProgramInfoLog(programID)); + + program.detach(this.vertexShader); + program.detach(this.fragmentShader); + + if (this.tempShaders) + { + this.vertexShader.dispose(); + this.fragmentShader.dispose(); + } + + return null; + } + + program.detach(this.vertexShader); + program.detach(this.fragmentShader); + + if (this.tempShaders) + { + this.vertexShader.dispose(); + this.fragmentShader.dispose(); + } + + for (var field : fields) + { + var uniformTag = field.getAnnotation(Uniform.class); + + var uniformName = field.getName(); + + if (uniformTag == null) + { + continue; + } + + var uniformTagName = uniformTag.name(); + + if (!uniformTagName.isBlank()) + { + uniformName = uniformTagName; + } + + var type = field.getType(); + + if (!UniformBase.class.isAssignableFrom(type)) + { + Logger.logf(SmartSeverity.ERROR, "Shader uniform '%s' is not of the %s type in a program ID '%d' of type '%s'.\n", uniformName, UniformBase.class.getCanonicalName(), programID, shaderClass.getCanonicalName()); + continue; + } + + var uniformType = type.asSubclass(UniformBase.class); + + int location = GL33.glGetUniformLocation(programID, uniformName); + + if (location == GL33.GL_INVALID_INDEX) + { + field.set(program, null); + Logger.logf(SmartSeverity.WARNING, "Did not find shader uniform '%s' in a program ID '%d' of type '%s'.\n", uniformName, programID, shaderClass.getCanonicalName()); + + continue; + } + + try + { + var uniformConstructor = uniformType.getConstructor(int.class); + + var uniform = uniformConstructor.newInstance(location); + + field.set(program, uniform); + + if (field.getAnnotation(AutoViewportProjection.class) != null) + { + if (uniform instanceof UniformMat4) + { + UniformMat4 umat4 = (UniformMat4) uniform; + AutomaticUniforms.VIEWPORT_PROJECTION.addListener(mat4 -> + { + program.start(); + umat4.load(mat4); + }); + Logger.logf(SmartSeverity.ADDED, "Uniform '%s' ID %d in '%s' ID %d now listens to AutomaticUniforms.VIEWPORT_PROJECTION.\n", uniformName, location, shaderClass.getCanonicalName(), programID); + } + else + { + Logger.logf(SmartSeverity.WARNING, "Uniform '%s' in '%s' is not of type '%s', cannot register for AutomaticUniforms.VIEWPORT_PROJECTION.\n", uniformName, UniformMat4.class.getSimpleName(), shaderClass.getCanonicalName()); + } + } + } + catch (NoSuchMethodException e) + { + Logger.logf(SmartSeverity.ERROR, "Shader uniform class '%s' must feature a constructor taking a parameter of type 'int'.\n", uniformType.getCanonicalName()); + e.printStackTrace(); + } + + } + + return program; + } + catch (IllegalArgumentException | NoSuchMethodException e) + { + Logger.logf(SmartSeverity.ERROR, "Shader program class '%s' does not have a valid default constructor.\n", shaderClass.getCanonicalName()); + e.printStackTrace(); + } + catch (InvocationTargetException e) + { + Logger.log(SmartSeverity.ERROR, "Caught an exception while reflectively invoking a method:"); + e.printStackTrace(); + } + catch (SecurityException | ReflectiveOperationException e) + { + e.printStackTrace(); + } + + return null; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderBase.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderBase.java new file mode 100644 index 0000000..60320bd --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderBase.java @@ -0,0 +1,36 @@ +package cz.tefek.pluto.engine.shader; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +public abstract class ShaderBase implements IShaderProgram +{ + private int programID; + + protected int getUniform(String name) + { + return GL33.glGetUniformLocation(this.programID, name); + } + + @Override + public void dispose() + { + Logger.logf(SmartSeverity.REMOVED, "Disposing of shader ID %d of type %s...\n", this.getID(), this.getClass().getCanonicalName()); + + this.stop(); + GL33.glDeleteProgram(this.programID); + } + + protected void bindAttribute(int attribute, String name) + { + GL33.glBindAttribLocation(this.programID, attribute, name); + } + + @Override + public int getID() + { + return this.programID; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderCompiler.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderCompiler.java new file mode 100644 index 0000000..276ab67 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderCompiler.java @@ -0,0 +1,42 @@ +package cz.tefek.pluto.engine.shader; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.asl.textio.TextIn; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.engine.shader.type.EnumShaderType; + +public class ShaderCompiler +{ + private static String preprocessCode(String code) + { + // TODO: More preprocessings options + return code.trim(); + } + + public static int load(ResourceAddress address, EnumShaderType type) + { + var sourceString = preprocessCode(TextIn.fromAddress(address)); + return load(address.toString(), sourceString, type); + } + + private static int load(String name, String code, EnumShaderType type) + { + int shaderID = GL33.glCreateShader(type.getGLID()); + GL33.glShaderSource(shaderID, code.trim()); + GL33.glCompileShader(shaderID); + + if (GL33.glGetShaderi(shaderID, GL33.GL_COMPILE_STATUS) == GL33.GL_FALSE) + { + Logger.log(SmartSeverity.ERROR, "Shader could not be compiled: " + name); + Logger.log(GL33.glGetShaderInfoLog(shaderID)); + } + + Logger.logf(SmartSeverity.ADDED, "Shader ID %d compiled: %s\n", shaderID, name); + + return shaderID; + } + +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderProgram.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderProgram.java new file mode 100644 index 0000000..bf9705d --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ShaderProgram.java @@ -0,0 +1,14 @@ +package cz.tefek.pluto.engine.shader; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(TYPE) +public @interface ShaderProgram +{ + +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/VertexArrayAttribute.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/VertexArrayAttribute.java new file mode 100644 index 0000000..a49f0a5 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/VertexArrayAttribute.java @@ -0,0 +1,21 @@ +package cz.tefek.pluto.engine.shader; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface VertexArrayAttribute +{ + /** + * The attribute ID. + */ + int value(); + + /** + * The attribute name, corresponding to the identifier in the shader. + */ + String name() default ""; +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/EnumShaderType.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/EnumShaderType.java new file mode 100644 index 0000000..1ae6e77 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/EnumShaderType.java @@ -0,0 +1,27 @@ +package cz.tefek.pluto.engine.shader.type; + +import org.lwjgl.opengl.GL33; +import org.lwjgl.opengl.GL40; +import org.lwjgl.opengl.GL43; + +public enum EnumShaderType +{ + VERTEX(GL33.GL_VERTEX_SHADER), + GEOMETRY(GL33.GL_GEOMETRY_SHADER), + COMPUTE(GL43.GL_COMPUTE_SHADER), + FRAGMENT(GL33.GL_FRAGMENT_SHADER), + TESSELATION_CONTROL(GL40.GL_TESS_CONTROL_SHADER), + TESSELATION_EVALUATION(GL40.GL_TESS_EVALUATION_SHADER); + + private int id; + + private EnumShaderType(int id) + { + this.id = id; + } + + public int getGLID() + { + return this.id; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/FragmentShader.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/FragmentShader.java new file mode 100644 index 0000000..cb7a36f --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/FragmentShader.java @@ -0,0 +1,20 @@ +package cz.tefek.pluto.engine.shader.type; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.pluto.engine.shader.ShaderCompiler; + +public final class FragmentShader implements IShader +{ + private int id; + + public FragmentShader(ResourceAddress address) + { + this.id = ShaderCompiler.load(address, EnumShaderType.FRAGMENT); + } + + @Override + public int getID() + { + return this.id; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/GeometryShader.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/GeometryShader.java new file mode 100644 index 0000000..7526bf3 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/GeometryShader.java @@ -0,0 +1,20 @@ +package cz.tefek.pluto.engine.shader.type; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.pluto.engine.shader.ShaderCompiler; + +public final class GeometryShader implements IShader +{ + private int id; + + public GeometryShader(ResourceAddress address) + { + this.id = ShaderCompiler.load(address, EnumShaderType.GEOMETRY); + } + + @Override + public int getID() + { + return this.id; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/IShader.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/IShader.java new file mode 100644 index 0000000..8086938 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/IShader.java @@ -0,0 +1,13 @@ +package cz.tefek.pluto.engine.shader.type; + +import org.lwjgl.opengl.GL33; + +public interface IShader +{ + int getID(); + + default void dispose() + { + GL33.glDeleteShader(this.getID()); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/VertexShader.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/VertexShader.java new file mode 100644 index 0000000..ba9d0dd --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/type/VertexShader.java @@ -0,0 +1,20 @@ +package cz.tefek.pluto.engine.shader.type; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.pluto.engine.shader.ShaderCompiler; + +public final class VertexShader implements IShader +{ + private int id; + + public VertexShader(ResourceAddress address) + { + this.id = ShaderCompiler.load(address, EnumShaderType.VERTEX); + } + + @Override + public int getID() + { + return this.id; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferBindingPoint.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferBindingPoint.java new file mode 100644 index 0000000..d52edf4 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferBindingPoint.java @@ -0,0 +1,13 @@ +package cz.tefek.pluto.engine.shader.ubo; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface UniformBufferBindingPoint +{ + int location(); +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferObject.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferObject.java new file mode 100644 index 0000000..6b5c164 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/ubo/UniformBufferObject.java @@ -0,0 +1,86 @@ +package cz.tefek.pluto.engine.shader.ubo; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.pluto.engine.shader.ShaderBase; + +public final class UniformBufferObject +{ + private int id; + private long size; + private String name; + + public UniformBufferObject(long size, String name) + { + this.id = GL33.glGenBuffers(); + this.size = size; + this.name = name; + + GL33.glBindBuffer(GL33.GL_UNIFORM_BUFFER, this.id); + GL33.glBufferData(GL33.GL_UNIFORM_BUFFER, this.size, GL33.GL_DYNAMIC_DRAW); + GL33.glBindBuffer(GL33.GL_UNIFORM_BUFFER, 0); + + GL33.glBindBufferRange(GL33.GL_UNIFORM_BUFFER, 0, this.id, 0, this.size); + } + + public void bindLocation(ShaderBase shader, int bindingPoint) + { + var programID = shader.getID(); + + int id = GL33.glGetUniformBlockIndex(programID, this.name); + + if (id == GL33.GL_INVALID_INDEX) + { + Logger.logf(SmartSeverity.ERROR, "Uniform block %s not found in shader %d.\n", this.name, programID); + GL33.glUseProgram(0); + return; + } + + GL33.glUniformBlockBinding(programID, id, bindingPoint); + } + + public void writeIntData(long offset, int[] data) + { + this.bind(); + GL33.glBufferSubData(GL33.GL_UNIFORM_BUFFER, offset, data); + this.unbind(); + } + + public void writeLongData(long offset, long[] data) + { + this.bind(); + GL33.glBufferSubData(GL33.GL_UNIFORM_BUFFER, offset, data); + this.unbind(); + } + + public void writeFloatData(long offset, float[] data) + { + this.bind(); + GL33.glBufferSubData(GL33.GL_UNIFORM_BUFFER, offset, data); + this.unbind(); + } + + public void writeDoubleData(long offset, double[] data) + { + this.bind(); + GL33.glBufferSubData(GL33.GL_UNIFORM_BUFFER, offset, data); + this.unbind(); + } + + public void bind() + { + GL33.glBindBuffer(GL33.GL_UNIFORM_BUFFER, this.id); + } + + public void unbind() + { + GL33.glBindBuffer(GL33.GL_UNIFORM_BUFFER, 0); + } + + public void free() + { + GL33.glDeleteBuffers(this.id); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/Uniform.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/Uniform.java new file mode 100644 index 0000000..4e68531 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/Uniform.java @@ -0,0 +1,16 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Uniform +{ + /** + * The uniform name, corresponding to the identifier in the shader. + */ + String name() default ""; +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayInt.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayInt.java new file mode 100644 index 0000000..51befac --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayInt.java @@ -0,0 +1,17 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.lwjgl.opengl.GL33; + +public class UniformArrayInt extends UniformBase +{ + public UniformArrayInt(int location) + { + super(location); + } + + public void load(int[] data) + { + GL33.glUniform1iv(this.location, data); + } + +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayMat3x2.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayMat3x2.java new file mode 100644 index 0000000..57ad758 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformArrayMat3x2.java @@ -0,0 +1,35 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Matrix3x2fc; +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryStack; + +import java.nio.FloatBuffer; + +public class UniformArrayMat3x2 extends UniformVec2 +{ + public UniformArrayMat3x2(int programID) + { + super(programID); + } + + public void load(Matrix3x2fc... matrices) + { + final int dimensions = 3 * 2; + + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buf = stack.mallocFloat(dimensions * matrices.length); + + for (int i = 0; i < matrices.length; i++) + { + buf.position(i * dimensions); + matrices[i].get(buf); + } + + buf.flip(); + + GL33.glUniformMatrix3x2fv(this.location, false, buf); + } + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBase.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBase.java new file mode 100644 index 0000000..38f652f --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBase.java @@ -0,0 +1,16 @@ +package cz.tefek.pluto.engine.shader.uniform; + +public abstract class UniformBase +{ + protected final int location; + + protected UniformBase(int location) + { + this.location = location; + } + + public final int getLocation() + { + return this.location; + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBoolean.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBoolean.java new file mode 100644 index 0000000..04839a2 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformBoolean.java @@ -0,0 +1,16 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.lwjgl.opengl.GL33; + +public class UniformBoolean extends UniformBase +{ + public UniformBoolean(int location) + { + super(location); + } + + public void load(boolean value) + { + GL33.glUniform1i(this.location, value ? 1 : 0); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformFloat.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformFloat.java new file mode 100644 index 0000000..43af75b --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformFloat.java @@ -0,0 +1,16 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.lwjgl.opengl.GL33; + +public class UniformFloat extends UniformBase +{ + public UniformFloat(int location) + { + super(location); + } + + public void load(float value) + { + GL33.glUniform1f(this.location, value); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformInt.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformInt.java new file mode 100644 index 0000000..b60d149 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformInt.java @@ -0,0 +1,16 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.lwjgl.opengl.GL33; + +public class UniformInt extends UniformBase +{ + public UniformInt(int location) + { + super(location); + } + + public void load(int value) + { + GL33.glUniform1i(this.location, value); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3.java new file mode 100644 index 0000000..85e7a1b --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Matrix3fc; +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryStack; + +import java.nio.FloatBuffer; + +public class UniformMat3 extends UniformBase +{ + public UniformMat3(int location) + { + super(location); + } + + public void load(Matrix3fc matrix) + { + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buf = stack.mallocFloat(3 * 3); + GL33.glUniformMatrix3fv(this.location, false, matrix.get(buf)); + } + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3x2.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3x2.java new file mode 100644 index 0000000..df9e783 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat3x2.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Matrix3x2fc; +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryStack; + +import java.nio.FloatBuffer; + +public class UniformMat3x2 extends UniformVec2 +{ + public UniformMat3x2(int programID) + { + super(programID); + } + + public void load(Matrix3x2fc matrix) + { + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buf = stack.mallocFloat(3 * 2); + GL33.glUniformMatrix3x2fv(this.location, false, matrix.get(buf)); + } + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat4.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat4.java new file mode 100644 index 0000000..6a63ee0 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformMat4.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Matrix4fc; +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryStack; + +import java.nio.FloatBuffer; + +public class UniformMat4 extends UniformBase +{ + public UniformMat4(int location) + { + super(location); + } + + public void load(Matrix4fc matrix) + { + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buf = stack.mallocFloat(4 * 4); + GL33.glUniformMatrix4fv(this.location, false, matrix.get(buf)); + } + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec2.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec2.java new file mode 100644 index 0000000..3ba419f --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec2.java @@ -0,0 +1,22 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Vector2fc; +import org.lwjgl.opengl.GL33; + +public class UniformVec2 extends UniformBase +{ + public UniformVec2(int location) + { + super(location); + } + + public void load(Vector2fc value) + { + GL33.glUniform2f(this.location, value.x(), value.y()); + } + + public void load(float x, float y) + { + GL33.glUniform2f(this.location, x, y); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec3.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec3.java new file mode 100644 index 0000000..c84f6d1 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec3.java @@ -0,0 +1,22 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Vector3fc; +import org.lwjgl.opengl.GL33; + +public class UniformVec3 extends UniformBase +{ + public UniformVec3(int location) + { + super(location); + } + + public void load(Vector3fc value) + { + GL33.glUniform3f(this.location, value.x(), value.y(), value.z()); + } + + public void load(float x, float y, float z) + { + GL33.glUniform3f(this.location, x, y, z); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec4.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec4.java new file mode 100644 index 0000000..53f856a --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/UniformVec4.java @@ -0,0 +1,22 @@ +package cz.tefek.pluto.engine.shader.uniform; + +import org.joml.Vector4fc; +import org.lwjgl.opengl.GL33; + +public class UniformVec4 extends UniformBase +{ + public UniformVec4(int location) + { + super(location); + } + + public void load(Vector4fc value) + { + GL33.glUniform4f(this.location, value.x(), value.y(), value.z(), value.w()); + } + + public void load(float x, float y, float z, float w) + { + GL33.glUniform4f(this.location, x, y, z, w); + } +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutoViewportProjection.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutoViewportProjection.java new file mode 100644 index 0000000..a299a97 --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutoViewportProjection.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.engine.shader.uniform.auto; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a uniform to automatically receive viewport's ortographic projection. + * + * @author 493msi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AutoViewportProjection +{ + +} diff --git a/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutomaticUniforms.java b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutomaticUniforms.java new file mode 100644 index 0000000..9614a5c --- /dev/null +++ b/plutoshader/src/main/java/cz/tefek/pluto/engine/shader/uniform/auto/AutomaticUniforms.java @@ -0,0 +1,11 @@ +package cz.tefek.pluto.engine.shader.uniform.auto; + +import org.joml.Matrix4fc; + +import cz.tefek.pluto.eventsystem.lambda.LambdaEventFactory; +import cz.tefek.pluto.eventsystem.lambda.LambdaEventFactory.LambdaEvent; + +public class AutomaticUniforms +{ + public static final LambdaEvent VIEWPORT_PROJECTION = LambdaEventFactory.createEvent(); +} diff --git a/plutospritesheet/pom.xml b/plutospritesheet/pom.xml new file mode 100644 index 0000000..7d9d947 --- /dev/null +++ b/plutospritesheet/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + cz.tefek + plutospritesheet + 0.2 + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutotexturing + 0.1 + + + cz.tefek + plutoframebuffer + 0.1 + + + cz.tefek + plutomesher + 0.2 + + + cz.tefek + plutoshader + 0.3 + + + \ No newline at end of file diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IRectangleShader2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IRectangleShader2D.java new file mode 100644 index 0000000..1c8b0f2 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IRectangleShader2D.java @@ -0,0 +1,29 @@ +package cz.tefek.pluto.engine.graphics; + +import org.joml.Matrix3x2fc; +import org.joml.Matrix4fc; +import org.joml.Vector2fc; +import org.joml.Vector4fc; + +import cz.tefek.pluto.engine.shader.IShaderProgram; + +public interface IRectangleShader2D extends IShaderProgram +{ + void loadProjectionMatrix(Matrix4fc matrix); + + void loadTransformationMatrix(Matrix3x2fc matrix); + + default void loadUV(Vector2fc uvBase, Vector2fc uvDelta) + { + this.loadUV(uvBase.x(), uvBase.y(), uvDelta.x(), uvDelta.y()); + } + + void loadUV(float uBase, float yBase, float uWidth, float vHeight); + + default void loadRecolor(Vector4fc col) + { + this.loadUV(col.x(), col.y(), col.z(), col.w()); + } + + void loadRecolor(float r, float g, float b, float a); +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IShader2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IShader2D.java new file mode 100644 index 0000000..f345f08 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/IShader2D.java @@ -0,0 +1,29 @@ +package cz.tefek.pluto.engine.graphics; + +import org.joml.Matrix3x2fc; +import org.joml.Matrix4fc; +import org.joml.Vector2fc; +import org.joml.Vector4fc; + +import cz.tefek.pluto.engine.shader.IShaderProgram; + +public interface IShader2D extends IShaderProgram +{ + void loadProjectionMatrix(Matrix4fc matrix); + + void loadTransformationMatrix(Matrix3x2fc matrix); + + default void loadUV(Vector2fc uvBase, Vector2fc uvDelta) + { + this.loadUV(uvBase.x(), uvBase.y(), uvDelta.x(), uvDelta.y()); + } + + void loadUV(float uBase, float yBase, float uWidth, float vHeight); + + default void loadRecolor(Vector4fc col) + { + this.loadUV(col.x(), col.y(), col.z(), col.w()); + } + + void loadRecolor(float r, float g, float b, float a); +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/PlutoSpriteSheetMod.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/PlutoSpriteSheetMod.java new file mode 100644 index 0000000..8a90e02 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/PlutoSpriteSheetMod.java @@ -0,0 +1,70 @@ +package cz.tefek.pluto.engine.graphics; + +import cz.tefek.io.asl.resource.ResourceSubscriber; +import cz.tefek.io.modloader.Mod; +import cz.tefek.io.modloader.ModEntry; +import cz.tefek.io.modloader.ModLoaderCore; +import cz.tefek.io.modloader.event.ModPreLoad; +import cz.tefek.io.modloader.event.ModPreLoadEvent; +import cz.tefek.io.modloader.event.ModUnload; +import cz.tefek.io.modloader.event.ModUnloadEvent; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.pluto.engine.ModLWJGL; +import cz.tefek.pluto.engine.graphics.spritesheet.FramebufferTiledSpriteSheet; +import cz.tefek.pluto.engine.shader.PlutoShaderMod; +import cz.tefek.pluto.engine.shader.RenderShaderBuilder; + +@ModEntry(modid = PlutoSpriteSheetMod.MOD_ID, version = "0.2", dependencies = { ModLWJGL.class, PlutoShaderMod.class }, author = "493msi", build = 1, displayName = "Pluto SpriteSheet", description = "A library to manage, store and draw sprites.") +public class PlutoSpriteSheetMod +{ + public static final String MOD_ID = "plutospritesheet"; + + public static Mod instance; + public static ResourceSubscriber subscriber; + + /** + * Strictly internal use only, do NOT use this outside of plutospritesheet + */ + private static Shader2D shader2D; + + /** + * Strictly internal use only, do NOT use this outside of plutospritesheet + */ + private static ShaderRectangle2D shaderRectangle2D; + + /** + * Strictly internal use only, do NOT use this outside of plutospritesheet + */ + private static ShaderRectangle2D spriteSheetShader; + + @ModPreLoad + public static void preLoad(ModPreLoadEvent event) + { + instance = ModLoaderCore.getModByID(MOD_ID); + subscriber = instance.getDefaultResourceSubscriber(); + + Logger.log("Intializing " + MOD_ID + "..."); + + shader2D = new RenderShaderBuilder(subscriber, "shaders.v2D#glsl", "shaders.f2D#glsl").build(Shader2D.class, false); + shaderRectangle2D = new RenderShaderBuilder(subscriber, "shaders.VertexRectangle2D#glsl", "shaders.FragmentRectangle2D#glsl").build(ShaderRectangle2D.class, false); + spriteSheetShader = new RenderShaderBuilder(subscriber, "shaders.VertexSpriteSheet#glsl", "shaders.FragmentSpriteSheet#glsl").build(ShaderRectangle2D.class, false); + + Renderer2D.load(shader2D); + RectangleRenderer2D.load(shaderRectangle2D); + + FramebufferTiledSpriteSheet.setSpriteShader(spriteSheetShader); + } + + @ModUnload + public static void unload(ModUnloadEvent unloadEvent) + { + FramebufferTiledSpriteSheet.setSpriteShader(null); + + spriteSheetShader.dispose(); + shaderRectangle2D.dispose(); + shader2D.dispose(); + + RectangleRenderer2D.unload(); + Renderer2D.unload(); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/RectangleRenderer2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/RectangleRenderer2D.java new file mode 100644 index 0000000..2c061f5 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/RectangleRenderer2D.java @@ -0,0 +1,260 @@ +package cz.tefek.pluto.engine.graphics; + +import java.util.Stack; + +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fc; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.gl.DrawMode; +import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets; +import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray; +import cz.tefek.pluto.engine.graphics.sprite.Sprite; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +/** + * A builder-like renderer for 2D rectangles. Note that the internal state is + * not monitored for performance reasons and outside events may affect active + * instances, such as changing the active shader or texture. In order to restore + * the internal state to the default. + * + *

+ * In contrast to {@link Renderer2D}, {@link RectangleRenderer2D} uses + * {@link RectangleTexture}s instead of standard 2D textures. + *

+ * + * @author 493msi + */ +public class RectangleRenderer2D +{ + static VertexArray standardQuad; + static VertexArray centeredQuad; + + private static ShaderRectangle2D defaultShader; + + public static final RectangleRenderer2D INSTANCE = new RectangleRenderer2D(); + + protected Matrix3x2f transformation = new Matrix3x2f(); + + protected IRectangleShader2D customShader; + + protected VertexArray activeVA; + + protected RectangleTexture activeTexture; + + protected boolean modifiedTransformation = false; + + private static Stack customShaderStack = new Stack<>(); + + public static void load(ShaderRectangle2D defaultShaderIn) + { + standardQuad = QuadPresets.basicNoNeg(); + centeredQuad = QuadPresets.halvedSize(); + + defaultShader = defaultShaderIn; + } + + public static void unload() + { + if (standardQuad != null) + { + standardQuad.delete(); + } + + if (centeredQuad != null) + { + centeredQuad.delete(); + } + } + + /** + * Pushes a custom {@link IRectangleShader2D shader} to be used in place of + * the default one. + */ + public static void pushCustomShader(IRectangleShader2D shader) + { + customShaderStack.push(shader); + } + + /** + * Removes the top {@link IRectangleShader2D shader} from the custom shader + * stack. + */ + public static IRectangleShader2D popCustomShader() + { + return customShaderStack.pop(); + } + + /** + * Checks if the renderer is currently supplied with a custom + * {@link IRectangleShader2D shader}. + */ + public static boolean hasCustomShader() + { + return !customShaderStack.empty(); + } + + /** + * Starts drawing, overriding the default shader with the supplied one. + */ + public static RectangleRenderer2D draw(IRectangleShader2D shader) + { + return draw(standardQuad, shader); + } + + /** + * Starts drawing, overriding the default shader with the supplied one. + */ + public static RectangleRenderer2D draw(VertexArray va, IRectangleShader2D shader) + { + GL33.glEnable(GL33.GL_BLEND); + + INSTANCE.customShader = shader; + + INSTANCE.customShader.start(); + + INSTANCE.customShader.loadRecolor(1, 1, 1, 1); + + INSTANCE.identity(); + + INSTANCE.switchVertexArray(va); + + INSTANCE.activeTexture = null; + + return INSTANCE; + } + + public static RectangleRenderer2D draw(VertexArray va) + { + return draw(va, hasCustomShader() ? customShaderStack.peek() : defaultShader); + } + + public static RectangleRenderer2D draw() + { + return draw(standardQuad); + } + + public RectangleRenderer2D identity() + { + this.transformation.m00 = 1; + this.transformation.m01 = 0; + this.transformation.m10 = 0; + this.transformation.m11 = 1; + this.transformation.m20 = 0; + this.transformation.m21 = 0; + + return this; + } + + public RectangleRenderer2D switchVertexArray(VertexArray va) + { + va.bind(); + va.enableAllAttributes(); + + this.activeVA = va; + + return this; + } + + public RectangleRenderer2D rotate(float rotation) + { + this.transformation.rotate(rotation); + return this; + } + + public RectangleRenderer2D at(float x, float y, float width, float height) + { + this.identity(); + this.transformation.translate(x, y); + this.transformation.scale(width, height); + + this.modifiedTransformation = true; + + return this; + } + + public RectangleRenderer2D translate(float x, float y) + { + this.transformation.translate(x, y); + + this.modifiedTransformation = true; + + return this; + } + + public RectangleRenderer2D scale(float width, float height) + { + this.transformation.scale(width, height); + + this.modifiedTransformation = true; + + return this; + } + + public RectangleRenderer2D transformation(Matrix3x2fc transformationMatrix) + { + this.transformation.set(transformationMatrix); + + this.modifiedTransformation = true; + + return this; + } + + private RectangleRenderer2D writeTransformation() + { + this.customShader.loadTransformationMatrix(this.transformation); + + this.modifiedTransformation = false; + + return this; + } + + public RectangleRenderer2D sprite(Sprite sprite) + { + return this.texture(sprite.getSheet(), sprite.getX(), sprite.getY(), sprite.getWidth(), sprite.getHeight()); + } + + public RectangleRenderer2D texture(RectangleTexture texture, int u, int v, int width, int height) + { + if (this.activeTexture != texture) + { + this.activeTexture = texture; + texture.bind(); + } + + this.customShader.loadUV(u, texture.getHeight() - v - height, width, height); + + return this; + } + + public RectangleRenderer2D texture(RectangleTexture texture) + { + return this.texture(texture, 0, 0, texture.getWidth(), texture.getHeight()); + } + + public RectangleRenderer2D recolor(float r, float g, float b, float a) + { + this.customShader.loadRecolor(r, g, b, a); + + return this; + } + + public RectangleRenderer2D recolor(Vector4f recolor) + { + this.customShader.loadRecolor(recolor); + + return this; + } + + public void flush() + { + if (this.modifiedTransformation) + { + this.writeTransformation(); + this.modifiedTransformation = false; + } + + this.activeVA.draw(DrawMode.TRIANGLES); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Renderer2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Renderer2D.java new file mode 100644 index 0000000..a70febb --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Renderer2D.java @@ -0,0 +1,242 @@ +package cz.tefek.pluto.engine.graphics; + +import java.util.Stack; + +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fc; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.gl.DrawMode; +import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets; +import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray; +import cz.tefek.pluto.engine.graphics.texture.texture2d.Texture2D; + +/** + * A builder-like renderer for 2D rectangles. Note that the internal state is + * not monitored for performance reasons and outside events may affect active + * instances, such as changing the active shader or texture. In order to restore + * the internal state to the default. + * + * @author 493msi + */ +public class Renderer2D +{ + public static VertexArray standardQuad; + public static VertexArray centeredQuad; + + private static IShader2D defaultShader; + + public static final Renderer2D INSTANCE = new Renderer2D(); + + protected Matrix3x2f transformation = new Matrix3x2f(); + + protected IShader2D customShader; + + protected VertexArray activeVA; + + protected Texture2D activeTexture; + + protected boolean modifiedTransformation = false; + + private static Stack customShaderStack = new Stack<>(); + + public static void load(IShader2D defaultShaderIn) + { + standardQuad = QuadPresets.basicNoNeg(); + centeredQuad = QuadPresets.halvedSize(); + defaultShader = defaultShaderIn; + } + + public static void unload() + { + if (standardQuad != null) + { + standardQuad.delete(); + } + + if (centeredQuad != null) + { + centeredQuad.delete(); + } + } + + /** + * Pushes a custom {@link IShader2D shader} to be used in place of the + * default one. + */ + public static void pushCustomShader(IShader2D shader) + { + customShaderStack.push(shader); + } + + /** + * Removes the top {@link IShader2D shader} from the custom shader stack. + */ + public static IShader2D popCustomShader() + { + return customShaderStack.pop(); + } + + /** + * Checks if the renderer is currently supplied with a custom + * {@link IShader2D shader}. + */ + public static boolean hasCustomShader() + { + return !customShaderStack.empty(); + } + + public static Renderer2D draw(VertexArray va, IShader2D shader) + { + + GL33.glEnable(GL33.GL_BLEND); + + INSTANCE.customShader = shader; + + INSTANCE.customShader.start(); + + INSTANCE.customShader.loadRecolor(1, 1, 1, 1); + + INSTANCE.identity(); + + INSTANCE.switchVertexArray(va); + + INSTANCE.activeTexture = null; + + return INSTANCE; + } + + public static Renderer2D draw(VertexArray va) + { + return draw(va, hasCustomShader() ? customShaderStack.peek() : defaultShader); + } + + public static Renderer2D draw() + { + return draw(standardQuad); + } + + public Renderer2D identity() + { + this.transformation.m00 = 1; + this.transformation.m01 = 0; + this.transformation.m10 = 0; + this.transformation.m11 = 1; + this.transformation.m20 = 0; + this.transformation.m21 = 0; + + return this; + } + + public Renderer2D switchVertexArray(VertexArray va) + { + va.bind(); + va.enableAllAttributes(); + + this.activeVA = va; + + return this; + } + + public Renderer2D rotate(float rotation) + { + this.transformation.rotate(rotation); + return this; + } + + public Renderer2D at(float x, float y, float width, float height) + { + this.identity(); + this.transformation.translate(x, y); + this.transformation.scale(width, height); + + this.modifiedTransformation = true; + + return this; + } + + public Renderer2D translate(float x, float y) + { + this.transformation.translate(x, y); + + this.modifiedTransformation = true; + + return this; + } + + public Renderer2D scale(float width, float height) + { + this.transformation.scale(width, height); + + this.modifiedTransformation = true; + + return this; + } + + public Renderer2D transformation(Matrix3x2fc transformationMatrix) + { + this.transformation.set(transformationMatrix); + + this.modifiedTransformation = true; + + return this; + } + + private Renderer2D writeTransformation() + { + this.customShader.loadTransformationMatrix(this.transformation); + + this.modifiedTransformation = false; + + return this; + } + + public Renderer2D texturef(Texture2D texture, float u, float v, float width, float height) + { + if (this.activeTexture != texture) + { + this.activeTexture = texture; + texture.bind(); + } + + this.customShader.loadUV(u, 1 - v - height, width, height); + + return this; + } + + public Renderer2D texture(Texture2D texture, int u, int v, int width, int height) + { + return this.texturef(texture, u / (float) texture.getWidth(), v / (float) texture.getHeight(), width / (float) texture.getWidth(), height / (float) texture.getHeight()); + } + + public Renderer2D texture(Texture2D texture) + { + return this.texturef(texture, 0.0f, 0.0f, 1.0f, 1.0f); + } + + public Renderer2D recolor(float r, float g, float b, float a) + { + this.customShader.loadRecolor(r, g, b, a); + + return this; + } + + public Renderer2D recolor(Vector4f recolor) + { + this.customShader.loadRecolor(recolor); + + return this; + } + + public void flush() + { + if (this.modifiedTransformation) + { + this.writeTransformation(); + this.modifiedTransformation = false; + } + + this.activeVA.draw(DrawMode.TRIANGLES); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Shader2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Shader2D.java new file mode 100644 index 0000000..67f2195 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/Shader2D.java @@ -0,0 +1,70 @@ +package cz.tefek.pluto.engine.graphics; + +import org.joml.Matrix3x2fc; +import org.joml.Matrix4fc; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes; +import cz.tefek.pluto.engine.shader.ShaderBase; +import cz.tefek.pluto.engine.shader.ShaderProgram; +import cz.tefek.pluto.engine.shader.VertexArrayAttribute; +import cz.tefek.pluto.engine.shader.uniform.Uniform; +import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2; +import cz.tefek.pluto.engine.shader.uniform.UniformMat4; +import cz.tefek.pluto.engine.shader.uniform.UniformVec2; +import cz.tefek.pluto.engine.shader.uniform.UniformVec4; +import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection; + +/** + * @author 493msi + * + */ +@ShaderProgram +public final class Shader2D extends ShaderBase implements IShader2D +{ + @AutoViewportProjection + @Uniform(name = "projection") + public UniformMat4 projectionMatrix; + + @Uniform(name = "transformation") + public UniformMat3x2 transformationMatrix; + + @Uniform + public UniformVec2 uvBase; + + @Uniform + public UniformVec2 uvDelta; + + @Uniform + public UniformVec4 recolor; + + @VertexArrayAttribute(ReservedAttributes.POSITION) + public int position; + + @VertexArrayAttribute(ReservedAttributes.UV) + public int uvCoords; + + @Override + public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight) + { + this.uvBase.load(uvStartX, uvStartY); + this.uvDelta.load(uWidth, vHeight); + } + + @Override + public void loadRecolor(float r, float g, float b, float a) + { + this.recolor.load(r, g, b, a); + } + + @Override + public void loadProjectionMatrix(Matrix4fc matrix) + { + this.projectionMatrix.load(matrix); + } + + @Override + public void loadTransformationMatrix(Matrix3x2fc matrix) + { + this.transformationMatrix.load(matrix); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/ShaderRectangle2D.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/ShaderRectangle2D.java new file mode 100644 index 0000000..cf53909 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/ShaderRectangle2D.java @@ -0,0 +1,70 @@ +package cz.tefek.pluto.engine.graphics; + +import org.joml.Matrix3x2fc; +import org.joml.Matrix4fc; + +import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes; +import cz.tefek.pluto.engine.shader.ShaderBase; +import cz.tefek.pluto.engine.shader.ShaderProgram; +import cz.tefek.pluto.engine.shader.VertexArrayAttribute; +import cz.tefek.pluto.engine.shader.uniform.Uniform; +import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2; +import cz.tefek.pluto.engine.shader.uniform.UniformMat4; +import cz.tefek.pluto.engine.shader.uniform.UniformVec2; +import cz.tefek.pluto.engine.shader.uniform.UniformVec4; +import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection; + +/** + * @author 493msi + * + */ +@ShaderProgram +public final class ShaderRectangle2D extends ShaderBase implements IRectangleShader2D +{ + @AutoViewportProjection + @Uniform(name = "projection") + public UniformMat4 projectionMatrix; + + @Uniform(name = "transformation") + public UniformMat3x2 transformationMatrix; + + @Uniform + public UniformVec2 uvBase; + + @Uniform + public UniformVec2 uvDelta; + + @Uniform + public UniformVec4 recolor; + + @VertexArrayAttribute(ReservedAttributes.POSITION) + public int position; + + @VertexArrayAttribute(ReservedAttributes.UV) + public int uvCoords; + + @Override + public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight) + { + this.uvBase.load(uvStartX, uvStartY); + this.uvDelta.load(uWidth, vHeight); + } + + @Override + public void loadRecolor(float r, float g, float b, float a) + { + this.recolor.load(r, g, b, a); + } + + @Override + public void loadProjectionMatrix(Matrix4fc matrix) + { + this.projectionMatrix.load(matrix); + } + + @Override + public void loadTransformationMatrix(Matrix3x2fc matrix) + { + this.transformationMatrix.load(matrix); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeleton.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeleton.java new file mode 100644 index 0000000..a08a16d --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeleton.java @@ -0,0 +1,26 @@ +package cz.tefek.pluto.engine.graphics.skeleton; + +import org.joml.Vector2f; +import org.joml.Vector2fc; + +public class SpriteSkeleton +{ + protected SpriteSkeletonLimb rootLimb; + protected static final Vector2f DEFAULT_SCALE = new Vector2f(1); + protected static final Vector2f DEFAULT_SCALE_FLIPPED = new Vector2f(-1, 1); + + public void render(Vector2fc position, Vector2fc scale) + { + this.rootLimb.render(position, scale); + } + + public void render(Vector2fc position) + { + this.render(position, DEFAULT_SCALE); + } + + public SpriteSkeletonLimb getRootLimb() + { + return this.rootLimb; + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeletonLimb.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeletonLimb.java new file mode 100644 index 0000000..e2aae9b --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/skeleton/SpriteSkeletonLimb.java @@ -0,0 +1,192 @@ +package cz.tefek.pluto.engine.graphics.skeleton; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.joml.Matrix3x2f; +import org.joml.Vector2f; +import org.joml.Vector2fc; +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.RectangleRenderer2D; +import cz.tefek.pluto.engine.graphics.Renderer2D; +import cz.tefek.pluto.engine.graphics.sprite.PartialTextureSprite; + +public class SpriteSkeletonLimb +{ + protected Vector2f pivotPoint; // The origin point of this limb's transformation + + protected float rotation; + + protected float width; + protected float height; + + protected Matrix3x2f transformation; + + private PartialTextureSprite sprite; + + protected static final Vector2f DEFAULT_SCALE = new Vector2f(1); + + // The reason I don't use an IdentityHashMap here is because I need to retain the insertion order perfectly, + // so the skeleton nicely draws back to front and not in a seemingly random order. + + private List> backChildren; // Children connected to this limb from behind at the position + private List> frontChildren; // Children connected to this limb from front at position + + public static final int MAX_DEPTH = 8; + + public SpriteSkeletonLimb(float pivotX, float pivotY) + { + this.backChildren = new ArrayList<>(); + this.frontChildren = new ArrayList<>(); + + this.transformation = new Matrix3x2f(); + + this.pivotPoint = new Vector2f(pivotX, pivotY); + } + + public List> getBackChildren() + { + return this.backChildren; + } + + public List> getFrontChildren() + { + return this.frontChildren; + } + + public void addChildFront(SpriteSkeletonLimb limb, float mountX, float mountY) + { + this.frontChildren.add(Pair.of(limb, new Vector2f(mountX, mountY))); + } + + public void addChildBack(SpriteSkeletonLimb limb, float mountX, float mountY) + { + this.backChildren.add(Pair.of(limb, new Vector2f(mountX, mountY))); + } + + public Vector2f getPivotPoint() + { + return this.pivotPoint; + } + + public void setSprite(PartialTextureSprite sprite) + { + this.sprite = sprite; + } + + public PartialTextureSprite getSprite() + { + return this.sprite; + } + + protected void renderInternal(RectangleRenderer2D renderer, Vector2fc position, Vector2fc scale) + { + renderer.identity().translate(position.x(), position.y()).rotate(this.rotation).translate(-this.pivotPoint.x * scale.x(), -this.pivotPoint.y * scale.y()).scale(this.width * scale.x(), this.height * scale.y()).sprite(this.sprite).flush(); + } + + protected void renderChildren(RectangleRenderer2D renderer, Vector2fc position, Vector2fc scale) + { + this.backChildren.forEach(entry -> + { + var limb = entry.getLeft(); + var mountingPoint = entry.getRight(); + var rescaledMountingPoint = mountingPoint.mul(scale, new Vector2f()); + var transformedMountingPoint = this.transformation.transformPosition(rescaledMountingPoint, new Vector2f()); + var transformedPos = position.add(transformedMountingPoint, new Vector2f()); + + limb.renderChildren(renderer, transformedPos, scale); + }); + + this.renderInternal(renderer, position, scale); + + this.frontChildren.forEach(entry -> + { + var limb = entry.getLeft(); + var mountingPoint = entry.getRight(); + var rescaledMountingPoint = mountingPoint.mul(scale, new Vector2f()); + var transformedMountingPoint = this.transformation.transformPosition(rescaledMountingPoint, new Vector2f()); + var transformedPos = position.add(transformedMountingPoint, new Vector2f()); + + limb.renderChildren(renderer, transformedPos, scale); + }); + } + + public void render(Vector2fc position, Vector2fc scale) + { + if (scale.x() * scale.y() < 0) + { + GL33.glCullFace(GL33.GL_FRONT); + this.renderChildren(RectangleRenderer2D.draw(Renderer2D.centeredQuad), position, scale); + GL33.glCullFace(GL33.GL_BACK); + } + else + { + this.renderChildren(RectangleRenderer2D.draw(Renderer2D.centeredQuad), position, scale); + } + } + + public void render(Vector2fc position) + { + this.renderChildren(RectangleRenderer2D.draw(Renderer2D.centeredQuad), position, DEFAULT_SCALE); + } + + public void setHeight(float height) + { + this.height = height; + } + + public void setWidth(float width) + { + this.width = width; + } + + public float getHeight() + { + return this.height; + } + + public float getWidth() + { + return this.width; + } + + public void setRotation(float rotation) + { + this.rotation = rotation; + this.updateTransformation(); + } + + public void addRotation(float rotation) + { + this.rotation += rotation; + this.updateTransformation(); + } + + public void setRotationAdjusted(float rotation) + { + var rotationDiff = rotation - this.rotation; + this.backChildren.forEach(entry -> entry.getKey().addRotationAdjusted(rotationDiff)); + this.frontChildren.forEach(entry -> entry.getKey().addRotationAdjusted(rotationDiff)); + this.setRotation(rotation); + } + + public void addRotationAdjusted(float rotation) + { + this.backChildren.forEach(entry -> entry.getKey().addRotationAdjusted(rotation)); + this.frontChildren.forEach(entry -> entry.getKey().addRotationAdjusted(rotation)); + this.addRotation(rotation); + } + + public float getRotation() + { + return this.rotation; + } + + protected void updateTransformation() + { + this.transformation.identity(); + this.transformation.rotate(this.rotation); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposablePlaceholderSprite.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposablePlaceholderSprite.java new file mode 100644 index 0000000..56d3aef --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposablePlaceholderSprite.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +public class DisposablePlaceholderSprite extends DisposableTextureSprite +{ + public DisposablePlaceholderSprite() + { + super(new RectangleTexture()); + + this.spriteTexture.load((String) null, MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + this.width = this.spriteTexture.getWidth(); + this.height = this.spriteTexture.getHeight(); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposableTextureSprite.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposableTextureSprite.java new file mode 100644 index 0000000..e33a9f8 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/DisposableTextureSprite.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +public class DisposableTextureSprite extends PartialTextureSprite implements SpriteDisposable +{ + public DisposableTextureSprite(RectangleTexture texture) + { + super(texture, 0, 0, texture.getWidth(), texture.getHeight()); + } + + @Override + public void delete() + { + this.spriteTexture.delete(); + this.spriteTexture = null; + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/PartialTextureSprite.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/PartialTextureSprite.java new file mode 100644 index 0000000..4eaa849 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/PartialTextureSprite.java @@ -0,0 +1,59 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; + +public class PartialTextureSprite implements Sprite +{ + protected transient RectangleTexture spriteTexture; + + protected int x; + protected int y; + protected int width; + protected int height; + + public PartialTextureSprite(RectangleTexture texture, int x, int y, int width, int height) + { + this.spriteTexture = texture; + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public int getX() + { + return this.x; + } + + @Override + public int getY() + { + return this.y; + } + + @Override + public int getHeight() + { + return this.height; + } + + @Override + public int getWidth() + { + return this.width; + } + + public void setSheet(RectangleTexture spriteTexture) + { + this.spriteTexture = spriteTexture; + } + + @Override + public RectangleTexture getSheet() + { + return this.spriteTexture; + } + +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/Sprite.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/Sprite.java new file mode 100644 index 0000000..a814881 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/Sprite.java @@ -0,0 +1,14 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +public interface Sprite +{ + int getX(); + + int getY(); + + int getWidth(); + + int getHeight(); + + T getSheet(); +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/SpriteDisposable.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/SpriteDisposable.java new file mode 100644 index 0000000..64ba764 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/SpriteDisposable.java @@ -0,0 +1,6 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +public interface SpriteDisposable extends Sprite +{ + void delete(); +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/TileSprite.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/TileSprite.java new file mode 100644 index 0000000..5d763bf --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/sprite/TileSprite.java @@ -0,0 +1,85 @@ +package cz.tefek.pluto.engine.graphics.sprite; + +import cz.tefek.pluto.engine.graphics.spritesheet.TiledSpriteSheet; + +public class TileSprite> implements Sprite +{ + protected T spriteSheet; + + public static final int IMPLICIT_POSITION = -1; + public static final int IMPLICIT_SIZE = -1; + + protected int x; + protected int y; + + protected int width; + protected int height; + + public TileSprite(int x, int y, int width, int height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public TileSprite() + { + this(IMPLICIT_POSITION, IMPLICIT_POSITION, IMPLICIT_SIZE, IMPLICIT_SIZE); + } + + @Override + public int getX() + { + return this.x == IMPLICIT_POSITION ? 0 : this.x; + } + + @Override + public int getY() + { + return this.y == IMPLICIT_POSITION ? 0 : this.y; + } + + @Override + public int getWidth() + { + return this.width == IMPLICIT_SIZE ? this.spriteSheet.getWidthInPixels() : this.width; + } + + @Override + public int getHeight() + { + return this.height == IMPLICIT_SIZE ? this.spriteSheet.getHeightInPixels() : this.height; + } + + public void setX(int x) + { + this.x = x; + } + + public void setY(int y) + { + this.y = y; + } + + public void setWidth(int width) + { + this.width = width; + } + + public void setHeight(int height) + { + this.height = height; + } + + public void setSpriteSheet(T spriteSheet) + { + this.spriteSheet = spriteSheet; + } + + @Override + public T getSheet() + { + return this.spriteSheet; + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/BufferedImageTiledSpriteSheet.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/BufferedImageTiledSpriteSheet.java new file mode 100644 index 0000000..9942593 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/BufferedImageTiledSpriteSheet.java @@ -0,0 +1,114 @@ +package cz.tefek.pluto.engine.graphics.spritesheet; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +import cz.tefek.pluto.engine.graphics.sprite.Sprite; +import cz.tefek.pluto.engine.graphics.sprite.TileSprite; + +// FIXME +/** + * A sprite atlas for {@link BufferedImage}. + * + * @deprecated Completely untested, use at your own risk. + * @author 493msi + * + */ +@Deprecated +public class BufferedImageTiledSpriteSheet extends TiledSpriteSheet +{ + protected Graphics2D drawGraphics; + + public BufferedImageTiledSpriteSheet(int expectedTileSize, int spriteSheetSize) + { + super(expectedTileSize, spriteSheetSize); + + var size = this.spriteSheetWidth * this.tileWidth; + this.spriteSheet = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + + this.drawGraphics = this.spriteSheet.createGraphics(); + this.drawGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } + + public BufferedImageTiledSpriteSheet(int tileSize) + { + this(tileSize, 8); + } + + @Override + public boolean requiresExpanding(int index) + { + return false; + } + + @Override + public void copyToNewImage() + { + this.drawGraphics.dispose(); + + var newSpriteSheet = new BufferedImage(this.getWidthInPixels(), this.getHeightInPixels(), BufferedImage.TYPE_INT_ARGB); + this.drawGraphics = newSpriteSheet.createGraphics(); + this.drawGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + + var i = 0; + + for (var sprite : this.sprites) + { + if (sprite == null) + { + i++; + continue; + } + + var xPos = i % this.spriteSheetWidth * this.tileWidth; + var yPos = i / this.spriteSheetWidth * this.tileHeight; + + this.drawTileSprite(sprite, xPos, yPos, this.tileWidth, this.tileHeight); + + sprite.setX(xPos); + sprite.setY(yPos); + sprite.setWidth(this.tileWidth); + sprite.setHeight(this.tileHeight); + + i++; + } + + this.spriteSheet = newSpriteSheet; + } + + @Override + public void delete() + { + this.drawGraphics.dispose(); + this.drawGraphics = null; + + super.delete(); + } + + @Override + protected void drawTileSprite(TileSprite> sprite, int x, int y, int width, int height) + { + var image = sprite.getSheet().getSpriteSheetImage(); + + var sx1 = sprite.getX(); + var sy1 = sprite.getY(); + var sx2 = sx1 + sprite.getWidth(); + var sy2 = sy1 + sprite.getHeight(); + + this.drawGraphics.drawImage(image, x, y, x + width, y + height, sx1, sy1, sx2, sy2, null); + } + + @Override + protected void drawSprite(Sprite sprite, int x, int y, int width, int height) + { + var image = sprite.getSheet(); + + var sx1 = sprite.getX(); + var sy1 = sprite.getY(); + var sx2 = sx1 + sprite.getWidth(); + var sy2 = sy1 + sprite.getHeight(); + + this.drawGraphics.drawImage(image, x, y, x + width, y + height, sx1, sy1, sx2, sy2, null); + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/FramebufferTiledSpriteSheet.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/FramebufferTiledSpriteSheet.java new file mode 100644 index 0000000..4e45d2d --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/FramebufferTiledSpriteSheet.java @@ -0,0 +1,154 @@ +package cz.tefek.pluto.engine.graphics.spritesheet; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.IRectangleShader2D; +import cz.tefek.pluto.engine.graphics.RectangleRenderer2D; +import cz.tefek.pluto.engine.graphics.gl.fbo.Framebuffer; +import cz.tefek.pluto.engine.graphics.gl.fbo.FramebufferTexture; +import cz.tefek.pluto.engine.graphics.sprite.Sprite; +import cz.tefek.pluto.engine.graphics.sprite.TileSprite; +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; +import cz.tefek.pluto.engine.graphics.texture.sampler.Sampler2D; +import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture; +import cz.tefek.pluto.engine.math.ProjectionMatrix; + +public class FramebufferTiledSpriteSheet extends TiledSpriteSheet +{ + protected Framebuffer spriteFBO; + protected Sampler2D sampler; + + protected static IRectangleShader2D shader; + + public FramebufferTiledSpriteSheet(int tileWidth, int tileHeight) + { + super(tileWidth, tileHeight); + this.spriteFBO = new Framebuffer(); + var fbTexture = new FramebufferTexture(this.getWidthInPixels(), this.getHeightInPixels(), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + this.spriteSheet = fbTexture; + this.spriteFBO.addTexture(fbTexture); + this.spriteFBO.unbind(); + + this.sampler = new Sampler2D(); + this.sampler.setFilteringParameters(MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + } + + @Override + public void copyToNewImage() + { + var newSpriteSheet = new FramebufferTexture(this.getWidthInPixels(), this.getHeightInPixels(), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE); + + this.spriteFBO.bind(); + this.spriteFBO.removeAllTextures(); + this.spriteFBO.addTexture(newSpriteSheet); + + var i = 0; + + for (var sprite : this.sprites) + { + if (sprite == null) + { + i++; + continue; + } + + var xPos = i % this.spriteSheetWidth * this.tileWidth; + var yPos = i / this.spriteSheetWidth * this.tileHeight; + + this.drawTileSprite(sprite, xPos, yPos, this.tileWidth, this.tileHeight); + + sprite.setX(xPos); + sprite.setY(yPos); + sprite.setWidth(this.tileWidth); + sprite.setHeight(this.tileHeight); + + i++; + } + + this.spriteFBO.unbind(); + this.spriteSheet.delete(); + this.spriteSheet = newSpriteSheet; + } + + public Framebuffer getFrameBuffer() + { + return this.spriteFBO; + } + + @Override + public void delete() + { + this.spriteFBO.unbind(); + this.spriteFBO.delete(); + this.spriteFBO = null; + + this.sampler.delete(); + this.sampler = null; + + this.spriteSheet.delete(); + + // this.spriteSheet = null; + // Done by parent + + super.delete(); + } + + public Sampler2D getSampler() + { + return this.sampler; + } + + @Override + protected void drawTileSprite(TileSprite> sprite, int x, int y, int width, int height) + { + GL33.glActiveTexture(GL33.GL_TEXTURE0); + + this.spriteFBO.bind(); + GL33.glViewport(0, 0, this.getWidthInPixels(), this.getHeightInPixels()); + this.sampler.bind(0); + + GL33.glBlendFunc(GL33.GL_ONE, GL33.GL_ZERO); + + shader.start(); + shader.loadProjectionMatrix(ProjectionMatrix.createOrtho2D(this.getWidthInPixels(), this.getHeightInPixels())); + + var texture = sprite.getSheet().getSpriteSheetImage(); + + RectangleRenderer2D.draw(shader).at(x, y, width, height).texture(texture, sprite.getX(), sprite.getY(), sprite.getWidth(), sprite.getHeight()).flush(); + + GL33.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + this.sampler.unbind(); + this.spriteFBO.unbind(); + } + + @Override + protected void drawSprite(Sprite sprite, int x, int y, int width, int height) + { + GL33.glActiveTexture(GL33.GL_TEXTURE0); + + this.spriteFBO.bind(); + GL33.glViewport(0, 0, this.getWidthInPixels(), this.getHeightInPixels()); + this.sampler.bind(0); + + GL33.glBlendFunc(GL33.GL_ONE, GL33.GL_ZERO); + + shader.start(); + shader.loadProjectionMatrix(ProjectionMatrix.createOrtho2D(this.getWidthInPixels(), this.getHeightInPixels())); + + RectangleRenderer2D.draw(shader).at(x, y, width, height).sprite(sprite).flush(); + + GL33.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + this.sampler.unbind(); + this.spriteFBO.unbind(); + } + + public static void setSpriteShader(IRectangleShader2D shaderIn) + { + shader = shaderIn; + } +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/SpriteSheet.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/SpriteSheet.java new file mode 100644 index 0000000..0869127 --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/SpriteSheet.java @@ -0,0 +1,32 @@ +package cz.tefek.pluto.engine.graphics.spritesheet; + +public abstract class SpriteSheet +{ + protected T spriteSheet; + + public SpriteSheet(T sourceImage) + { + this.spriteSheet = sourceImage; + } + + public SpriteSheet() + { + + } + + public void setSpriteSheetImage(T sourceImage) + { + this.spriteSheet = sourceImage; + } + + public T getSpriteSheetImage() + { + return this.spriteSheet; + } + + public abstract int getWidthInPixels(); + + public abstract int getHeightInPixels(); + + public abstract void delete(); +} diff --git a/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/TiledSpriteSheet.java b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/TiledSpriteSheet.java new file mode 100644 index 0000000..df512fa --- /dev/null +++ b/plutospritesheet/src/main/java/cz/tefek/pluto/engine/graphics/spritesheet/TiledSpriteSheet.java @@ -0,0 +1,244 @@ +package cz.tefek.pluto.engine.graphics.spritesheet; + +import java.util.Vector; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; +import cz.tefek.pluto.engine.graphics.sprite.Sprite; +import cz.tefek.pluto.engine.graphics.sprite.SpriteDisposable; +import cz.tefek.pluto.engine.graphics.sprite.TileSprite; + +public abstract class TiledSpriteSheet extends SpriteSheet +{ + protected Vector>> sprites; + + protected static int idCounter = 1; + + protected int id = idCounter++; + + protected int tileWidth; + protected int tileHeight; + + protected final float aspectRatio; + + protected int spriteSheetWidth; + protected int spriteSheetHeight; + + private static final int approxStarterSpriteSheetSize = 8; + private static final int approxStarterSpriteCount = approxStarterSpriteSheetSize * approxStarterSpriteSheetSize; + + public TiledSpriteSheet(int tileWidth, int tileHeight) + { + + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + + this.aspectRatio = (float) tileWidth / tileHeight; + + this.spriteSheetWidth = Math.round(approxStarterSpriteCount / (approxStarterSpriteSheetSize / this.aspectRatio)); + this.spriteSheetHeight = Math.round(approxStarterSpriteCount / (approxStarterSpriteSheetSize * this.aspectRatio)); + + this.sprites = new Vector<>(this.spriteSheetWidth * this.spriteSheetHeight); + this.sprites.setSize(this.spriteSheetWidth * this.spriteSheetHeight); + } + + protected abstract void drawTileSprite(TileSprite> sprite, int x, int y, int width, int height); + + protected abstract void drawSprite(Sprite sprite, int x, int y, int width, int height); + + @Override + public int getWidthInPixels() + { + return this.tileWidth * this.spriteSheetWidth; + } + + @Override + public int getHeightInPixels() + { + return this.tileHeight * this.spriteSheetHeight; + } + + protected boolean requiresExpanding(int index) + { + return index >= this.sprites.size(); + } + + protected void expand() + { + Logger.logf(Severity.INFO, "Spritesheet #%d: Expanding from %dx%d to ", this.id, this.spriteSheetWidth, this.spriteSheetHeight); + + this.spriteSheetWidth *= 2; + this.spriteSheetHeight *= 2; + + this.sprites.setSize(this.spriteSheetWidth * this.spriteSheetHeight); + + Logger.logf("%dx%d\n", this.spriteSheetWidth, this.spriteSheetHeight); + + this.copyToNewImage(); + } + + protected void upscale(int factor) + { + Logger.logf(Severity.INFO, "Spritesheet #%d: Upscaling from %dx%d to ", this.id, this.tileWidth, this.tileHeight); + + this.tileWidth *= factor; + this.tileHeight *= factor; + + Logger.logf("%dx%d\n", this.tileWidth, this.tileHeight); + + this.copyToNewImage(); + } + + @Override + public void delete() + { + this.sprites.clear(); + this.sprites = null; + + this.spriteSheet = null; + } + + public abstract void copyToNewImage(); + + public TileSprite> addSprite(Sprite sprite, int index) + { + if (!this.aspectRatiosMatch(sprite)) + { + throw new IllegalArgumentException("Sprite and spritesheet aspect ratios do not match!"); + } + + if (!this.isMultiple(sprite)) + { + throw new IllegalArgumentException("The sprite and the spritesheet do not have a common resolution to scale to."); + } + + if (sprite.getWidth() > this.tileWidth) + { + this.upscale(sprite.getWidth() / this.tileWidth); + } + + while (this.requiresExpanding(index)) + { + this.expand(); + } + + var newX = index % this.spriteSheetWidth * this.tileWidth; + var newY = index / this.spriteSheetWidth * this.tileHeight; + var newWidth = this.tileWidth; + var newHeight = this.tileHeight; + + this.drawSprite(sprite, newX, newY, newWidth, newHeight); + + if (sprite instanceof SpriteDisposable) + { + var disposableSprite = (SpriteDisposable) sprite; + disposableSprite.delete(); + } + + var copySprite = new TileSprite>(newX, newY, newWidth, newHeight); + copySprite.setSpriteSheet(this); + + this.sprites.set(index, copySprite); + + return copySprite; + } + + private boolean aspectRatiosMatch(Sprite sprite) + { + final var epsilon = 0.001f; + + return Math.abs(sprite.getWidth() - this.aspectRatio * sprite.getHeight()) < epsilon; + } + + private boolean isMultiple(Sprite sprite) + { + final var spriteWidth = sprite.getWidth(); + final var spriteHeight = sprite.getHeight(); + + // Shortcut for images with matching sizes; + if (spriteWidth == this.tileWidth && spriteHeight == this.tileHeight) + { + return true; + } + + if (this.tileWidth < spriteWidth) + { + int i = this.tileWidth; + + while (i < spriteWidth) + { + i <<= 1; + } + + if (i != spriteWidth) + { + return false; + } + } + else + { + int i = spriteWidth; + + while (i < this.tileWidth) + { + i <<= 1; + } + + if (i != this.tileWidth) + { + return false; + } + } + + if (this.tileHeight < spriteHeight) + { + int i = this.tileHeight; + + while (i < spriteHeight) + { + i <<= 1; + } + + if (i != spriteHeight) + { + return false; + } + } + else + { + int i = spriteHeight; + + while (i < this.tileHeight) + { + i <<= 1; + } + + if (i != this.tileHeight) + { + return false; + } + } + + return true; + } + + public int getTileWidth() + { + return this.tileWidth; + } + + public int getTileHeight() + { + return this.tileHeight; + } + + public int getSpritesPerRow() + { + return this.spriteSheetWidth; + } + + public int getSpritesPerColumn() + { + return this.spriteSheetHeight; + } +} diff --git a/plutostatic/pom.xml b/plutostatic/pom.xml new file mode 100644 index 0000000..dc46bde --- /dev/null +++ b/plutostatic/pom.xml @@ -0,0 +1,217 @@ + + 4.0.0 + cz.tefek + plutostatic + 0.3 + plutostatic + + 11 + 11 + UTF-8 + UTF-8 + 3.2.3 + 1.9.17 + 1.8.0 + 1.8.0 + + + + lwjgl-natives-linux-amd64 + + + unix + amd64 + + + + natives-linux + + + + lwjgl-natives-macos-amd64 + + + mac + amd64 + + + + natives-macos + + + + lwjgl-natives-windows-amd64 + + + windows + amd64 + + + + natives-windows + + + + lwjgl-natives-windows-x86 + + + windows + x86 + + + + natives-windows-x86 + + + + + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + + + + org.lwjgl + lwjgl + + + org.lwjgl + lwjgl-assimp + + + org.lwjgl + lwjgl-glfw + + + org.lwjgl + lwjgl-llvm + + + org.lwjgl + lwjgl-lz4 + + + org.lwjgl + lwjgl-nanovg + + + org.lwjgl + lwjgl-openal + + + org.lwjgl + lwjgl-opengl + + + org.lwjgl + lwjgl-opus + + + org.lwjgl + lwjgl-stb + + + + org.lwjgl + lwjgl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-assimp + ${lwjgl.natives} + + + org.lwjgl + lwjgl-glfw + ${lwjgl.natives} + + + org.lwjgl + lwjgl-llvm + ${lwjgl.natives} + + + org.lwjgl + lwjgl-lz4 + ${lwjgl.natives} + + + org.lwjgl + lwjgl-nanovg + ${lwjgl.natives} + + + org.lwjgl + lwjgl-openal + ${lwjgl.natives} + + + org.lwjgl + lwjgl-opengl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-opus + ${lwjgl.natives} + + + org.lwjgl + lwjgl-stb + ${lwjgl.natives} + + + + org.joml + joml + ${joml.version} + + + com.code-disaster.steamworks4j + steamworks4j + ${steamworks4j.version} + + + com.code-disaster.steamworks4j + steamworks4j-server + ${steamworks4j-server.version} + + + org.joml + joml + ${joml.version} + + + commons-io + commons-io + 2.6 + + + cz.tefek + plutoio2 + 0.2 + + + \ No newline at end of file diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/ModLWJGL.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/ModLWJGL.java new file mode 100644 index 0000000..1fdd108 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/ModLWJGL.java @@ -0,0 +1,11 @@ +package cz.tefek.pluto.engine; + +import org.lwjgl.Version; + +import cz.tefek.io.modloader.ModEntry; + +@ModEntry(modid = "modlwjgl", version = ModLWJGL.version, author = "The LWJGL team", displayName = "LWJGL", description = "The LWJGL library, without which the Pluto Engine wouldn't exist.") +public class ModLWJGL +{ + public static final String version = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/BufferHelper.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/BufferHelper.java new file mode 100644 index 0000000..a84addf --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/BufferHelper.java @@ -0,0 +1,119 @@ +package cz.tefek.pluto.engine.buffer; + +import org.apache.commons.io.IOUtils; +import org.lwjgl.BufferUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +/** + * A utility class to handle primitive native buffers. + * + * @author 493msi + * @since 0.1 + * + */ +public final class BufferHelper +{ + /** + * Creates an {@link IntBuffer} from the input int array. + * + * @param data The input int array. + * @return The created {@link IntBuffer} + * + * @author 493msi + * @since 0.1 + */ + public static IntBuffer flippedIntBuffer(int[] data) + { + IntBuffer buffer = BufferUtils.createIntBuffer(data.length); + buffer.put(data); + buffer.flip(); + + return buffer; + } + + /** + * Creates an {@link FloatBuffer} from the input float array. + * + * @param data The input float array. + * @return The created {@link FloatBuffer} + * + * @author 493msi + * @since 0.1 + */ + public static FloatBuffer flippedFloatBuffer(float[] data) + { + FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length); + buffer.put(data); + buffer.flip(); + + return buffer; + } + + /** + * Creates a {@link ByteBuffer} from the input byte array. + * + * @param data The input byte array. + * @return The created {@link ByteBuffer} + * + * @author 493msi + * @since 0.1 + */ + public static ByteBuffer flippedByteBuffer(byte[] data) + { + ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); + buffer.put(data); + buffer.flip(); + + return buffer; + } + + /** + * Reallocates a new, bigger {@link ByteBuffer} and copies the data from the + * old one. + * + * @param buffer The input {@link ByteBuffer}. + * @param newCapacity The new buffer's capacity, can't be smaller than the + * current one. + * @return The new, bigger {@link ByteBuffer}. + * + * @author 493msi + * @since 0.1 + */ + public static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity) + { + if (buffer.capacity() > newCapacity) + { + throw new IllegalArgumentException("New capacity is smaller than the previous one."); + } + + ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity); + buffer.flip(); + newBuffer.put(buffer); + 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 fis = new FileInputStream(path)) + { + var ba = IOUtils.toByteArray(fis); + return flippedByteBuffer(ba); + } + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/GLFWImageUtil.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/GLFWImageUtil.java new file mode 100644 index 0000000..5376779 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/GLFWImageUtil.java @@ -0,0 +1,61 @@ +package cz.tefek.pluto.engine.buffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.glfw.GLFWImage; + +import cz.tefek.tpl.TPL; + +/** + * 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(String... icons) + { + var icon = GLFWImage.create(icons.length); + + for (int iconIndex = 0; iconIndex < icons.length; iconIndex++) + { + var img = TPL.loadPixels(icons[iconIndex]); + var imgData = img.getData(); + var imgWidth = img.getWidth(); + var imgHeight = img.getHeight(); + + var byteBuf = BufferUtils.createByteBuffer(imgWidth * imgHeight * 4); + + for (int i = 0; i < imgHeight * imgWidth; i++) + { + var data = imgData[i]; + + byteBuf.put((byte) ((data & 0x00ff0000) >> 16)); + byteBuf.put((byte) ((data & 0x0000ff00) >> 8)); + byteBuf.put((byte) (data & 0x000000ff)); + byteBuf.put((byte) ((data & 0xff000000) >> 24)); + } + + byteBuf.flip(); + + var glfwImg = GLFWImage.create(); + glfwImg.set(imgWidth, imgHeight, byteBuf); + + icon.put(glfwImg); + } + + icon.flip(); + return icon; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/package-info.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/package-info.java new file mode 100644 index 0000000..74612f6 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/buffer/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities for better native buffer handling. + * + * @author 493msi + * + */ +package cz.tefek.pluto.engine.buffer; diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/MiniTime.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/MiniTime.java new file mode 100644 index 0000000..0d7f6a6 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/MiniTime.java @@ -0,0 +1,315 @@ +package cz.tefek.pluto.engine.chrono; + +import java.util.concurrent.TimeUnit; + +/** + * A helper class to convert from a time span in milliseconds to a simplified + * time span {@link String} format and vice versa. Note this action is fully + * reversible at the cost of losing millisecond precision. + * + *

MiniTime format specification:

+ * + *
+ * [Nw][Nd][Nh][Nm][Ns]
+ * 
+ *  w - weeks
+ *  d - days
+ *  h - hours
+ *  m - minutes
+ *  s - seconds
+ *  
+ *  N - a decimal integer
+ * 
+ *
    + *
  • At least one value is required.
  • + *
  • At least time unit is required.
  • + *
  • Time units are case insensitive.
  • + *
  • String cannot start with a letter or end with a number.
  • + *
  • All values must be valid positive 32-bit signed integers.
  • + *
  • All values except the first one must be less than 1 of the previous time + * unit.
  • + *
  • Skipping a time unit assumes zero of that time unit.
  • + *
+ * + *

Permitted values:

+ *
    + *
  • standard MiniTime scheme, from 0s to + * 2147483647w6d23h59m59s
  • + *
  • string "forever" (parses as {@link Long#MAX_VALUE})
  • + *
+ * + * @author 493msi + * @since 0.2 + */ +public class MiniTime +{ + private static class MiniTimeCouldNotBeParsedException 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; + private static final int MINUTES_IN_HOUR = 60; + private static final int SECONDS_IN_MINUTE = 60; + private static final int MILLIS_IN_MINUTE = 1000; + + /** + * Converts a MiniTime spec string to the respective time duration in + * milliseconds. + * + * @param input The source MiniTime non-null string + * @return The resulting time span in milliseconds + * + * @author 493msi + * @since 0.2 + */ + public static long parse(String input) + { + if (input == null) + { + throw new IllegalArgumentException("MiniTime string cannot be null!"); + } + + // Nothing to parse + if (input.isEmpty()) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + if (input.equalsIgnoreCase("forever")) + { + return Long.MAX_VALUE; + } + + // Follow the scheme + if (!input.matches("[0-9]*[wW]?[0-9]*[dD]?[0-9]*[hH]?[0-9]*[mM]?[0-9]*[sS]?")) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + // 4584 of what? Potatoes? + if (input.matches("[0-9]+")) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + // Where are the numbers? + if (input.matches("[a-zA-Z]+")) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + // It shouldn't start with a letter + if (input.matches("^[a-zA-Z].+")) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + var nrs = input.split("[a-zA-Z]"); + var letters = input.split("[0-9]+"); + + if (nrs.length != letters.length) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + long time = 0; + + for (int i = 1; i < nrs.length; i++) + { + var type = letters[i - 1]; + int number = 0; + + try + { + // The only time this fails is when the number is too long + number = Integer.parseUnsignedInt(nrs[i]); + } + catch (NumberFormatException nfe) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + var allow = 0L; + var multiplier = 0; + + switch (type.toLowerCase()) + { + 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": + allow = DAYS_IN_WEEK; + multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * MILLIS_IN_MINUTE; + break; + case "h": + case "H": + allow = HOURS_IN_DAY; + multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * MILLIS_IN_MINUTE; + break; + case "m": + case "M": + allow = MINUTES_IN_HOUR; + multiplier = SECONDS_IN_MINUTE * MILLIS_IN_MINUTE; + break; + case "s": + case "S": + allow = SECONDS_IN_MINUTE; + multiplier = MILLIS_IN_MINUTE; + break; + default: + break; + } + + // The top one can be more than it normally could have, for example you can + // issue a ban for 48h but not 46h120m (it looks dumb) + if (i == 1) + { + allow = Integer.MAX_VALUE; + } + + if (number > allow) + { + throw new MiniTimeCouldNotBeParsedException(); + } + + time += multiplier * number; + } + + return System.currentTimeMillis() + time; + } + + /** + * Converts a time span between two Unix-epoch millisecond time points to a + * MiniTime string. Note ALL time spans larger or equal than + * {@link Integer#MAX_VALUE} will be permanently converted to "forever". + * + * @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 + * + * @author 493msi + * @since 0.2 + */ + public static String fromMillisDiffNow(long after, long before) + { + if (after == Long.MAX_VALUE) + { + return "forever"; + } + + return formatDiff(after - before); + } + + /** + * Converts a time span between now and a future time point in Unix-epoch + * milliseconds to a MiniTime string. Note ALL time spans larger or equal + * than {@link Integer#MAX_VALUE} will be permanently converted to + * "forever". + * + * @param input The source time span in milliseconds + * @return The resulting MiniTime string + * + * @throws IllegalArgumentException on a negative time duration + * + * @author 493msi + * @since 0.2 + */ + public static String fromMillisDiffNow(long future) + { + if (future == Long.MAX_VALUE) + { + return "forever"; + } + + var diff = future - System.currentTimeMillis(); + + return formatDiff(diff); + } + + /** + * Converts a time span milliseconds to a MiniTime string. Note ALL time + * spans larger or equal than {@link Integer#MAX_VALUE} will be permanently + * converted to "forever". + * + * @param diff The source time span in milliseconds + * @return The resulting MiniTime string + * + * @throws IllegalArgumentException on a negative time duration + * + * @author 493msi + * @since 0.2 + */ + public static String formatDiff(long diff) + { + if (diff < 0) + { + throw new IllegalArgumentException("Negative time span cannot be converted to MiniTime."); + } + + var xweeks = miliseconds.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; + + return formatTime(xweeks, xdays, xhours, xminutes, xseconds); + } + + private static String formatTime(long weeks, long days, long hours, long minutes, long seconds) + { + var sb = new StringBuilder(); + + if (weeks > 0) + { + sb.append(weeks); + sb.append('w'); + } + if (days > 0) + { + sb.append(days); + sb.append('d'); + } + if (hours > 0) + { + sb.append(hours); + sb.append('h'); + } + if (minutes > 0) + { + sb.append(minutes); + sb.append('m'); + } + if (seconds > 0) + { + sb.append(seconds); + sb.append('s'); + } + + var timeStr = sb.toString(); + + return timeStr.isEmpty() ? "0s" : timeStr; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/package-info.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/package-info.java new file mode 100644 index 0000000..9f0edec --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/chrono/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities for time manipulation and conversion. + * + * @author 493msi + * + */ +package cz.tefek.pluto.engine.chrono; diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Display.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Display.java new file mode 100644 index 0000000..33fc8ab --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Display.java @@ -0,0 +1,215 @@ +package cz.tefek.pluto.engine.display; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWImage; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.opengl.ARBDebugOutput; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL33; +import org.lwjgl.opengl.GLDebugMessageARBCallback; +import org.lwjgl.system.MemoryUtil; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; +import cz.tefek.pluto.engine.gl.GLDebugInfo; + +/** + * A wrapper class to provide abstraction over GLFW windows. + * + * @author 493msi + * @since 0.2 + */ +public class Display +{ + int width; + int height; + boolean debugMode; + + private boolean wasResized; + + private boolean openGLContext; + + private long windowPointer; + + private GLFWErrorCallback glfwErrorCallback; + + private GLFWWindowSizeCallback resizeCallback; + + private GLDebugMessageARBCallback glDebugCallback; + + Display() + { + this.glfwErrorCallback = new DisplayErrorCallback(); + GLFW.glfwSetErrorCallback(this.glfwErrorCallback); + } + + public void create(String name) + { + GLFW.glfwWindowHint(GLFW.GLFW_STENCIL_BITS, 4); + + this.windowPointer = GLFW.glfwCreateWindow(this.width, this.height, name, MemoryUtil.NULL, MemoryUtil.NULL); + + if (this.windowPointer == MemoryUtil.NULL) + { + this.destroy(); + throw new IllegalStateException("Failed to create the GLFW window..."); + } + + GLFWVidMode vidmode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor()); + GLFW.glfwSetWindowPos(this.windowPointer, (vidmode.width() - this.width) / 2, (vidmode.height() - this.height) / 2); + + GLFW.glfwMakeContextCurrent(this.windowPointer); + + this.resizeCallback = new GLFWWindowSizeCallback() { + @Override + public void invoke(long window, int width, int height) + { + if (width > 0 && height > 0) + { + if (Display.this.debugMode) + { + Logger.logf(Severity.INFO, "Resized to %dx%d.\n", width, height); + } + + Display.this.width = width; + Display.this.height = height; + Display.this.wasResized = true; + + if (Display.this.openGLContext) + { + GL33.glViewport(0, 0, Display.this.width, Display.this.height); + } + } + } + }; + + GLFW.glfwSetWindowSizeCallback(this.windowPointer, this.resizeCallback); + } + + public void setName(String newName) + { + GLFW.glfwSetWindowTitle(this.windowPointer, newName); + } + + 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 != null) + { + this.glfwErrorCallback.free(); + } + + if (this.glDebugCallback != null) + { + this.glDebugCallback.free(); + } + + if (this.resizeCallback != null) + { + this.resizeCallback.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(true); + + GLDebugInfo.printDebugInfo(glCapabilities); + + if (this.debugMode) + { + this.glDebugCallback = new GLDebugMessageARBCallback() { + @Override + public void invoke(int source, int type, int id, int severity, int length, long message, long userParam) + { + var mes = GLDebugMessageARBCallback.getMessage(length, message); + System.err.println(mes); + } + }; + + ARBDebugOutput.glDebugMessageCallbackARB(this.glDebugCallback, 0); + } + + GL33.glEnable(GL33.GL_CULL_FACE); + GL33.glCullFace(GL33.GL_BACK); + + this.openGLContext = true; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayBuilder.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayBuilder.java new file mode 100644 index 0000000..a3bc32a --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayBuilder.java @@ -0,0 +1,78 @@ +package cz.tefek.pluto.engine.display; + +import org.lwjgl.glfw.GLFW; + +public class DisplayBuilder +{ + private Display display; + + public DisplayBuilder() + { + this.display = new Display(); + } + + public DisplayBuilder setInitialSize(int width, int height) + { + this.display.width = width; + this.display.height = height; + + return this; + } + + public DisplayBuilder hintResizeable(boolean resizeable) + { + GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, resizeable ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); + + return this; + } + + public DisplayBuilder hintVisible(boolean visible) + { + GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, visible ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); + + return this; + } + + public DisplayBuilder hintMSAA(int samples) + { + GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, samples); + + return this; + } + + public DisplayBuilder hintDebugContext(boolean enabled) + { + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_DEBUG_CONTEXT, enabled ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE); + this.display.debugMode = enabled; + + return this; + } + + public DisplayBuilder hintOpenGLVersion(int major, int minor) + { + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, major); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, minor); + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); + + return this; + } + + public Display export() + { + return this.display; + } + + public static void initGLFW() + { + if (!GLFW.glfwInit()) + { + throw new IllegalStateException("Could not init GLFW!"); + } + } + + public static void destroyGLFW() + { + GLFW.glfwTerminate(); + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayErrorCallback.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayErrorCallback.java new file mode 100644 index 0000000..e69f368 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/DisplayErrorCallback.java @@ -0,0 +1,17 @@ +package cz.tefek.pluto.engine.display; + +import org.lwjgl.glfw.GLFWErrorCallback; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +public class DisplayErrorCallback extends GLFWErrorCallback +{ + @Override + public void invoke(int error, long description) + { + Logger.logf(SmartSeverity.ERROR, "GLFW Error code %d:\n", error); + Logger.logf(getDescription(description)); + } + +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Framerate.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Framerate.java new file mode 100644 index 0000000..bcda317 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/Framerate.java @@ -0,0 +1,75 @@ +package cz.tefek.pluto.engine.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 FPS = Double.NaN; + + private static int interpolatedFPS; + + private static boolean firstRemoved = false; + + private static Deque drawTimestamps = new LinkedBlockingDeque<>(); + + public static double getFrameTime() + { + return frameTime; + } + + public static double getFPS() + { + return FPS; + } + + public static int getInterpolatedFPS() + { + return interpolatedFPS; + } + + public static void tick() + { + var now = System.nanoTime(); + + if (lastDraw > 0) + { + var frameTimeNs = now - lastDraw; + frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(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; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/display/package-info.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/package-info.java new file mode 100644 index 0000000..f32cb0e --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/display/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities for display handling. + * + * @author 493msi + * + */ +package cz.tefek.pluto.engine.display; diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/event/IEvent.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/event/IEvent.java new file mode 100644 index 0000000..9aaae0d --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/event/IEvent.java @@ -0,0 +1,12 @@ +package cz.tefek.pluto.engine.event; + +/** + * Base interface of an event, extend this to create custom events. + * + * @author 493msi + * @since 0.1 + */ +public interface IEvent +{ + +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/event/package-info.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/event/package-info.java new file mode 100644 index 0000000..252e530 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/event/package-info.java @@ -0,0 +1,7 @@ +/** + * Tools to create both annotation-based and interface based handlers. + * + * @author 493msi + * + */ +package cz.tefek.pluto.engine.event; diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/GLDebugInfo.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/GLDebugInfo.java new file mode 100644 index 0000000..fbf76a8 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/GLDebugInfo.java @@ -0,0 +1,41 @@ +package cz.tefek.pluto.engine.gl; + +import org.lwjgl.opengl.ARBFramebufferObject; +import org.lwjgl.opengl.ARBUniformBufferObject; +import org.lwjgl.opengl.GL33; +import org.lwjgl.opengl.GLCapabilities; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.SmartSeverity; + +public class GLDebugInfo +{ + public static void printDebugInfo(GLCapabilities glCapabilities) + { + Logger.logf(SmartSeverity.INFO, "OpenGL20: %b\n", glCapabilities.OpenGL20); + Logger.logf(SmartSeverity.INFO, "OpenGL21: %b\n", glCapabilities.OpenGL21); + + Logger.logf(SmartSeverity.INFO, "OpenGL30: %b\n", glCapabilities.OpenGL30); + + Logger.logf(SmartSeverity.INFO, "OpenGL33: %b\n", glCapabilities.OpenGL33); + + Logger.logf(SmartSeverity.INFO, "OpenGL40: %b\n", glCapabilities.OpenGL40); + + Logger.logf(SmartSeverity.INFO, "OpenGL45: %b\n", glCapabilities.OpenGL45); + + Logger.logf(SmartSeverity.INFO, "GL_MAX_TEXTURE_SIZE: %d\n", GL33.glGetInteger(GL33.GL_MAX_TEXTURE_SIZE)); + Logger.logf(SmartSeverity.INFO, "GL_MAX_VERTEX_ATTRIBS: %d\n", GL33.glGetInteger(GL33.GL_MAX_VERTEX_ATTRIBS)); + Logger.logf(SmartSeverity.INFO, "GL_MAX_TEXTURE_IMAGE_UNITS: %d\n", GL33.glGetInteger(GL33.GL_MAX_TEXTURE_IMAGE_UNITS)); + + Logger.logf(SmartSeverity.INFO, "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d\n", GL33.glGetInteger(GL33.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)); + + Logger.logf(SmartSeverity.INFO, "ARBFramebufferObject.GL_MAX_RENDERBUFFER_SIZE: %d\n", GL33.glGetInteger(ARBFramebufferObject.GL_MAX_RENDERBUFFER_SIZE)); + Logger.logf(SmartSeverity.INFO, "ARBFramebufferObject.GL_MAX_SAMPLES: %d\n", GL33.glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES)); + Logger.logf(SmartSeverity.INFO, "ARBFramebufferObject.GL_MAX_COLOR_ATTACHMENTS: %d\n", GL33.glGetInteger(ARBFramebufferObject.GL_MAX_COLOR_ATTACHMENTS)); + + Logger.logf(SmartSeverity.INFO, "GL_ARB_uniform_buffer_object: %b\n", glCapabilities.GL_ARB_uniform_buffer_object); + + Logger.logf(SmartSeverity.INFO, "GL_MAX_UNIFORM_BLOCK_SIZE : %d bytes\n", GL33.glGetInteger(ARBUniformBufferObject.GL_MAX_UNIFORM_BLOCK_SIZE)); + + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/IOpenGLEnum.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/IOpenGLEnum.java new file mode 100644 index 0000000..d212783 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/gl/IOpenGLEnum.java @@ -0,0 +1,13 @@ +package cz.tefek.pluto.engine.gl; + +/** + * Denotes the implementing class is a set of OpenGL enums. + * + * + * @author 493msi + * + */ +public interface IOpenGLEnum +{ + public int getGLID(); +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ClampedSineWave.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ClampedSineWave.java new file mode 100644 index 0000000..7a4a04c --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ClampedSineWave.java @@ -0,0 +1,72 @@ +package cz.tefek.pluto.engine.math; + +/** + * A clamped sine wave generator, for animations, mostly. + * + * @since 0.2 + * @author 493msi + */ +public class ClampedSineWave +{ + /** + * Gets a clamped value of the abs(sine) function from the given parameters as a + * {@link double}. + * + * @param animationProgress The animation progress in frames. + * @param frameOffset The animation offset in frames. + * @param animationFrames The total amount of animation frames. + * @param bottomClamp The sine wave clamp, minimum value. + * @param topClamp The sine wave clamp, maximum value. + * + * @return The resulting value + * + * @since 0.2 + * @author 493msi + */ + public static double getAbsolute(double animationProgress, double frameOffset, double animationFrames, double bottomClamp, double topClamp) + { + var actualProgress = (animationProgress + frameOffset) / animationFrames; + return Math.min(Math.max(Math.abs(Math.sin(actualProgress * Math.PI)), bottomClamp), topClamp); + } + + /** + * Gets a clamped value of the sine function from the given parameters as a + * {@link double}. + * + * @param animationProgress The animation progress in frames. + * @param frameOffset The animation offset in frames. + * @param animationFrames The total amount of animation frames. + * @param bottomClamp The sine wave clamp, minimum value. + * @param topClamp The sine wave clamp, maximum value. + * + * @return The resulting value + * + * @since 0.2 + * @author 493msi + */ + public static double get(double animationProgress, double frameOffset, double animationFrames, float bottomClamp, float topClamp) + { + var actualProgress = (animationProgress + frameOffset) / animationFrames; + return Math.min(Math.max(Math.sin(actualProgress * Math.PI), bottomClamp), topClamp); + } + + /** + * Gets a clamped value of the sine function from the given parameters as a + * {@link double}. + * + * @param animationProgress The animation progress in frames. + * @param animationFrames The total amount of animation frames. + * @param bottomClamp The sine wave clamp, minimum value. + * @param topClamp The sine wave clamp, maximum value. + * + * @return The resulting value + * + * @since 0.2 + * @author 493msi + */ + public static double get(double animationProgress, double animationFrames, float bottomClamp, float topClamp) + { + var progress = animationProgress / animationFrames; + return Math.min(Math.max(Math.sin(progress * Math.PI), bottomClamp), topClamp); + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezier.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezier.java new file mode 100644 index 0000000..eccc8f2 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezier.java @@ -0,0 +1,80 @@ +package cz.tefek.pluto.engine.math; + +/** + * A class to generate a cubic bezier interpolation function. Not very + * optimized, so use it sparsely. + * + * @since 0.2 + * @author 493msi + */ +public class CubicBezier +{ + private static final int iterations = 16; + private double a, b, c, d; + + /** + * Creates a new {@code CubicBezier} from the given parameters. + * + * @param cpx1 the X position of the direction the function "steers towards" + * @param cpy1 the Y position of the direction the function "steers towards" + * @param cpx2 the X position of the direction the function "arrives from" + * @param cpy2 the Y position of the direction the function "arrives from" + * + * @since 0.2 + * @author 493msi + */ + public CubicBezier(double cpx1, double cpy1, double cpx2, double cpy2) + { + if (cpx1 < 0 || cpx1 > 1 || cpx2 < 0 || cpx2 > 1) + { + throw new IllegalArgumentException("Parameter out of range, only 0..1 is supported (only one Y value in 0..1 for each X in 0..1)."); + } + + this.a = cpx1; + this.b = cpy1; + this.c = cpx2; + this.d = cpy2; + } + + /** + * Solves this {@code CubicBezier} for the given x and the supplied + * parameters in the constructor. + * + * @param xIn the input X position + * + * @return the approximate Y position for the given X position + * + * @since 0.2 + * @author 493msi + */ + public double forX(double xIn) + { + if (xIn < 0) + return forX(0); + + if (xIn > 1) + return forX(1); + + double t = 0.5; + + double x; + double y = 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * d + t * t * t; + + double delta = 0.25; + boolean uh; + + for (int i = 0; i < iterations; i++) + { + x = 3 * (1 - t) * (1 - t) * t * a + 3 * (1 - t) * t * t * c + t * t * t; + y = 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * d + t * t * t; + + uh = x > xIn; + + t += uh ? -delta : delta; + + delta /= 2; + } + + return y; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezierLT.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezierLT.java new file mode 100644 index 0000000..3703dba --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/CubicBezierLT.java @@ -0,0 +1,65 @@ +package cz.tefek.pluto.engine.math; + +/** + * A class to generate a cubic bezier interpolation function. This + * implementation creates a lookup table. + * + * @since 0.2 + * @author 493msi + */ +public class CubicBezierLT +{ + private static final int tableSize = 1 << 11; + private final double[] values; + private final double upperBound = tableSize - 1.0; + + /** + * Creates a new {@code CubicBezierLT} from the given parameters. + * + * @param cpx1 the X position of the direction the function "steers towards" + * @param cpy1 the Y position of the direction the function "steers towards" + * @param cpx2 the X position of the direction the function "arrives from" + * @param cpy2 the Y position of the direction the function "arrives from" + * + * @since 0.2 + * @author 493msi + */ + public CubicBezierLT(double cpx1, double cpy1, double cpx2, double cpy2) + { + if (cpx1 < 0 || cpx1 > 1 || cpx2 < 0 || cpx2 > 1) + { + throw new IllegalArgumentException("Parameter out of range, only 0..1 is supported (only one Y value in 0..1 for each X in 0..1)."); + } + + var cubicBezier = new CubicBezier(cpx1, cpy1, cpx2, cpy2); + + this.values = new double[tableSize]; + + for (int i = 0; i < tableSize; i++) + { + values[i] = cubicBezier.forX(i / upperBound); + } + } + + /** + * Retrives the approximate value for the given x and the supplied + * parameters in the constructor. + * + * @param xIn the input X position + * + * @return the approximate Y position for the given X position + * + * @since 0.2 + * @author 493msi + */ + public double forX(double xIn) + { + if (xIn < 0) + return forX(0); + + if (xIn > 1) + return forX(1); + + return values[(int) (xIn * upperBound)]; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ProjectionMatrix.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ProjectionMatrix.java new file mode 100644 index 0000000..2660725 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ProjectionMatrix.java @@ -0,0 +1,66 @@ +package cz.tefek.pluto.engine.math; + +import org.joml.Matrix4f; + +public class ProjectionMatrix +{ + /** + * Create a 2D orthogonal projection Matrix4f based on the width and height. + * + * @param width The ortho width + * @param height The ortho height + * + * @return the matrix + * + * @author 493msi + * @since 0.1 + */ + public static Matrix4f createOrtho2D(int width, int height) + { + var orthoMatrix = new Matrix4f(); + orthoMatrix.setOrtho2D(0, width, height, 0); + + return orthoMatrix; + } + + /** + * Create a centered 2D orthogonal projection Matrix3x2f based on the width and + * height. + * + * @param width The ortho width + * @param height The ortho height + * + * @return the matrix + * + * @author 493msi + * @since 0.3 + */ + public static Matrix4f createOrtho2DCentered(int width, int height) + { + var orthoMatrix = new Matrix4f(); + orthoMatrix.setOrtho2D(width / 2.0f, width / 2.0f, height / 2.0f, height / 2.0f); + + return orthoMatrix; + } + + /** + * Create a perspective frustum based on the parameters. + * + * @param aspectRatio The aspect ratio of the frustum + * @param fov The fov of the frustum + * @param zNear The distance of the zNear clipping plane + * @param zFar The distance of the zFar clipping plane + * + * @return the perspective matrix + * + * @author 493msi + * @since 0.1 + */ + public static Matrix4f createPerspective(float aspectRatio, float fov, float zNear, float zFar) + { + var perspective = new Matrix4f(); + perspective.setPerspective(fov, aspectRatio, zNear, zFar); + + return perspective; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/TransformationMatrix.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/TransformationMatrix.java new file mode 100644 index 0000000..03859f5 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/TransformationMatrix.java @@ -0,0 +1,113 @@ +package cz.tefek.pluto.engine.math; + +import org.joml.Matrix3x2f; +import org.joml.Matrix4f; +import org.joml.Vector2fc; +import org.joml.Vector3fc; + +public class TransformationMatrix +{ + /** + *

+ * Creates a transformation matrix from the given translation, rotation and + * scale. + *

+ *

+ * The rotation is specified as rotating around the Z axis (roll), then rotating + * around the X axis (pitch) and finally rotating around the Y axis (yaw). + *

+ * + * @param translation The translation vector + * @param rotation The rotation angles + * @param scale The scale vector + * + * @author 493msi + * @since 0.1 + */ + public static Matrix4f create(Vector3fc translation, Vector3fc yxzRotation, Vector3fc scale) + { + var transformation = new Matrix4f(); + transformation.translate(translation); + transformation.rotateAffineYXZ(yxzRotation.x(), yxzRotation.y(), yxzRotation.z()); + transformation.scale(scale); + + return transformation; + } + + /** + *

+ * Creates a transformation matrix from the given translation, rotation and + * scale. + *

+ * + * @param x The translation on the X axis + * @param y The translation on the Y axis + * @param z The translation on the Z axis + * @param pitch The pitch rotation + * @param yaw The yaw rotation + * @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 + * + * @author 493msi + * @since 0.3 + */ + public static Matrix4f create(float x, float y, float z, float pitch, float yaw, float roll, float scaleX, float scaleY, float scaleZ) + { + var transformation = new Matrix4f(); + transformation.translate(x, y, z); + transformation.rotateAffineYXZ(pitch, yaw, roll); + transformation.scale(scaleX, scaleY, scaleZ); + + return transformation; + } + + /** + *

+ * Creates a 2D transformation matrix from the given translation, rotation and + * scale. + *

+ * + * @param translation The translation vector + * @param rotation The rotation angle + * @param scale The scale vector + * + * @author 493msi + * @since 0.3 + */ + public static Matrix3x2f create2D(Vector2fc translation, float rotation, Vector2fc scale) + { + var transformation = new Matrix3x2f(); + transformation.translate(translation.x(), translation.y()); + transformation.rotate(rotation); + transformation.scale(scale); + + return transformation; + } + + /** + *

+ * Creates a 2D transformation matrix from the given translation, rotation and + * scale. + *

+ * + * @param x The translation on the X axis + * @param y The translation on the Y axis + * @param rotation The rotation angle + * @param scaleX The scale on the X axis + * @param scaleY The scale on the Y axis + * + * @author 493msi + * @since 0.3 + */ + public static Matrix3x2f create2D(float x, float y, float rotation, float scaleX, float scaleY) + { + var transformation = new Matrix3x2f(); + transformation.translate(x, y); + transformation.rotate(rotation); + transformation.scale(scaleX, scaleY); + + return transformation; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ViewMatrix.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ViewMatrix.java new file mode 100644 index 0000000..43078e9 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/ViewMatrix.java @@ -0,0 +1,33 @@ +package cz.tefek.pluto.engine.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 + * + * @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; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionClass.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionClass.java new file mode 100644 index 0000000..8982a38 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionClass.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.engine.math.collision; + +public class CollisionClass +{ + private static int idSource = 0; + + private int id = idSource++; + private boolean selfCollision; + + public CollisionClass(boolean selfCollision) + { + this.selfCollision = selfCollision; + } + + public int getID() + { + return this.id; + } + + public boolean hasSelfCollision() + { + return this.selfCollision; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurface.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurface.java new file mode 100644 index 0000000..20f0d33 --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurface.java @@ -0,0 +1,83 @@ +package cz.tefek.pluto.engine.math.collision; + +public class CollisionSurface +{ + protected float friction; // 0..1 where 0 is no friction and 1 is superglue + protected float bounciness; // 0..n where 0 is no bounciness and 1 is is a perfect bounce + protected float drag; // 0..n where the value defines how much the surface is affected by air + + public CollisionSurface(float friction, float bounciness, float drag) + { + this.friction = friction; + this.bounciness = bounciness; + this.drag = drag; + } + + /** + * Returns the friction; a value 0..1 where 0 is no friction and 1 is infinite + * friction, making the surfaces impossible to slide on. + * + * @return the friction value + */ + public float getFriction() + { + return this.friction; + } + + /** + * Sets the friction; a value 0..1 where 0 is no friction and 1 is infinite + * friction, making the surfaces impossible to slide on. + * + * @param friction the friction value + */ + public void setFriction(float friction) + { + this.friction = friction; + } + + /** + * Returns the bounciness; a value 0..1..n where 0 means the surface doesn't + * bounce off at all and 1 means the object bounces off at the exact same + * velocity, values above 1 might have some undesirable effects and are + * discouraged. + * + * @return the bounciness + */ + public float getBounciness() + { + return this.bounciness; + } + + /** + * Sets the bounciness; a value 0..1..n where 0 means the surface doesn't bounce + * off at all and 1 means the object bounces off at the exact same velocity, + * values above 1 might have some undesirable effects and are discouraged. + * + * @param bounciness the bounciness value + */ + public void setBounciness(float bounciness) + { + this.bounciness = bounciness; + } + + /** + * @return the drag + */ + public float getDrag() + { + return this.drag; + } + + /** + * Sets the drag modifier; a value 0..n which defines how much this surface is + * affected by air resistance. A value of 1 should be the golden standard for + * most objects. + * + * @param drag the drag modifier + */ + public void setDrag(float drag) + { + this.drag = drag; + } + +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceCircle.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceCircle.java new file mode 100644 index 0000000..1c400da --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceCircle.java @@ -0,0 +1,13 @@ +package cz.tefek.pluto.engine.math.collision; + +import org.joml.Circlef; + +public class CollisionSurfaceCircle +{ + private Circlef circle; + + public Circlef getCircle() + { + return this.circle; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceLine.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceLine.java new file mode 100644 index 0000000..ca4a2ce --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/collision/CollisionSurfaceLine.java @@ -0,0 +1,18 @@ +package cz.tefek.pluto.engine.math.collision; + +import org.joml.LineSegmentf; + +public class CollisionSurfaceLine extends CollisionSurface +{ + public CollisionSurfaceLine(float friction, float bounciness, float drag) + { + super(friction, bounciness, drag); + } + + protected LineSegmentf lineSegment; + + public LineSegmentf getLineSegment() + { + return this.lineSegment; + } +} diff --git a/plutostatic/src/main/java/cz/tefek/pluto/engine/math/package-info.java b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/package-info.java new file mode 100644 index 0000000..dc3367b --- /dev/null +++ b/plutostatic/src/main/java/cz/tefek/pluto/engine/math/package-info.java @@ -0,0 +1,7 @@ +/** + * Math utility classes. + * + * @author 493msi + * + */ +package cz.tefek.pluto.engine.math; diff --git a/plutotexturing/pom.xml b/plutotexturing/pom.xml new file mode 100644 index 0000000..9a8fbb9 --- /dev/null +++ b/plutotexturing/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + cz.tefek + plutotexturing + 0.1 + plutotexturing + + 11 + 11 + UTF-8 + UTF-8 + + + + cz.tefek + plutostatic + 0.3 + + + \ No newline at end of file diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MagFilter.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MagFilter.java new file mode 100644 index 0000000..7dbeb60 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MagFilter.java @@ -0,0 +1,24 @@ +package cz.tefek.pluto.engine.graphics.texture; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.gl.IOpenGLEnum; + +public enum MagFilter implements IOpenGLEnum +{ + NEAREST(GL33.GL_NEAREST), + LINEAR(GL33.GL_LINEAR); + + MagFilter(int id) + { + this.id = id; + } + + private int id; + + @Override + public int getGLID() + { + return this.id; + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MinFilter.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MinFilter.java new file mode 100644 index 0000000..3eac01d --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/MinFilter.java @@ -0,0 +1,35 @@ +package cz.tefek.pluto.engine.graphics.texture; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.gl.IOpenGLEnum; + +public enum MinFilter implements IOpenGLEnum +{ + NEAREST(GL33.GL_NEAREST, false), + LINEAR(GL33.GL_LINEAR, false), + NEAREST_MIPMAP_NEAREST(GL33.GL_NEAREST_MIPMAP_NEAREST, true), + LINEAR_MIPMAP_NEAREST(GL33.GL_LINEAR_MIPMAP_NEAREST, true), + NEAREST_MIPMAP_LINEAR(GL33.GL_NEAREST_MIPMAP_LINEAR, true), + LINEAR_MIPMAP_LINEAR(GL33.GL_LINEAR_MIPMAP_LINEAR, true); + + MinFilter(int id, boolean isMipMapped) + { + this.id = id; + this.mipMapped = isMipMapped; + } + + private int id; + private boolean mipMapped; + + @Override + public int getGLID() + { + return this.id; + } + + public boolean isMipMapped() + { + return this.mipMapped; + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/Texture.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/Texture.java new file mode 100644 index 0000000..1fc902d --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/Texture.java @@ -0,0 +1,241 @@ +package cz.tefek.pluto.engine.graphics.texture; + +import java.util.Arrays; + +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryUtil; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; + +import cz.tefek.io.asl.resource.ResourceAddress; +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; +import cz.tefek.io.pluto.debug.SmartSeverity; +import cz.tefek.tpl.TPL; +import cz.tefek.tpl.TPNImage; + +public abstract class Texture +{ + protected int glID = 0; + protected final int type; + protected final int dimensions; + + protected int width = 1; + protected int height = 1; + protected int depth = 1; + + protected MinFilter minFilter; + protected MagFilter magFilter; + + protected WrapMode wrapModeU; + protected WrapMode wrapModeV; + protected WrapMode wrapModeW; + + public Texture(int type, int dimensions) + { + this.glID = GL33.glGenTextures(); + this.type = type; + this.dimensions = dimensions; + + Logger.logf(SmartSeverity.ADDED, "Texture with ID %d of type '%s' created...\n", this.glID, this.getClass().getSimpleName()); + } + + public void bind() + { + GL33.glBindTexture(this.type, this.glID); + } + + public void unbind() + { + GL33.glBindTexture(this.type, 0); + } + + public void delete() + { + Logger.logf(SmartSeverity.REMOVED, "Texture with ID %d of type '%s' deleted...\n", this.glID, this.getClass().getSimpleName()); + + // Delete unbinds automatically + GL33.glDeleteTextures(this.glID); + + this.glID = 0; + + this.width = 0; + this.height = 0; + this.depth = 0; + + this.wrapModeU = null; + this.wrapModeV = null; + this.wrapModeW = null; + + this.minFilter = null; + this.magFilter = null; + } + + public int getDepth() + { + return this.depth; + } + + public int getHeight() + { + return this.height; + } + + public int getWidth() + { + return this.width; + } + + public MagFilter getMagFilter() + { + return this.magFilter; + } + + public MinFilter getMinFilter() + { + return this.minFilter; + } + + public WrapMode getWrapModeU() + { + return this.wrapModeU; + } + + public WrapMode getWrapModeV() + { + return this.wrapModeV; + } + + public WrapMode getWrapModeW() + { + return this.wrapModeW; + } + + public Texture setFilteringOptions(MagFilter magFilter, MinFilter minFilter) + { + if (minFilter.isMipMapped()) + { + if (!this.supportsMipMapping()) + { + Logger.logf(SmartSeverity.ERROR, "The texture of type '%s' does not support mipmaps.\n", this.getClass().getSimpleName()); + return this; + } + + GL33.glGenerateMipmap(this.type); + } + + this.magFilter = magFilter; + GL33.glTexParameteri(this.type, GL33.GL_TEXTURE_MAG_FILTER, this.magFilter.getGLID()); + + this.minFilter = minFilter; + GL33.glTexParameteri(this.type, GL33.GL_TEXTURE_MIN_FILTER, this.minFilter.getGLID()); + + return this; + } + + public Texture setWrapOptions(WrapMode... wrapOptions) + { + if (wrapOptions.length != this.dimensions) + { + Logger.log(Severity.ERROR, "Error: WrapMode option count does not match texture's dimensions."); + return this; + } + + this.wrapModeU = wrapOptions[0]; + GL33.glTexParameteri(this.type, GL33.GL_TEXTURE_WRAP_S, this.wrapModeU.getGLID()); + + if (this.dimensions >= 2) + { + this.wrapModeV = wrapOptions[1]; + GL33.glTexParameteri(this.type, GL33.GL_TEXTURE_WRAP_T, this.wrapModeV.getGLID()); + + if (this.dimensions >= 3) + { + this.wrapModeW = wrapOptions[2]; + GL33.glTexParameteri(this.type, GL33.GL_TEXTURE_WRAP_R, this.wrapModeW.getGLID()); + } + } + + return this; + } + + public void writeData(ByteBuffer buffer) + { + this.writeData(MemoryUtil.memAddress(buffer)); + } + + public abstract boolean supportsMipMapping(); + + public abstract void writeData(long address); + + public int getID() + { + return this.glID; + } + + protected WrapMode getDefaultWrapMode() + { + return WrapMode.REPEAT; + } + + public void load(String file) + { + var wrap = new WrapMode[this.dimensions]; + Arrays.fill(wrap, this.getDefaultWrapMode()); + this.load(file, MagFilter.LINEAR, MinFilter.LINEAR, wrap); + } + + public void load(ResourceAddress file) + { + var wrap = new WrapMode[this.dimensions]; + Arrays.fill(wrap, this.getDefaultWrapMode()); + this.load(file.toPath(), MagFilter.LINEAR, MinFilter.LINEAR, wrap); + } + + public void load(ResourceAddress file, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap) + { + this.load(file.toPath(), magFilter, minFilter, wrap); + } + + public void load(String file, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap) + { + TPNImage image = TPL.load(file); + this.load(image, magFilter, minFilter, wrap); + } + + public void load(BufferedImage imageIn, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap) + { + TPNImage image = TPL.loadImage(imageIn); + this.load(image, magFilter, minFilter, wrap); + } + + public void load(TPNImage image, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap) + { + this.load(image.getData(), image.getWidth(), image.getHeight(), magFilter, minFilter, wrap); + } + + public void load(ByteBuffer imageData, int width, int height, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap) + { + this.width = width; + this.height = height; + + this.bind(); + + GL33.glTexParameteriv(this.type, GL33.GL_TEXTURE_SWIZZLE_RGBA, new int[] { GL33.GL_ALPHA, GL33.GL_BLUE, GL33.GL_GREEN, GL33.GL_RED }); // TODO: Temp solution until I find a smart way to swizzle BufferedImage data + + this.setFilteringOptions(magFilter, minFilter); + this.setWrapOptions(wrap); + this.writeData(imageData); + } + + public boolean isValid() + { + return this.glID > 0; + } + + public int getType() + { + return this.type; + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/WrapMode.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/WrapMode.java new file mode 100644 index 0000000..1dfe740 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/WrapMode.java @@ -0,0 +1,34 @@ +package cz.tefek.pluto.engine.graphics.texture; + +import java.util.EnumSet; + +import org.lwjgl.opengl.GL33; +import org.lwjgl.opengl.GL44; + +import cz.tefek.pluto.engine.gl.IOpenGLEnum; + +public enum WrapMode implements IOpenGLEnum +{ + REPEAT(GL33.GL_REPEAT), + CLAMP_TO_EDGE(GL33.GL_CLAMP_TO_EDGE), + CLAMP_TO_BORDER(GL33.GL_CLAMP_TO_BORDER), + MIRROR_CLAMP_TO_EDGE(GL44.GL_MIRROR_CLAMP_TO_EDGE), + MIRRORED_REPEAT(GL33.GL_MIRRORED_REPEAT); + + WrapMode(int id) + { + this.id = id; + } + + public static final EnumSet repeatModes = EnumSet.of(WrapMode.MIRRORED_REPEAT, WrapMode.REPEAT); + public static final EnumSet clampModes = EnumSet.of(WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_BORDER, MIRROR_CLAMP_TO_EDGE); + public static final EnumSet mirrorModes = EnumSet.of(WrapMode.MIRROR_CLAMP_TO_EDGE, WrapMode.MIRRORED_REPEAT); + + private int id; + + @Override + public int getGLID() + { + return this.id; + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler2D.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler2D.java new file mode 100644 index 0000000..145fdb8 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler2D.java @@ -0,0 +1,54 @@ +package cz.tefek.pluto.engine.graphics.texture.sampler; + +import java.util.HashSet; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; + +public class Sampler2D +{ + private int id; + + private HashSet usedUnits; + + public Sampler2D() + { + this.id = GL33.glGenSamplers(); + this.usedUnits = new HashSet<>(GL33.glGetInteger(GL33.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)); + } + + public void setFilteringParameters(MagFilter magFilter, MinFilter minFilter, WrapMode wrapModeS, WrapMode wrapModeT) + { + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_MIN_FILTER, minFilter.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_MAG_FILTER, magFilter.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_WRAP_S, wrapModeS.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_WRAP_T, wrapModeT.getGLID()); + } + + public void bind(int unit) + { + this.usedUnits.add(unit); + GL33.glBindSampler(unit, this.id); + } + + public void delete() + { + this.unbind(); + GL33.glDeleteSamplers(this.id); + } + + public void unbind(int unit) + { + GL33.glBindSampler(unit, 0); + this.usedUnits.remove(unit); + } + + public void unbind() + { + this.usedUnits.forEach(this::unbind); + this.usedUnits.clear(); + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler3D.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler3D.java new file mode 100644 index 0000000..c632859 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/sampler/Sampler3D.java @@ -0,0 +1,55 @@ +package cz.tefek.pluto.engine.graphics.texture.sampler; + +import java.util.HashSet; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.texture.MagFilter; +import cz.tefek.pluto.engine.graphics.texture.MinFilter; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; + +public class Sampler3D +{ + private int id; + + private HashSet usedUnits; + + public Sampler3D() + { + this.id = GL33.glGenSamplers(); + this.usedUnits = new HashSet<>(GL33.glGetInteger(GL33.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)); + } + + public void setFilteringParameters(MinFilter minFilter, MagFilter magFilter, WrapMode wrapModeS, WrapMode wrapModeT, WrapMode wrapModeR) + { + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_MIN_FILTER, minFilter.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_MAG_FILTER, magFilter.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_WRAP_S, wrapModeS.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_WRAP_T, wrapModeT.getGLID()); + GL33.glSamplerParameteri(this.id, GL33.GL_TEXTURE_WRAP_R, wrapModeR.getGLID()); + } + + public void bind(int unit) + { + this.usedUnits.add(unit); + GL33.glBindSampler(unit, this.id); + } + + public void delete() + { + this.unbind(); + GL33.glDeleteSamplers(this.id); + } + + public void unbind(int unit) + { + GL33.glBindSampler(unit, 0); + this.usedUnits.remove(unit); + } + + public void unbind() + { + this.usedUnits.forEach(this::unbind); + this.usedUnits.clear(); + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/RectangleTexture.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/RectangleTexture.java new file mode 100644 index 0000000..f5ce622 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/RectangleTexture.java @@ -0,0 +1,49 @@ +package cz.tefek.pluto.engine.graphics.texture.texture2d; + +import java.util.Arrays; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.io.pluto.debug.Logger; +import cz.tefek.io.pluto.debug.Severity; +import cz.tefek.pluto.engine.graphics.texture.Texture; +import cz.tefek.pluto.engine.graphics.texture.WrapMode; + +public class RectangleTexture extends Texture +{ + public RectangleTexture() + { + super(GL33.GL_TEXTURE_RECTANGLE, 2); + } + + @Override + public boolean supportsMipMapping() + { + return false; + } + + @Override + public Texture setWrapOptions(WrapMode... wrapOptions) + { + if (Arrays.stream(wrapOptions).anyMatch(WrapMode.repeatModes::contains)) + { + Logger.log(Severity.ERROR, "Error: Rectangle textures do not support repeat wrap modes!"); + + return this; + } + + return super.setWrapOptions(wrapOptions); + } + + @Override + public void writeData(long address) + { + GL33.glTexImage2D(this.type, 0, GL33.GL_RGBA8, this.width, this.height, 0, GL33.GL_RGBA, GL33.GL_UNSIGNED_BYTE, address); + } + + @Override + protected WrapMode getDefaultWrapMode() + { + return WrapMode.CLAMP_TO_EDGE; + } +} diff --git a/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/Texture2D.java b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/Texture2D.java new file mode 100644 index 0000000..3916226 --- /dev/null +++ b/plutotexturing/src/main/java/cz/tefek/pluto/engine/graphics/texture/texture2d/Texture2D.java @@ -0,0 +1,29 @@ +package cz.tefek.pluto.engine.graphics.texture.texture2d; + +import org.lwjgl.opengl.GL33; + +import cz.tefek.pluto.engine.graphics.texture.Texture; + +/** + * @author 493msi + * + */ +public class Texture2D extends Texture +{ + public Texture2D() + { + super(GL33.GL_TEXTURE_2D, 2); + } + + @Override + public void writeData(long address) + { + GL33.glTexImage2D(this.type, 0, GL33.GL_RGBA8, this.width, this.height, 0, GL33.GL_RGBA, GL33.GL_UNSIGNED_BYTE, address); + } + + @Override + public boolean supportsMipMapping() + { + return true; + } +}