From e96a30c275917de1e12a89a2ca8727c38aa2ca57 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Wed, 29 Jul 2020 12:30:34 +0300 Subject: [PATCH] Initial commit --- .gitattributes | 6 + .gitignore | 17 + LICENSE | 674 ++++++++++++++++++ build.gradle | 73 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 +++++ gradlew.bat | 100 +++ settings.gradle | 1 + src/main/java/ru/windcorp/optica/Optica.java | 22 + .../ru/windcorp/optica/OpticaLauncher.java | 26 + src/main/java/ru/windcorp/optica/Proxy.java | 24 + .../windcorp/optica/client/ClientProxy.java | 42 ++ .../optica/client/OpticaClientMain.java | 28 + .../ru/windcorp/optica/client/TestLayer.java | 167 +++++ .../windcorp/optica/client/graphics/GUI.java | 47 ++ .../optica/client/graphics/Layer.java | 47 ++ .../graphics/backend/GraphicsBackend.java | 103 +++ .../graphics/backend/GraphicsInterface.java | 74 ++ .../client/graphics/backend/InputHandler.java | 107 +++ .../graphics/backend/LWJGLInitializer.java | 97 +++ .../graphics/backend/OpenGLObjectTracker.java | 45 ++ .../graphics/backend/RenderTaskQueue.java | 107 +++ .../client/graphics/backend/RenderThread.java | 75 ++ .../optica/client/graphics/backend/Usage.java | 38 + .../graphics/backend/VertexBufferObject.java | 203 ++++++ .../backend/shaders/CombinedShader.java | 93 +++ .../graphics/backend/shaders/Program.java | 63 ++ .../graphics/backend/shaders/Shader.java | 106 +++ .../backend/shaders/attributes/Attribute.java | 48 ++ .../attributes/AttributeVertexArray.java | 114 +++ .../backend/shaders/uniforms/Uniform.java | 88 +++ .../shaders/uniforms/Uniform1Float.java | 43 ++ .../backend/shaders/uniforms/Uniform1Int.java | 43 ++ .../shaders/uniforms/Uniform2Float.java | 49 ++ .../backend/shaders/uniforms/Uniform2Int.java | 49 ++ .../shaders/uniforms/Uniform2Matrix.java | 39 + .../shaders/uniforms/Uniform3Float.java | 49 ++ .../backend/shaders/uniforms/Uniform3Int.java | 49 ++ .../shaders/uniforms/Uniform3Matrix.java | 47 ++ .../shaders/uniforms/Uniform4Float.java | 49 ++ .../backend/shaders/uniforms/Uniform4Int.java | 49 ++ .../shaders/uniforms/Uniform4Matrix.java | 47 ++ .../client/graphics/input/CursorEvent.java | 40 ++ .../graphics/input/CursorMoveEvent.java | 129 ++++ .../client/graphics/input/InputEvent.java | 30 + .../client/graphics/input/KeyEvent.java | 96 +++ .../client/graphics/model/DynamicModel.java | 124 ++++ .../client/graphics/model/EmptyModel.java | 45 ++ .../optica/client/graphics/model/Face.java | 299 ++++++++ .../optica/client/graphics/model/Faces.java | 126 ++++ .../optica/client/graphics/model/Model.java | 48 ++ .../optica/client/graphics/model/Shape.java | 199 ++++++ .../graphics/model/ShapeRenderProgram.java | 298 ++++++++ .../optica/client/graphics/model/Shapes.java | 218 ++++++ .../client/graphics/model/StaticModel.java | 86 +++ .../graphics/model/WorldRenderable.java | 26 + .../client/graphics/texture/Pixels.java | 77 ++ .../graphics/texture/SimpleTexture.java | 33 + .../client/graphics/texture/Sprite.java | 56 ++ .../client/graphics/texture/Texture.java | 24 + .../graphics/texture/TextureManager.java | 144 ++++ .../graphics/texture/TexturePrimitive.java | 101 +++ .../optica/client/graphics/world/Camera.java | 121 ++++ .../client/graphics/world/LayerWorld.java | 148 ++++ .../client/graphics/world/WorldRenderer.java | 78 ++ .../optica/client/world/ChunkRender.java | 169 +++++ .../optica/client/world/WorldRender.java | 53 ++ .../client/world/renders/BlockRender.java | 54 ++ .../client/world/renders/BlockRenderNone.java | 34 + .../renders/BlockRenderTexturedCube.java | 63 ++ .../renders/BlockRenderTransparentCube.java | 38 + .../client/world/renders/BlockRenders.java | 65 ++ .../renders/bro/BlockRenderOpaqueCube.java | 40 ++ .../bro/BlockRenderOpaqueCubeOptimizer.java | 187 +++++ .../renders/bro/BlockRenderOptimizer.java | 32 + .../bro/BlockRenderOptimizerGenerator.java | 30 + .../bro/BlockRenderOptimizerGenerators.java | 52 ++ .../optica/common/block/BlockData.java | 28 + .../common/block/BlockDataRegistry.java | 35 + .../optica/common/block/BlockFace.java | 24 + .../optica/common/resource/Resource.java | 58 ++ .../common/resource/ResourceManager.java | 26 + .../optica/common/util/CoordinatePacker.java | 69 ++ .../ru/windcorp/optica/common/util/Named.java | 61 ++ .../optica/common/util/Namespaced.java | 91 +++ .../optica/common/util/StashingStack.java | 227 ++++++ .../optica/common/util/ThrowingRunnable.java | 63 ++ .../optica/common/world/ChunkData.java | 114 +++ .../optica/common/world/WorldData.java | 50 ++ .../assets/shaders/Shape.fragment.glsl | 53 ++ .../assets/shaders/Shape.vertex.glsl | 27 + .../assets/shaders/WorldDefault.fragment.glsl | 8 + .../assets/shaders/WorldDefault.vertex.glsl | 6 + .../resources/assets/textures/compass.png | Bin 0 -> 256 bytes src/main/resources/assets/textures/glass.png | Bin 0 -> 644 bytes .../resources/assets/textures/glass_clear.png | Bin 0 -> 5475 bytes .../assets/textures/grass_bottom.png | Bin 0 -> 816 bytes .../resources/assets/textures/grass_side.png | Bin 0 -> 752 bytes .../resources/assets/textures/grass_top.png | Bin 0 -> 792 bytes .../resources/assets/textures/side_east.png | Bin 0 -> 588 bytes .../resources/assets/textures/side_north.png | Bin 0 -> 609 bytes .../resources/assets/textures/side_south.png | Bin 0 -> 591 bytes .../resources/assets/textures/side_west.png | Bin 0 -> 604 bytes src/main/resources/assets/textures/stone.png | Bin 0 -> 1179 bytes .../optica/util/CoordinatePackerTest.java | 54 ++ .../windcorp/optica/util/NamespacedTest.java | 153 ++++ .../optica/util/StashingStackTest.java | 83 +++ 108 files changed, 7929 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/ru/windcorp/optica/Optica.java create mode 100644 src/main/java/ru/windcorp/optica/OpticaLauncher.java create mode 100644 src/main/java/ru/windcorp/optica/Proxy.java create mode 100644 src/main/java/ru/windcorp/optica/client/ClientProxy.java create mode 100644 src/main/java/ru/windcorp/optica/client/OpticaClientMain.java create mode 100644 src/main/java/ru/windcorp/optica/client/TestLayer.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/GUI.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/Layer.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/OpenGLObjectTracker.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/RenderTaskQueue.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/RenderThread.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/Usage.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/VertexBufferObject.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/CombinedShader.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Program.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Shader.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/Attribute.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/AttributeVertexArray.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Float.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Int.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Float.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Int.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Matrix.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Float.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Int.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Matrix.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Float.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Int.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Matrix.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/CursorMoveEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/InputEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/DynamicModel.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/Face.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/Model.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/StaticModel.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/SimpleTexture.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/Sprite.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/Texture.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java create mode 100644 src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/ChunkRender.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/WorldRender.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderNone.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTransparentCube.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCube.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizer.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerator.java create mode 100644 src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerators.java create mode 100644 src/main/java/ru/windcorp/optica/common/block/BlockData.java create mode 100644 src/main/java/ru/windcorp/optica/common/block/BlockDataRegistry.java create mode 100644 src/main/java/ru/windcorp/optica/common/block/BlockFace.java create mode 100644 src/main/java/ru/windcorp/optica/common/resource/Resource.java create mode 100644 src/main/java/ru/windcorp/optica/common/resource/ResourceManager.java create mode 100644 src/main/java/ru/windcorp/optica/common/util/CoordinatePacker.java create mode 100644 src/main/java/ru/windcorp/optica/common/util/Named.java create mode 100644 src/main/java/ru/windcorp/optica/common/util/Namespaced.java create mode 100644 src/main/java/ru/windcorp/optica/common/util/StashingStack.java create mode 100644 src/main/java/ru/windcorp/optica/common/util/ThrowingRunnable.java create mode 100644 src/main/java/ru/windcorp/optica/common/world/ChunkData.java create mode 100644 src/main/java/ru/windcorp/optica/common/world/WorldData.java create mode 100644 src/main/resources/assets/shaders/Shape.fragment.glsl create mode 100644 src/main/resources/assets/shaders/Shape.vertex.glsl create mode 100644 src/main/resources/assets/shaders/WorldDefault.fragment.glsl create mode 100644 src/main/resources/assets/shaders/WorldDefault.vertex.glsl create mode 100644 src/main/resources/assets/textures/compass.png create mode 100644 src/main/resources/assets/textures/glass.png create mode 100644 src/main/resources/assets/textures/glass_clear.png create mode 100644 src/main/resources/assets/textures/grass_bottom.png create mode 100644 src/main/resources/assets/textures/grass_side.png create mode 100644 src/main/resources/assets/textures/grass_top.png create mode 100644 src/main/resources/assets/textures/side_east.png create mode 100644 src/main/resources/assets/textures/side_north.png create mode 100644 src/main/resources/assets/textures/side_south.png create mode 100644 src/main/resources/assets/textures/side_west.png create mode 100644 src/main/resources/assets/textures/stone.png create mode 100644 src/test/java/ru/windcorp/optica/util/CoordinatePackerTest.java create mode 100644 src/test/java/ru/windcorp/optica/util/NamespacedTest.java create mode 100644 src/test/java/ru/windcorp/optica/util/StashingStackTest.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..61793db --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfbf4ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Ignore Eclipse IDE files +bin +.settings +.classpath +.project + +# Ignore Windows Thumb.db files +**/Thumbs.db + +# Ignore MacOS +**/.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3877ae0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..4a47ffe --- /dev/null +++ b/build.gradle @@ -0,0 +1,73 @@ +plugins { + // Apply the java-library plugin to add support for Java Library + id 'java-library' +} + +repositories { + mavenCentral() + jcenter() + + maven { url 'http://windcorp.ru/./maven' } +} + +dependencies { + implementation 'org.apache.commons:commons-math3:3.6.1' + implementation 'com.google.guava:guava:28.0-jre' + implementation 'net.sf.trove4j:trove4j:3.0.3' + + implementation 'ru.windcorp.fork.io.github.java-graphics:glm:1.0.1' + + testImplementation 'junit:junit:4.12' +} + +/* + * LWJGL + * (auto-generated script) + * ((here be dragons)) + */ + +import org.gradle.internal.os.OperatingSystem + +project.ext.lwjglVersion = "3.2.3" + +switch (OperatingSystem.current()) { + case OperatingSystem.LINUX: + def osArch = System.getProperty("os.arch") + project.ext.lwjglNatives = osArch.startsWith("arm") || osArch.startsWith("aarch64") + ? "natives-linux-${osArch.contains("64") || osArch.startsWith("armv8") ? "arm64" : "arm32"}" + : "natives-linux" + break + case OperatingSystem.MAC_OS: + project.ext.lwjglNatives = "natives-macos" + break + case OperatingSystem.WINDOWS: + project.ext.lwjglNatives = System.getProperty("os.arch").contains("64") ? "natives-windows" : "natives-windows-x86" + break +} + +dependencies { + implementation platform("org.lwjgl:lwjgl-bom:$lwjglVersion") + + implementation "org.lwjgl:lwjgl" + implementation "org.lwjgl:lwjgl-assimp" + implementation "org.lwjgl:lwjgl-bgfx" + implementation "org.lwjgl:lwjgl-glfw" + implementation "org.lwjgl:lwjgl-nanovg" + implementation "org.lwjgl:lwjgl-nuklear" + implementation "org.lwjgl:lwjgl-openal" + implementation "org.lwjgl:lwjgl-opengl" + implementation "org.lwjgl:lwjgl-par" + implementation "org.lwjgl:lwjgl-stb" + implementation "org.lwjgl:lwjgl-vulkan" + runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-bgfx::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-nanovg::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-nuklear::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-par::$lwjglNatives" + runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" + if (lwjglNatives == "natives-macos") runtimeOnly "org.lwjgl:lwjgl-vulkan::$lwjglNatives" +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b GIT binary patch literal 58702 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

@up^y4&I8N>~wvL%f(S2W%NN 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 D613UA literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6ce793f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f3e0061 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'Optica' diff --git a/src/main/java/ru/windcorp/optica/Optica.java b/src/main/java/ru/windcorp/optica/Optica.java new file mode 100644 index 0000000..9ebf2b4 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/Optica.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica; + +public class Optica { + +} diff --git a/src/main/java/ru/windcorp/optica/OpticaLauncher.java b/src/main/java/ru/windcorp/optica/OpticaLauncher.java new file mode 100644 index 0000000..4f89519 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/OpticaLauncher.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica; + +public class OpticaLauncher { + + public static void launch(String[] args, Proxy proxy) { + proxy.initialize(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/Proxy.java b/src/main/java/ru/windcorp/optica/Proxy.java new file mode 100644 index 0000000..4720695 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/Proxy.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica; + +public interface Proxy { + + void initialize(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/ClientProxy.java b/src/main/java/ru/windcorp/optica/client/ClientProxy.java new file mode 100644 index 0000000..c0f1bcf --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/ClientProxy.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client; + +import ru.windcorp.optica.Proxy; +import ru.windcorp.optica.client.graphics.GUI; +import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; +import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; +import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram; +import ru.windcorp.optica.client.graphics.world.LayerWorld; + +public class ClientProxy implements Proxy { + + @Override + public void initialize() { + GraphicsBackend.initialize(); + try { + RenderTaskQueue.waitAndInvoke(ShapeRenderProgram::init); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + GUI.addBottomLayer(new LayerWorld()); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/OpticaClientMain.java b/src/main/java/ru/windcorp/optica/client/OpticaClientMain.java new file mode 100644 index 0000000..c086634 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/OpticaClientMain.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client; + +import ru.windcorp.optica.OpticaLauncher; + +public class OpticaClientMain { + + public static void main(String[] args) { + OpticaLauncher.launch(args, new ClientProxy()); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/TestLayer.java b/src/main/java/ru/windcorp/optica/client/TestLayer.java new file mode 100644 index 0000000..69e3a57 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/TestLayer.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client; + +import org.lwjgl.glfw.GLFW; + +import com.google.common.eventbus.Subscribe; + +import glm.mat._3.Mat3; +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.Layer; +import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; +import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; +import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.graphics.input.CursorMoveEvent; +import ru.windcorp.optica.client.graphics.model.Model; +import ru.windcorp.optica.client.graphics.model.DynamicModel; +import ru.windcorp.optica.client.graphics.model.Shapes; +import ru.windcorp.optica.client.graphics.texture.SimpleTexture; +import ru.windcorp.optica.client.graphics.texture.Sprite; +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.graphics.texture.TextureManager; +import ru.windcorp.optica.client.graphics.world.Camera; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public class TestLayer extends Layer { + + private final Model model; + + private final Camera camera = new Camera( + new Vec3(), + 0, (float) Math.PI, + (float) Math.toRadians(70) + ); + + private final Vec3 velocity = new Vec3(); + private final Vec3 tmp = new Vec3(); + + private final Mat3 angMat = new Mat3(); + + private int movementX = 0; + private int movementY = 0; + private int movementZ = 0; + + public TestLayer() { + super("Test"); + + Texture top = qtex("grass_top"); + Texture side = qtex("grass_side"); + Texture bottom = qtex("grass_bottom"); + + model = new DynamicModel(DynamicModel.builder() + .addDynamicPart( + new Shapes.PppBuilder(top, bottom, side, side, side, side) + .create() + ) + ) { + @Override + protected void getDynamicTransform(int shapeIndex, Mat4 result) { + result.translate(0, 0, +5); + } + }; + } + + private Texture qtex(String name) { + return new SimpleTexture(new Sprite(TextureManager.load(name, false))); + } + + @Override + protected void initialize() { + GraphicsInterface.subscribeToInputEvents(this); + } + + private final WorldRenderer renderer = new WorldRenderer(); + + @Override + protected void doRender() { + camera.apply(renderer); + + angMat.set().rotateY(-camera.getYaw()); + + tmp.set(movementX, 0, movementZ); + angMat.mul_(tmp); // bug in jglm + tmp.y = movementY; + tmp.mul(0.1f); + tmp.sub(velocity); + tmp.mul(0.1f); + velocity.add(tmp); + camera.move(velocity); + + model.render(renderer); + + renderer.reset(); + } + + private boolean flag = true; + + @Subscribe + public void onKeyEvent(KeyEvent event) { + if (event.isRepeat()) return; + + int multiplier = event.isPress() ? 1 : -1; + + switch (event.getKey()) { + case GLFW.GLFW_KEY_W: + movementZ += -1 * multiplier; + break; + case GLFW.GLFW_KEY_S: + movementZ += +1 * multiplier; + break; + case GLFW.GLFW_KEY_A: + movementX += -1 * multiplier; + break; + case GLFW.GLFW_KEY_D: + movementX += +1 * multiplier; + break; + case GLFW.GLFW_KEY_SPACE: + movementY += +1 * multiplier; + break; + case GLFW.GLFW_KEY_LEFT_SHIFT: + movementY += -1 * multiplier; + break; + + case GLFW.GLFW_KEY_ESCAPE: + if (!event.isPress()) return; + + if (flag) { + GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); + } else { + GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + flag = !flag; + break; + } + } + + @Subscribe + public void onMouseMoved(CursorMoveEvent event) { + if (!flag) return; + + final float yawScale = 0.002f; + final float pitchScale = yawScale; + + camera.turn( + (float) (event.getChangeY() * pitchScale), + (float) (event.getChangeX() * yawScale) + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/GUI.java b/src/main/java/ru/windcorp/optica/client/graphics/GUI.java new file mode 100644 index 0000000..a476c08 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/GUI.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics; + +import java.util.ArrayList; +import java.util.List; + +public class GUI { + + private static final List LAYERS = new ArrayList<>(); + + private GUI() {} + + public synchronized static void addBottomLayer(Layer layer) { + LAYERS.add(layer); + } + + public synchronized static void addTopLayer(Layer layer) { + LAYERS.add(0, layer); + } + + public synchronized static void removeLayer(Layer layer) { + LAYERS.remove(layer); + } + + public synchronized static void render() { + for (int i = LAYERS.size() - 1; i >= 0; --i) { + LAYERS.get(i).render(); + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/Layer.java b/src/main/java/ru/windcorp/optica/client/graphics/Layer.java new file mode 100644 index 0000000..fcdfaf6 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/Layer.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics; + +public abstract class Layer { + + private final String name; + private boolean hasInitialized = false; + + public Layer(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Layer " + name; + } + + public void render() { + if (!hasInitialized) { + initialize(); + hasInitialized = true; + } + + doRender(); + } + + protected abstract void initialize(); + + protected abstract void doRender(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java new file mode 100644 index 0000000..bd52e70 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsBackend.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.glfw.GLFW.*; + +public class GraphicsBackend { + + private static RenderThread renderThread; + + private static long windowHandle; + + private static int framebufferWidth; + private static int framebufferHeight; + + private static double frameLength = 1.0 / 60; // TODO do something about it + private static int framesRendered = 0; + private static double frameStart = Double.NaN; + + private GraphicsBackend() {} + + public static void initialize() { + startRenderThread(); + } + + private static void startRenderThread() { + renderThread = new RenderThread(); + renderThread.start(); + } + + public static Thread getRenderThread() { + return renderThread; + } + + static void setWindowHandle(long windowHandle) { + GraphicsBackend.windowHandle = windowHandle; + } + + public static long getWindowHandle() { + return windowHandle; + } + + public static int getFramebufferWidth() { + return framebufferWidth; + } + + public static int getFramebufferHeight() { + return framebufferHeight; + } + + static void onFramebufferResized(long window, int newWidth, int newHeight) { + if (window != windowHandle) return; + + framebufferWidth = newWidth; + framebufferHeight = newHeight; + + glViewport(0, 0, framebufferWidth, framebufferHeight); + } + + static void startFrame() { + double now = glfwGetTime(); + + if (Double.isNaN(frameStart)) { + frameStart = now; + } else { + frameLength = now - frameStart; + frameStart = now; + } + } + + static void endFrame() { + framesRendered++; + } + + public static double getFrameStart() { + return frameStart; + } + + public static double getFrameLength() { + return frameLength; + } + + public static int getFramesRendered() { + return framesRendered; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java new file mode 100644 index 0000000..42f6937 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/GraphicsInterface.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import glm.vec._2.d.Vec2d; + +public class GraphicsInterface { + + private GraphicsInterface() {} + + public static Thread getRenderThread() { + return GraphicsBackend.getRenderThread(); + } + + public static boolean isRenderThread() { + return Thread.currentThread() == getRenderThread(); + } + + public static int getFramebufferWidth() { + return GraphicsBackend.getFramebufferWidth(); + } + + public static int getFramebufferHeight() { + return GraphicsBackend.getFramebufferHeight(); + } + + public static float getAspectRatio() { + return ((float) getFramebufferWidth()) / getFramebufferHeight(); + } + + public static double getTime() { + return GraphicsBackend.getFrameStart(); + } + + public static double getFrameLength() { + return GraphicsBackend.getFrameLength(); + } + + public static double getFPS() { + return 1 / GraphicsBackend.getFrameLength(); + } + + public static void subscribeToInputEvents(Object listener) { + InputHandler.register(listener); + } + + public static double getCursorX() { + return InputHandler.getCursorX(); + } + + public static double getCursorY() { + return InputHandler.getCursorY(); + } + + public static Vec2d getCursorPosition() { + return InputHandler.getCursorPosition(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java new file mode 100644 index 0000000..f990463 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/InputHandler.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import com.google.common.eventbus.EventBus; + +import glm.vec._2.d.Vec2d; +import ru.windcorp.optica.client.graphics.input.*; + +public class InputHandler { + + private static final EventBus INPUT_EVENT_BUS = new EventBus("Input"); + + private static class ModifiableKeyEvent extends KeyEvent { + + public void initialize(int key, int scancode, int action, int mods) { + this.key = key; + this.scancode = scancode; + this.action = action; + this.mods = mods; + } + + } + + private static final ModifiableKeyEvent THE_KEY_EVENT = new ModifiableKeyEvent(); + + static void handleKeyInput( + long window, + int key, + int scancode, + int action, + int mods + ) { + if (GraphicsBackend.getWindowHandle() != window) return; + THE_KEY_EVENT.initialize(key, scancode, action, mods); + dispatch(THE_KEY_EVENT); + } + + private static class ModifiableCursorMoveEvent extends CursorMoveEvent { + + public void initialize(double x, double y) { + Vec2d newPos = getNewPosition(); + newPos.x = x; + newPos.y = y; + } + + } + + private static final Vec2d CURSOR_POSITION = new Vec2d().set( + Double.NaN, Double.NaN + ); + + private static final ModifiableCursorMoveEvent THE_CURSOR_MOVE_EVENT = + new ModifiableCursorMoveEvent(); + + static void handleMouseMoveInput( + long window, + double x, double y + ) { + if (GraphicsBackend.getWindowHandle() != window) return; + + if (Double.isNaN(CURSOR_POSITION.x)) { + CURSOR_POSITION.set(x, y); + } + + THE_CURSOR_MOVE_EVENT.initialize(x, y); + dispatch(THE_CURSOR_MOVE_EVENT); + + CURSOR_POSITION.set(x, y); + } + + public static double getCursorX() { + return CURSOR_POSITION.x; + } + + public static double getCursorY() { + return CURSOR_POSITION.y; + } + + public static Vec2d getCursorPosition() { + return CURSOR_POSITION; + } + + private static void dispatch(InputEvent event) { + INPUT_EVENT_BUS.post(event); + } + + public static void register(Object listener) { + INPUT_EVENT_BUS.register(listener); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java new file mode 100644 index 0000000..dd69d0f --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/LWJGLInitializer.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.system.MemoryUtil.*; + +import org.lwjgl.opengl.GL; + +class LWJGLInitializer { + + private LWJGLInitializer() {} + + public static void initialize() { + checkEnvironment(); + initializeGLFW(); + createWindow(); + positionWindow(); + createWindowIcons(); + initializeOpenGL(); + setupWindowCallbacks(); + + glfwShowWindow(GraphicsBackend.getWindowHandle()); + } + + private static void checkEnvironment() { + // TODO Auto-generated method stub + } + + private static void initializeGLFW() { + // TODO Do GLFW error handling: check glfwInit, setup error callback + glfwInit(); + } + + private static void createWindow() { + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); + + long handle = glfwCreateWindow(900, 900, "OpticaTest", NULL, NULL); + + // TODO Check that handle != NULL + + GraphicsBackend.setWindowHandle(handle); + + glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + glfwMakeContextCurrent(handle); + glfwSwapInterval(0); + } + + private static void positionWindow() { + // TODO Auto-generated method stub + + } + + private static void createWindowIcons() { + // TODO Auto-generated method stub + + } + + private static void initializeOpenGL() { + GL.createCapabilities(); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + private static void setupWindowCallbacks() { + long handle = GraphicsBackend.getWindowHandle(); + + glfwSetFramebufferSizeCallback(handle, + GraphicsBackend::onFramebufferResized); + + glfwSetKeyCallback(handle, InputHandler::handleKeyInput); + glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/OpenGLObjectTracker.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/OpenGLObjectTracker.java new file mode 100644 index 0000000..b2fa223 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/OpenGLObjectTracker.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import java.util.ArrayList; +import java.util.Collection; + +/* + * FIXME deal with client invocations of .delete() when properly disposing of + * objects mid-execution + */ + +public class OpenGLObjectTracker { + + public static interface OpenGLDeletable { + void delete(); + } + + private static final Collection TO_DELETE = new ArrayList<>(); + + public synchronized static void register(OpenGLDeletable object) { + TO_DELETE.add(object); + } + + public synchronized static void deleteAllObjects() { + TO_DELETE.forEach(OpenGLDeletable::delete); + TO_DELETE.clear(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderTaskQueue.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderTaskQueue.java new file mode 100644 index 0000000..8a8d927 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderTaskQueue.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import ru.windcorp.optica.common.util.ThrowingRunnable; + +public class RenderTaskQueue { + + private static final Queue QUEUE = new ConcurrentLinkedQueue<>(); + + private RenderTaskQueue() {} + + public static void invokeLater(Runnable task) { + QUEUE.add(task); + } + + public static void invokeNow(Runnable task) { + if (GraphicsInterface.isRenderThread()) { + task.run(); + } else { + invokeLater(task); + } + } + + private static final Object WAIT_AND_INVOKE_MONITOR = new Object(); + + @SuppressWarnings("unchecked") + public static void waitAndInvoke( + ThrowingRunnable task + ) throws InterruptedException, T { + + if (GraphicsInterface.isRenderThread()) { + task.run(); + return; + } + + final AtomicBoolean flag = + new AtomicBoolean(false); + final AtomicReference thrownContainer = + new AtomicReference<>(null); + + invokeLater(() -> { + + try { + task.run(); + } catch (Throwable t) { + thrownContainer.set(t); + } + + flag.set(true); + + synchronized (WAIT_AND_INVOKE_MONITOR) { + WAIT_AND_INVOKE_MONITOR.notifyAll(); + } + }); + + while (!flag.get()) { + synchronized (WAIT_AND_INVOKE_MONITOR) { + WAIT_AND_INVOKE_MONITOR.wait(); + } + } + + Throwable thrown = thrownContainer.get(); + if (thrown != null) { + if (thrown instanceof RuntimeException) { + throw (RuntimeException) thrown; + } + + if (thrown instanceof Error) { + throw (Error) thrown; + } + + throw (T) thrown; // Guaranteed + } + } + + public static void runTasks() { + Iterator tasks = QUEUE.iterator(); + + while (tasks.hasNext()) { + tasks.next().run(); + tasks.remove(); + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderThread.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderThread.java new file mode 100644 index 0000000..239f71c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/RenderThread.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL11.*; + +import ru.windcorp.optica.client.graphics.GUI; + +class RenderThread extends Thread { + + public RenderThread() { + super("Render"); + } + + @Override + public void run() { + LWJGLInitializer.initialize(); + mainLoop(); + freeResources(); + } + + private void mainLoop() { + while (shouldRun()) { + GraphicsBackend.startFrame(); + RenderTaskQueue.runTasks(); + render(); + waitForFrame(); + GraphicsBackend.endFrame(); + } + } + + private void render() { + clear(); + doRender(); + glfwPollEvents(); + } + + private void clear() { + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + private void doRender() { + GUI.render(); + } + + private void waitForFrame() { + glfwSwapBuffers(GraphicsBackend.getWindowHandle()); + } + + private void freeResources() { + OpenGLObjectTracker.deleteAllObjects(); + } + + private boolean shouldRun() { + return !glfwWindowShouldClose(GraphicsBackend.getWindowHandle()); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/Usage.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/Usage.java new file mode 100644 index 0000000..1a356c1 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/Usage.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import static org.lwjgl.opengl.GL15.GL_DYNAMIC_DRAW; +import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW; +import static org.lwjgl.opengl.GL15.GL_STREAM_DRAW; + +public enum Usage { // TODO add _COPY and _READ, pref. as another enum + STATIC(GL_STATIC_DRAW), + DYNAMIC(GL_DYNAMIC_DRAW), + STREAM(GL_STREAM_DRAW); + + private final int glCode; + + private Usage(int glCode) { + this.glCode = glCode; + } + + public int getGlCode() { + return glCode; + } +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/VertexBufferObject.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/VertexBufferObject.java new file mode 100644 index 0000000..abf4cb8 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/VertexBufferObject.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend; + +import static org.lwjgl.opengl.GL20.*; + +import java.nio.*; + +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; + +public class VertexBufferObject implements OpenGLDeletable { + + public static enum BindTarget { + ARRAY(GL_ARRAY_BUFFER), + ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER); + + private final int glCode; + + private BindTarget(int glCode) { + this.glCode = glCode; + } + + public int getGlCode() { + return glCode; + } + } + + private final int handle; + + private long length = 0; + private final Usage usage; + + public VertexBufferObject(Usage usage) { + handle = glGenBuffers(); + OpenGLObjectTracker.register(this); + + this.usage = usage; + } + + public void setData(ByteBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining(); + } + + public void setData(double[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.length * Double.BYTES; + } + + public void setData(DoubleBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining() * Double.BYTES; + } + + public void setData(float[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.length * Float.BYTES; + } + + public void setData(FloatBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining() * Float.BYTES; + } + + public void setData(int[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.length * Integer.BYTES; + } + + public void setData(IntBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining() * Integer.BYTES; + } + + public void setData(long[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.length * Long.BYTES; + } + + public void setData(LongBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining() * Long.BYTES; + } + + public void setData(short[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.length * Short.BYTES; + } + + public void setData(ShortBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, data, usage.getGlCode()); + length = data.remaining() * Short.BYTES; + } + + public void initData(long length) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferData(GL_ARRAY_BUFFER, length, usage.getGlCode()); + this.length = length; + } + + public void setData(int offset, ByteBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, double[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, DoubleBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, float[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, FloatBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, int[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, IntBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, long[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, LongBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, short[] data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void setData(int offset, ShortBuffer data) { + glBindBuffer(GL_ARRAY_BUFFER, handle); + glBufferSubData(GL_ARRAY_BUFFER, offset, data); + } + + public void bind(BindTarget target) { + glBindBuffer(target.getGlCode(), handle); + } + + public long getLength() { + return length; + } + + public Usage getUsage() { + return usage; + } + + public int getHandle() { + return handle; + } + + @Override + public void delete() { + glDeleteBuffers(handle); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/CombinedShader.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/CombinedShader.java new file mode 100644 index 0000000..dcd9571 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/CombinedShader.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders; + +import ru.windcorp.optica.common.resource.Resource; + +public class CombinedShader extends Shader { + + public CombinedShader(String... resources) { + super(getTypeOf(resources), combine(resources)); + } + + private static ShaderType getTypeOf(String[] resources) { + ShaderType first = ShaderType.guessByResourceName(resources[0]); + + for (int i = 1; i < resources.length; ++i) { + if (ShaderType.guessByResourceName(resources[i]) != first) { + throw new IllegalArgumentException( + "Deduced shader types of " + + resources[0] + + " and " + + resources[i] + + " differ" + ); + } + } + + return first; + } + + private static String combine(String[] resources) { + StringBuilder accumulator = new StringBuilder("#version 120\n"); + + for (String resourceName : resources) { + Resource resource = getShaderResource(resourceName); + + accumulator.append("\n// START " + resourceName); + accumulator.append(stripVersionAnnotations(resource)); + accumulator.append('\n'); + } + + return accumulator.toString(); + } + + private static String stripVersionAnnotations(Resource resource) { + String contents = resource.readAsString(); + + int versionIndex; + for (versionIndex = 0; versionIndex < contents.length(); ++versionIndex) + { + if (!Character.isWhitespace(contents.codePointAt(versionIndex))) + break; + } + + if (versionIndex < contents.length()) { + if (contents.codePointAt(versionIndex) == '#') { + final String versionAnnotation = "#version "; + + if (contents.regionMatches( + versionIndex, + versionAnnotation, + 0, + versionAnnotation.length() + )) { + contents = contents.substring( + versionIndex + + versionAnnotation.length() + + "120".length() + ); + } + + } + } + + return contents; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Program.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Program.java new file mode 100644 index 0000000..e2ed2ac --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Program.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders; + +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker; +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; +import ru.windcorp.optica.client.graphics.backend.shaders.attributes.Attribute; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +public class Program implements OpenGLDeletable { + + private int handle; + + public Program(Shader vertexShader, Shader fragmentShader) { + handle = glCreateProgram(); + OpenGLObjectTracker.register(this); + + glAttachShader(handle, vertexShader.getHandle()); + glAttachShader(handle, fragmentShader.getHandle()); + + glLinkProgram(handle); + + if (glGetProgrami(handle, GL_LINK_STATUS) == GL_FALSE) { + throw new RuntimeException("Bad program:\n" + glGetProgramInfoLog(handle)); + } + } + + public Attribute getAttribute(String name) { + return new Attribute(glGetAttribLocation(handle, name), this); + } + + public Uniform getUniform(String name) { + return new Uniform(glGetUniformLocation(handle, name), this); + } + + public void use() { + glUseProgram(handle); + } + + @Override + public void delete() { + glDeleteProgram(handle); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Shader.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Shader.java new file mode 100644 index 0000000..a2c4081 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/Shader.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders; + +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker; +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; +import ru.windcorp.optica.common.resource.Resource; +import ru.windcorp.optica.common.resource.ResourceManager; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import java.util.Locale; + +public class Shader implements OpenGLDeletable { + + public static enum ShaderType { + VERTEX(GL_VERTEX_SHADER), + FRAGMENT(GL_FRAGMENT_SHADER); + + private final int glCode; + + private ShaderType(int glCode) { + this.glCode = glCode; + } + + public int getGlCode() { + return glCode; + } + + public static ShaderType guessByResourceName(String resource) { + resource = resource.toLowerCase(Locale.ENGLISH); + + if (resource.contains("vertex")) return VERTEX; + if (resource.contains("fragment")) return FRAGMENT; + if (resource.contains("vsh")) return VERTEX; + if (resource.contains("fsh")) return FRAGMENT; + + throw new IllegalArgumentException( + "Cannot deduce shader type from resource name \"" + + resource + "\"" + ); + } + } + + private static final String SHADER_ASSETS_PREFIX = "assets/shaders/"; + + protected static Resource getShaderResource(String name) { + return ResourceManager.getResource(SHADER_ASSETS_PREFIX + name); + } + + private final int handle; + private final ShaderType type; + + public Shader(ShaderType type, String source) { + handle = glCreateShader(type.getGlCode()); + OpenGLObjectTracker.register(this); + + this.type = type; + + glShaderSource(handle, source); + glCompileShader(handle); + + if (glGetShaderi(handle, GL_COMPILE_STATUS) == GL_FALSE) { + System.out.println("***************** ERROR ******************"); + System.out.println(source); + throw new RuntimeException("Bad shader:\n" + glGetShaderInfoLog(handle)); + } + } + + public Shader(String resource) { + this( + ShaderType.guessByResourceName(resource), + getShaderResource(resource).readAsString() + ); + } + + @Override + public void delete() { + glDeleteShader(handle); + } + + public int getHandle() { + return handle; + } + + public ShaderType getType() { + return type; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/Attribute.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/Attribute.java new file mode 100644 index 0000000..fcbbe9b --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/Attribute.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.attributes; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +public class Attribute { + + protected final int handle; + private final Program program; + + public Attribute(int handle, Program program) { + if (handle < 0) { + throw new RuntimeException("Bad handle: " + handle); + } + + this.handle = handle; + this.program = program; + } + + public int getHandle() { + return handle; + } + + public Program getProgram() { + return program; + } + + public AttributeVertexArray asVertexArray() { + return new AttributeVertexArray(handle, program); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/AttributeVertexArray.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/AttributeVertexArray.java new file mode 100644 index 0000000..f382f9d --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/attributes/AttributeVertexArray.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.attributes; + +import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +public class AttributeVertexArray extends Attribute { + + private boolean isEnabled = false; + + public AttributeVertexArray(int handle, Program program) { + super(handle, program); + } + + public void enable() { + if (!isEnabled) { + glEnableVertexAttribArray(handle); + isEnabled = true; + } + } + + public void disable() { + if (isEnabled) { + glDisableVertexAttribArray(handle); + isEnabled = false; + } + } + + public void set( + int size, boolean normalized, int stride, + ByteBuffer pointer + ) { + glVertexAttribPointer( + handle, + size, GL_BYTE, normalized, stride, pointer + ); + } + + public void set( + int size, boolean normalized, int stride, + FloatBuffer pointer + ) { + glVertexAttribPointer( + handle, + size, GL_FLOAT, normalized, stride, pointer + ); + } + + public void set( + int size, boolean normalized, int stride, + IntBuffer pointer + ) { + glVertexAttribPointer( + handle, + size, GL_INT, normalized, stride, pointer + ); + } + + public void set( + int size, boolean normalized, int stride, + ShortBuffer pointer + ) { + glVertexAttribPointer( + handle, + size, GL_SHORT, normalized, stride, pointer + ); + } + + public void set( + int size, int type, boolean normalized, int stride, + long pointer + ) { + glVertexAttribPointer( + handle, + size, type, normalized, stride, pointer + ); + } + + public void set( + int size, int type, boolean normalized, int stride, + VertexBufferObject vbo, long offset + ) { + glBindBuffer(GL_ARRAY_BUFFER, vbo.getHandle()); + glVertexAttribPointer( + handle, + size, type, normalized, stride, offset + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform.java new file mode 100644 index 0000000..fadcbc7 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +public class Uniform { + + protected final int handle; + private final Program program; + + public Uniform(int handle, Program program) { + if (handle < 0) { + throw new RuntimeException("Bad handle: " + handle); + } + + this.handle = handle; + this.program = program; + } + + public int getHandle() { + return handle; + } + + public Program getProgram() { + return program; + } + + public Uniform1Float as1Float() { + return new Uniform1Float(handle, program); + } + + public Uniform1Int as1Int() { + return new Uniform1Int(handle, program); + } + + public Uniform2Float as2Float() { + return new Uniform2Float(handle, program); + } + + public Uniform2Int as2Int() { + return new Uniform2Int(handle, program); + } + + public Uniform3Float as3Float() { + return new Uniform3Float(handle, program); + } + + public Uniform3Int as3Int() { + return new Uniform3Int(handle, program); + } + + public Uniform4Float as4Float() { + return new Uniform4Float(handle, program); + } + + public Uniform4Int as4Int() { + return new Uniform4Int(handle, program); + } + + public Uniform2Matrix as2Matrix() { + return new Uniform2Matrix(handle, program); + } + + public Uniform3Matrix as3Matrix() { + return new Uniform3Matrix(handle, program); + } + + public Uniform4Matrix as4Matrix() { + return new Uniform4Matrix(handle, program); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Float.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Float.java new file mode 100644 index 0000000..7a64168 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Float.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +public class Uniform1Float extends Uniform { + + public Uniform1Float(int handle, Program program) { + super(handle, program); + } + + public void set(float value) { + glUniform1f(handle, value); + } + + public void set(float[] value) { + glUniform1fv(handle, value); + } + + public void set(FloatBuffer value) { + glUniform1fv(handle, value); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Int.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Int.java new file mode 100644 index 0000000..2fa81a2 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform1Int.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.IntBuffer; + +public class Uniform1Int extends Uniform { + + public Uniform1Int(int handle, Program program) { + super(handle, program); + } + + public void set(int value) { + glUniform1i(handle, value); + } + + public void set(int[] value) { + glUniform1iv(handle, value); + } + + public void set(IntBuffer value) { + glUniform1iv(handle, value); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Float.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Float.java new file mode 100644 index 0000000..9a4130a --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Float.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +import glm.vec._2.Vec2; + +public class Uniform2Float extends Uniform { + + public Uniform2Float(int handle, Program program) { + super(handle, program); + } + + public void set(float x, float y) { + glUniform2f(handle, x, y); + } + + public void set(float[] value) { + glUniform2fv(handle, value); + } + + public void set(FloatBuffer value) { + glUniform2fv(handle, value); + } + + public void set(Vec2 value) { + glUniform2f(handle, value.x, value.y); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Int.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Int.java new file mode 100644 index 0000000..4df68f7 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Int.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.IntBuffer; + +import glm.vec._2.i.Vec2i; + +public class Uniform2Int extends Uniform { + + public Uniform2Int(int handle, Program program) { + super(handle, program); + } + + public void set(int x, int y) { + glUniform2i(handle, x, y); + } + + public void set(int[] value) { + glUniform2iv(handle, value); + } + + public void set(IntBuffer value) { + glUniform2iv(handle, value); + } + + public void set(Vec2i value) { + glUniform2i(handle, value.x, value.y); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Matrix.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Matrix.java new file mode 100644 index 0000000..348cb40 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform2Matrix.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +public class Uniform2Matrix extends Uniform { + + public Uniform2Matrix(int handle, Program program) { + super(handle, program); + } + + public void set(float[] value) { + glUniformMatrix2fv(handle, false, value); + } + + public void set(FloatBuffer value) { + glUniformMatrix2fv(handle, false, value); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Float.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Float.java new file mode 100644 index 0000000..1f54360 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Float.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +import glm.vec._3.Vec3; + +public class Uniform3Float extends Uniform { + + public Uniform3Float(int handle, Program program) { + super(handle, program); + } + + public void set(float x, float y, float z) { + glUniform3f(handle, x, y, z); + } + + public void set(float[] value) { + glUniform3fv(handle, value); + } + + public void set(FloatBuffer value) { + glUniform3fv(handle, value); + } + + public void set(Vec3 value) { + glUniform3f(handle, value.x, value.y, value.z); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Int.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Int.java new file mode 100644 index 0000000..8936406 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Int.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.IntBuffer; + +import glm.vec._3.i.Vec3i; + +public class Uniform3Int extends Uniform { + + public Uniform3Int(int handle, Program program) { + super(handle, program); + } + + public void set(int x, int y, int z) { + glUniform3i(handle, x, y, z); + } + + public void set(int[] value) { + glUniform3iv(handle, value); + } + + public void set(IntBuffer value) { + glUniform3iv(handle, value); + } + + public void set(Vec3i value) { + glUniform3i(handle, value.x, value.y, value.z); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Matrix.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Matrix.java new file mode 100644 index 0000000..62ddb77 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform3Matrix.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +import glm.mat._3.Mat3; + +public class Uniform3Matrix extends Uniform { + + public Uniform3Matrix(int handle, Program program) { + super(handle, program); + } + + public void set(float[] value) { + glUniformMatrix3fv(handle, false, value); + } + + public void set(FloatBuffer value) { + glUniformMatrix3fv(handle, false, value); + } + + private static final float[] BUFFER = new float[3 * 3]; + + public void set(Mat3 value) { + glUniformMatrix3fv(handle, false, value.toFa(BUFFER)); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Float.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Float.java new file mode 100644 index 0000000..6b22f76 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Float.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +import glm.vec._4.Vec4; + +public class Uniform4Float extends Uniform { + + public Uniform4Float(int handle, Program program) { + super(handle, program); + } + + public void set(float x, float y, float z, float w) { + glUniform4f(handle, x, y, z, w); + } + + public void set(float[] value) { + glUniform4fv(handle, value); + } + + public void set(FloatBuffer value) { + glUniform4fv(handle, value); + } + + public void set(Vec4 value) { + glUniform4f(handle, value.x, value.y, value.z, value.w); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Int.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Int.java new file mode 100644 index 0000000..e21bf27 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Int.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.IntBuffer; + +import glm.vec._4.i.Vec4i; + +public class Uniform4Int extends Uniform { + + public Uniform4Int(int handle, Program program) { + super(handle, program); + } + + public void set(int x, int y, int z, int w) { + glUniform4i(handle, x, y, z, w); + } + + public void set(int[] value) { + glUniform4iv(handle, value); + } + + public void set(IntBuffer value) { + glUniform4iv(handle, value); + } + + public void set(Vec4i value) { + glUniform4i(handle, value.x, value.y, value.z, value.w); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Matrix.java b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Matrix.java new file mode 100644 index 0000000..03e9d62 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/backend/shaders/uniforms/Uniform4Matrix.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.backend.shaders.uniforms; + +import ru.windcorp.optica.client.graphics.backend.shaders.Program; + +import static org.lwjgl.opengl.GL20.*; +import java.nio.FloatBuffer; + +import glm.mat._4.Mat4; + +public class Uniform4Matrix extends Uniform { + + public Uniform4Matrix(int handle, Program program) { + super(handle, program); + } + + public void set(float[] value) { + glUniformMatrix4fv(handle, false, value); + } + + public void set(FloatBuffer value) { + glUniformMatrix4fv(handle, false, value); + } + + private static final float[] BUFFER = new float[4 * 4]; + + public void set(Mat4 value) { + glUniformMatrix4fv(handle, false, value.toFa(BUFFER)); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java new file mode 100644 index 0000000..9483e28 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorEvent.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import glm.vec._2.d.Vec2d; +import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; + +public abstract class CursorEvent extends InputEvent { + + public double getCursorX() { + return GraphicsInterface.getCursorX(); + } + + public double getCursorY() { + return GraphicsInterface.getCursorY(); + } + + public Vec2d getCursorPosition() { + return GraphicsInterface.getCursorPosition(); + } + + @Override + public abstract CursorEvent snapshot(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/CursorMoveEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorMoveEvent.java new file mode 100644 index 0000000..6c04d2b --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/CursorMoveEvent.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import glm.vec._2.Vec2; +import glm.vec._2.d.Vec2d; + +public class CursorMoveEvent extends CursorEvent { + + private final Vec2d newPosition = new Vec2d(); + + protected CursorMoveEvent(double newX, double newY) { + newPosition.set(newX, newY); + } + + protected CursorMoveEvent(Vec2d newPos) { + newPosition.set(newPos.x, newPos.y); + } + + @Override + public double getCursorX() { + return getCursorPosition().x; + } + + @Override + public double getCursorY() { + return getCursorPosition().y; + } + + @Override + public Vec2d getCursorPosition() { + return getNewPosition(); + } + + public double getNewX() { + return getNewPosition().x; + } + + public double getNewY() { + return getNewPosition().y; + } + + public Vec2d getNewPosition() { + return newPosition; + } + + public double getPreviousX() { + return getPreviousPosition().x; + } + + public double getPreviousY() { + return getPreviousPosition().y; + } + + public Vec2d getPreviousPosition() { + return super.getCursorPosition(); + } + + public double getChangeX() { + return getNewX() - getPreviousX(); + } + + public double getChangeY() { + return getNewY() - getPreviousY(); + } + + public Vec2 getChange(Vec2 result) { + return result.set(getChangeX(), getChangeY()); + } + + protected CursorMoveEvent() {} + + @Override + public CursorMoveEvent snapshot() { + return new StaticMouseMoveEvent( + getPreviousPosition(), + getNewPosition(), + getTime() + ); + } + + private class StaticMouseMoveEvent extends CursorMoveEvent { + + private final double time; + private final Vec2d previousPosition = new Vec2d(); + + public StaticMouseMoveEvent( + Vec2d previousPosition, + Vec2d newPosition, + double time + ) { + super(newPosition); + this.previousPosition.set(previousPosition.x, previousPosition.y); + this.time = time; + } + + @Override + public Vec2d getPreviousPosition() { + return previousPosition; + } + + @Override + public double getTime() { + return time; + } + + @Override + public CursorMoveEvent snapshot() { + return this; + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/InputEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/InputEvent.java new file mode 100644 index 0000000..8910bcf --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/InputEvent.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; + +public abstract class InputEvent { + + public double getTime() { + return GraphicsInterface.getTime(); + } + + public abstract InputEvent snapshot(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java new file mode 100644 index 0000000..8c9fe89 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/input/KeyEvent.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.input; + +import org.lwjgl.glfw.GLFW; + +public class KeyEvent extends InputEvent { + + protected int key; + protected int scancode; + protected int action; + protected int mods; + + protected KeyEvent(int key, int scancode, int action, int mods) { + this(); + this.key = key; + this.scancode = scancode; + this.action = action; + this.mods = mods; + } + + protected KeyEvent() {} + + public int getKey() { + return key; + } + + public int getScancode() { + return scancode; + } + + public int getAction() { + return action; + } + + public boolean isPress() { + return action == GLFW.GLFW_PRESS; + } + + public boolean isRelease() { + return action == GLFW.GLFW_RELEASE; + } + + public boolean isRepeat() { + return action == GLFW.GLFW_REPEAT; + } + + public int getMods() { + return mods; + } + + @Override + public InputEvent snapshot() { + return new StaticKeyEvent(key, scancode, action, mods, getTime()); + } + + private class StaticKeyEvent extends KeyEvent { + + private final double time; + + public StaticKeyEvent( + int key, int scancode, int action, int mods, + double time + ) { + super(key, scancode, action, mods); + this.time = time; + } + + @Override + public double getTime() { + return time; + } + + @Override + public InputEvent snapshot() { + return this; + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/DynamicModel.java b/src/main/java/ru/windcorp/optica/client/graphics/model/DynamicModel.java new file mode 100644 index 0000000..a0f7575 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/DynamicModel.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.google.common.primitives.Booleans; + +import glm.mat._4.Mat4; + +public abstract class DynamicModel extends Model { + + private static final Mat4 IDENTITY = new Mat4(); + + private final Mat4[] transforms; + private final boolean[] dynamics; + + public DynamicModel( + WorldRenderable[] parts, + Mat4[] transforms, + boolean[] dynamic + ) { + super(parts); + this.transforms = transforms; + this.dynamics = dynamic; + } + + public DynamicModel(Builder builder) { + this( + builder.getParts(), + builder.getTransforms(), + builder.getDynamics() + ); + } + + @Override + protected Mat4 getTransform(int shapeIndex) { + Mat4 transform = transforms[shapeIndex]; + + if (dynamics[shapeIndex]) { + transform.identity(); + getDynamicTransform(shapeIndex, transform); + } + + return transform; + } + + protected abstract void getDynamicTransform(int shapeIndex, Mat4 result); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final List parts = new ArrayList<>(); + private final List transforms = new ArrayList<>(); + private final List dynamics = new ArrayList<>(); + + protected Builder() {} + + private Builder addPart( + WorldRenderable part, + Mat4 transform, + boolean isDynamic + ) { + parts.add(Objects.requireNonNull(part, "part")); + transforms.add(Objects.requireNonNull(transform, "transform")); + dynamics.add(isDynamic); + + return this; + } + + public Builder addStaticPart( + WorldRenderable part, + Mat4 transform + ) { + return addPart(part, new Mat4(transform), false); + } + + public Builder addDynamicPart( + WorldRenderable part + ) { + return addPart(part, new Mat4(), true); + } + + public Builder addStaticPart( + WorldRenderable part + ) { + return addStaticPart(part, IDENTITY); + } + + private WorldRenderable[] getParts() { + return parts.toArray(new WorldRenderable[parts.size()]); + } + + private Mat4[] getTransforms() { + return transforms.toArray(new Mat4[transforms.size()]); + } + + private boolean[] getDynamics() { + return Booleans.toArray(dynamics); + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java b/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java new file mode 100644 index 0000000..0fda90c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/EmptyModel.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import glm.mat._4.Mat4; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public class EmptyModel extends Model { + + private static final EmptyModel INSTANCE = new EmptyModel(); + + private EmptyModel() { + super(new WorldRenderable[0]); + } + + public static EmptyModel getInstance() { + return INSTANCE; + } + + @Override + public void render(WorldRenderer renderer) { + // Do nothing + } + + @Override + protected Mat4 getTransform(int shapeIndex) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java new file mode 100644 index 0000000..ec90680 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Face.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.util.Objects; + +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.texture.Texture; + +public class Face { + + private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null; + + private Shape shape = null; + int locationOfIndices; + int locationOfVertices; + + private Texture texture; + + ByteBuffer vertices; + private boolean verticesUpdated = true; + + private ShortBuffer userIndices; + private boolean userIndicesUpdated = true; + + public Face( + Texture texture, + ByteBuffer vertices, + ShortBuffer indices + ) { + setTexture(texture); + setVertices(vertices); + setIndices(indices); + } + + public Face( + Texture texture, + ByteBuffer vertices + ) { + this(texture, vertices, null); + } + + void setShape(Shape shape) { + this.shape = shape; + + checkVertices(); + checkIndices(); + } + + void computeNormals() { + Vec3 a = new Vec3(); + Vec3 b = new Vec3(); + Vec3 c = new Vec3(); + Vec3 normal = new Vec3(); + + for (int i = 0; i < getIndexCount(); i += 3) { + int indexA = getIndex(i + 0); + int indexB = getIndex(i + 1); + int indexC = getIndex(i + 2); + + loadVertexPosition(indexA, a); + loadVertexPosition(indexB, b); + loadVertexPosition(indexC, c); + + computeOneNormal(a, b, c, normal); + + saveVertexNormal(indexA, normal); + saveVertexNormal(indexB, normal); + saveVertexNormal(indexC, normal); + } + } + + private void computeOneNormal( + Vec3 a, Vec3 b, Vec3 c, + Vec3 normal + ) { + b.sub(a); + c.sub(a); + b.cross(c, normal); + normal.normalize(); + } + + private void checkVertices() { + if (vertices.remaining() % getBytesPerVertex() != 0) { + throw new IllegalArgumentException( + "Invalid vertex buffer: " + + (vertices.remaining() % getBytesPerVertex()) + + " extra bytes after last vertex" + ); + } + } + + private void checkIndices() { + if (userIndices != GENERATE_SUCCESSIVE_LATER) { + if (userIndices.remaining() % 3 != 0) { + throw new IllegalArgumentException( + "Invalid vertex indices: " + + (userIndices.remaining() % 3) + + " extra indices after last triangle" + ); + } + + userIndices.mark(); + int vertexCount = getVertexCount(); + + while (userIndices.hasRemaining()) { + short index = userIndices.get(); + if (index < 0 || index >= vertexCount) { + throw new IllegalArgumentException( + "Invalid vertex index " + index + + " (" + vertexCount + " vertices available)" + ); + } + } + + userIndices.reset(); + } else { + if (getVertexCount() % 3 != 0) { + throw new IllegalArgumentException( + "Invalid vertices: " + + (getVertexCount() % 3) + + " extra indices after last triangle " + + "(indices are automatic)" + ); + } + } + } + + boolean needsVerticesUpdate() { + return verticesUpdated; + } + + public void markForIndexUpdate() { + if (shape != null) checkIndices(); + markShapeForReassembly(); + userIndicesUpdated = true; + } + + boolean needsIndicesUpdate() { + return userIndicesUpdated; + } + + void resetUpdateFlags() { + verticesUpdated = false; + userIndicesUpdated = false; + } + + private void markShapeForReassembly() { + if (shape != null) { + shape.markForReassembly(); + } + } + + public int getVertexCount() { + return vertices.remaining() / getBytesPerVertex(); + } + + private int getBytesPerVertex() { + return shape.getProgram().getBytesPerVertex(); + } + + public ByteBuffer getVertices() { + return vertices; + } + + private void loadVertexPosition(int index, Vec3 result) { + int offset = vertices.position() + index * getBytesPerVertex(); + + result.set( + vertices.getFloat(offset + 0 * Float.BYTES), + vertices.getFloat(offset + 1 * Float.BYTES), + vertices.getFloat(offset + 2 * Float.BYTES) + ); + } + + private void saveVertexNormal(int index, Vec3 normal) { + int offset = vertices.position() + index * getBytesPerVertex() + ( + 3 * Float.BYTES + + 3 * Float.BYTES + + 2 * Float.BYTES + ); + + vertices.putFloat(offset + 0 * Float.BYTES, normal.x); + vertices.putFloat(offset + 1 * Float.BYTES, normal.y); + vertices.putFloat(offset + 2 * Float.BYTES, normal.z); + + verticesUpdated = true; + } + + public Face setVertices(ByteBuffer vertices) { + this.vertices = Objects.requireNonNull(vertices, "vertices"); + markShapeForReassembly(); + this.verticesUpdated = true; + + if (shape != null) checkVertices(); + + return this; + } + + int getLocationOfVertices() { + return locationOfVertices; + } + + int getByteOffsetOfVertices() { + return locationOfVertices; + } + + public ShortBuffer getIndices() { + if (userIndices == GENERATE_SUCCESSIVE_LATER) { + userIndices = generateSuccessiveIndices(0); + } + + return userIndices; + } + + public int getIndex(int i) { + if (userIndices == GENERATE_SUCCESSIVE_LATER) { + return i; + } else { + ShortBuffer indices = getIndicesOrNull(); + return indices.get(indices.position() + i); + } + } + + ShortBuffer getIndicesOrNull() { + if (userIndices == GENERATE_SUCCESSIVE_LATER) { + return null; + } + + return userIndices; + } + + public int getIndexCount() { + if (userIndices == GENERATE_SUCCESSIVE_LATER) { + return getVertexCount(); + } + + return userIndices.remaining(); + } + + public Face setIndices(ShortBuffer indices) { + if (indices == null) { + indices = GENERATE_SUCCESSIVE_LATER; + } + + this.userIndices = indices; + markForIndexUpdate(); + + if (shape != null) checkIndices(); + + return this; + } + + private ShortBuffer generateSuccessiveIndices(int offset) { + int vertexCount = getVertexCount(); + ShortBuffer result = ShortBuffer.allocate(vertexCount); + + for (short vertex = 0; vertex < vertexCount; ++vertex) { + result.put((short) (vertex + offset)); + } + + result.flip(); + return result; + } + + int getLocationOfIndices() { + return locationOfIndices; + } + + int getByteOffsetOfIndices() { + return locationOfIndices * Short.BYTES; + } + + public Texture getTexture() { + return texture; + } + + public void setTexture(Texture texture) { + this.texture = Objects.requireNonNull(texture, "texture"); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java new file mode 100644 index 0000000..6c5d6c9 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Faces.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.nio.ShortBuffer; + +import glm.vec._2.Vec2; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram.VertexBuilder; +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.common.block.BlockFace; + +public class Faces { + + private Faces() {} + + public static Face createRectangle( + Texture texture, + Vec3 colorMultiplier, + Vec3 origin, + Vec3 width, + Vec3 height + ) { + VertexBuilder builder = new VertexBuilder(); + + Vec3 pos = new Vec3(); + Vec2 texCoords = new Vec2(); + + builder.addVertex( + origin, + colorMultiplier, + texCoords.set(0, 0) + ).addVertex( + pos.set(origin).add(height), + colorMultiplier, + texCoords.set(0, 1) + ).addVertex( + pos.set(origin).add(width), + colorMultiplier, + texCoords.set(1, 0) + ).addVertex( + pos.add(height), + colorMultiplier, + texCoords.set(1, 1) + ); + + return new Face( + texture, + builder.assemble(), + ShortBuffer.wrap(new short[] { + 3, 1, 0, + 2, 3, 0 + }) + ); + } + + public static Face createBlockFace( + Texture texture, + Vec3 colorMultiplier, + Vec3 blockCenter, + BlockFace face + ) { + switch (face) { + case TOP: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(-0.5f, +0.5f, +0.5f), + new Vec3( 0, -1, 0), + new Vec3(+1, 0, 0) + ); + case BOTTOM: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(-0.5f, -0.5f, -0.5f), + new Vec3( 0, +1, 0), + new Vec3(+1, 0, 0) + ); + case NORTH: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(+0.5f, -0.5f, -0.5f), + new Vec3( 0, +1, 0), + new Vec3( 0, 0, +1) + ); + case SOUTH: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(-0.5f, +0.5f, -0.5f), + new Vec3( 0, -1, 0), + new Vec3( 0, 0, +1) + ); + case EAST: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(-0.5f, -0.5f, -0.5f), + new Vec3(+1, 0, 0), + new Vec3( 0, 0, +1) + ); + case WEST: + return createRectangle( + texture, colorMultiplier, + blockCenter.add(+0.5f, +0.5f, -0.5f), + new Vec3(-1, 0, 0), + new Vec3( 0, 0, +1) + ); + default: + throw new NullPointerException("face"); + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java new file mode 100644 index 0000000..b65ed39 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Model.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import glm.mat._4.Mat4; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public abstract class Model implements WorldRenderable { + + private final WorldRenderable[] parts; + + public Model(WorldRenderable[] parts) { + this.parts = parts; + } + + protected abstract Mat4 getTransform(int partIndex); + + @Override + public void render(WorldRenderer renderer) { + for (int i = 0; i < parts.length; ++i) { + WorldRenderable part = parts[i]; + Mat4 transform = getTransform(i); + + try { + renderer.pushWorldTransform().mul(transform); + part.render(renderer); + } finally { + renderer.popWorldTransform(); + } + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java new file mode 100644 index 0000000..05dba75 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Shape.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; + +import org.lwjgl.BufferUtils; + +import ru.windcorp.optica.client.graphics.backend.Usage; +import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public class Shape implements WorldRenderable { + + private final ShapeRenderProgram program; + private final Face[] faces; + private final Usage usage; + + private ByteBuffer vertices; + private ShortBuffer indices; + + private boolean initialized = false; + private boolean needsAssembly = true; + private boolean needsVBOUpdate = true; + + private VertexBufferObject verticesVbo; + private VertexBufferObject indicesVbo; + + public Shape(Usage usage, ShapeRenderProgram program, Face... faces) { + this.program = program; + this.faces = faces; + this.usage = usage; + + configureFaces(); + + assembleBuffers(); + } + + public Shape(Usage usage, Face... faces) { + this(usage, ShapeRenderProgram.getDefault(), faces); + } + + private void configureFaces() { + for (Face face : faces) { + face.setShape(this); + face.computeNormals(); + } + } + + private void assembleBuffers() { + // TODO optimize: only update faces that requested it + + resizeBuffers(); + + for (Face face : faces) { + assembleVertices(face); + assembleIndices(face); + face.resetUpdateFlags(); + } + + this.vertices.flip(); + this.indices.flip(); + + needsAssembly = false; + needsVBOUpdate = true; + } + + private void resizeBuffers() { + int verticesRequired = 0, indicesRequired = 0; + for (Face face : faces) { + verticesRequired += face.getVertices().remaining(); + indicesRequired += face.getIndices().remaining(); + } + + if (this.vertices == null || vertices.capacity() < verticesRequired) { + this.vertices = BufferUtils.createByteBuffer(verticesRequired); + } else { + this.vertices.position(0).limit(verticesRequired); + } + + if (this.indices == null || this.indices.capacity() < indicesRequired) { + this.indices = BufferUtils.createShortBuffer(indicesRequired); + } else { + this.indices.position(0).limit(indicesRequired); + } + } + + private void assembleVertices(Face face) { + face.locationOfVertices = this.vertices.position(); + + insertVertices(face); + linkVerticesWith(face); + } + + private void insertVertices(Face face) { + ByteBuffer faceVertices = face.getVertices(); + + faceVertices.mark(); + this.vertices.put(faceVertices); + faceVertices.reset(); + } + + private void linkVerticesWith(Face face) { + int limit = vertices.limit(); + int position = vertices.position(); + + vertices.limit(position).position(face.getLocationOfVertices()); + face.vertices = vertices.slice(); + + vertices.position(position).limit(limit); + } + + private void assembleIndices(Face face) { + short vertexOffset = (short) ( + face.getLocationOfVertices() / program.getBytesPerVertex() + ); + + face.locationOfIndices = indices.position(); + + ShortBuffer faceIndices = face.getIndices(); + + if (faceIndices == null) { + for (int i = 0; i < face.getVertexCount(); ++i) { + this.indices.put((short) (vertexOffset + i)); + } + } else { + for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) { + short faceIndex = faceIndices.get(i); + faceIndex += vertexOffset; + this.indices.put(faceIndex); + } + } + } + + void markForReassembly() { + needsAssembly = true; + } + + @Override + public void render(WorldRenderer renderer) { + if (!initialized) initialize(); + if (needsAssembly) assembleBuffers(); + if (needsVBOUpdate) updateVBO(); + + program.render(renderer, this); + } + + private void initialize() { + verticesVbo = new VertexBufferObject(usage); + indicesVbo = new VertexBufferObject(usage); + needsVBOUpdate = true; + + initialized = true; + } + + private void updateVBO() { + verticesVbo.setData(vertices); + indicesVbo.setData(indices); + + needsVBOUpdate = false; + } + + VertexBufferObject getVerticesVbo() { + return verticesVbo; + } + + VertexBufferObject getIndicesVbo() { + return indicesVbo; + } + + public ShapeRenderProgram getProgram() { + return program; + } + + public Face[] getFaces() { + return faces; + } + + public Usage getUsage() { + return usage; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java new file mode 100644 index 0000000..e0560ca --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/ShapeRenderProgram.java @@ -0,0 +1,298 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; + +import com.google.common.collect.ObjectArrays; + +import glm.vec._2.Vec2; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.backend.VertexBufferObject; +import ru.windcorp.optica.client.graphics.backend.VertexBufferObject.BindTarget; +import ru.windcorp.optica.client.graphics.backend.shaders.CombinedShader; +import ru.windcorp.optica.client.graphics.backend.shaders.Program; +import ru.windcorp.optica.client.graphics.backend.shaders.attributes.AttributeVertexArray; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform1Int; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform2Float; +import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform4Matrix; +import ru.windcorp.optica.client.graphics.texture.Sprite; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public class ShapeRenderProgram extends Program { + + private static ShapeRenderProgram def = null; + + public static void init() { + def = new ShapeRenderProgram( + new String[] {"WorldDefault.vertex.glsl"}, + new String[] {"WorldDefault.fragment.glsl"} + ); + } + + public static ShapeRenderProgram getDefault() { + return def; + } + + private static final int DEFAULT_BYTES_PER_VERTEX = + 3 * Float.BYTES + // Position + 3 * Float.BYTES + // Color multiplier + 2 * Float.BYTES + // Texture coordinates + 3 * Float.BYTES; // Normals + + private static final String SHAPE_VERTEX_SHADER_RESOURCE = + "Shape.vertex.glsl"; + private static final String SHAPE_FRAGMENT_SHADER_RESOURCE = + "Shape.fragment.glsl"; + + private static final String + FINAL_TRANSFORM_UNIFORM_NAME = "finalTransform", + WORLD_TRANSFORM_UNIFORM_NAME = "worldTransform", + POSITIONS_ATTRIBUTE_NAME = "inputPositions", + COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier", + TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords", + TEXTURE_SLOT_UNIFORM_NAME = "textureSlot", + TEXTURE_START_UNIFORM_NAME = "textureStart", + TEXTURE_SIZE_UNIFORM_NAME = "textureSize", + NORMALS_ATTRIBUTE_NAME = "inputNormals"; + + private final Uniform4Matrix finalTransformUniform; + private final Uniform4Matrix worldTransformUniform; + private final AttributeVertexArray positionsAttribute; + private final AttributeVertexArray colorsAttribute; + private final AttributeVertexArray textureCoordsAttribute; + private final Uniform1Int textureSlotUniform; + private final Uniform2Float textureStartUniform; + private final Uniform2Float textureSizeUniform; + private final AttributeVertexArray normalsAttribute; + + public ShapeRenderProgram( + String[] vertexShaderResources, + String[] fragmentShaderResources + ) { + super( + new CombinedShader( + attachVertexShader(vertexShaderResources) + ), + new CombinedShader( + attachFragmentShader(fragmentShaderResources) + ) + ); + + this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME) + .as4Matrix(); + + this.worldTransformUniform = getUniform(WORLD_TRANSFORM_UNIFORM_NAME) + .as4Matrix(); + + this.positionsAttribute = + getAttribute(POSITIONS_ATTRIBUTE_NAME).asVertexArray(); + + this.colorsAttribute = + getAttribute(COLOR_MULTIPLER_ATTRIBUTE_NAME).asVertexArray(); + + this.textureCoordsAttribute = + getAttribute(TEXTURE_COORDS_ATTRIBUTE_NAME).asVertexArray(); + + this.textureSlotUniform = getUniform(TEXTURE_SLOT_UNIFORM_NAME) + .as1Int(); + + this.textureStartUniform = getUniform(TEXTURE_START_UNIFORM_NAME) + .as2Float(); + + this.textureSizeUniform = getUniform(TEXTURE_SIZE_UNIFORM_NAME) + .as2Float(); + + this.normalsAttribute = getAttribute(NORMALS_ATTRIBUTE_NAME) + .asVertexArray(); + } + + private static String[] attachVertexShader(String[] others) { + return ObjectArrays.concat(SHAPE_VERTEX_SHADER_RESOURCE, others); + } + + private static String[] attachFragmentShader(String[] others) { + return ObjectArrays.concat(SHAPE_FRAGMENT_SHADER_RESOURCE, others); + } + + public void render( + WorldRenderer renderer, + Shape shape + ) { + use(); + configure(renderer); + + bindVertices(shape.getVerticesVbo()); + bindIndices(shape.getIndicesVbo()); + + try { + positionsAttribute.enable(); + colorsAttribute.enable(); + textureCoordsAttribute.enable(); + normalsAttribute.enable(); + + for (Face face : shape.getFaces()) { + renderFace(face); + } + + } finally { + positionsAttribute.disable(); + colorsAttribute.disable(); + textureCoordsAttribute.disable(); + normalsAttribute.disable(); + } + } + + protected void configure(WorldRenderer renderer) { + finalTransformUniform.set(renderer.getFinalTransform()); + worldTransformUniform.set(renderer.getWorldTransform()); + } + + protected int bindVertices(VertexBufferObject vertices) { + int vertexStride = getBytesPerVertex(); + int offset = 0; + + positionsAttribute.set( + 3, GL11.GL_FLOAT, false, vertexStride, vertices, + offset + ); + offset += 3 * Float.BYTES; + + colorsAttribute.set( + 3, GL11.GL_FLOAT, false, vertexStride, vertices, + offset + ); + offset += 3 * Float.BYTES; + + textureCoordsAttribute.set( + 2, GL11.GL_FLOAT, false, vertexStride, vertices, + offset + ); + offset += 2 * Float.BYTES; + + normalsAttribute.set( + 3, GL11.GL_FLOAT, false, vertexStride, vertices, + offset + ); + offset += 3 * Float.BYTES; + + return offset; + } + + protected void bindIndices(VertexBufferObject indices) { + indices.bind(BindTarget.ELEMENT_ARRAY); + } + + protected void renderFace(Face face) { + Sprite sprite = face.getTexture().getSprite(); + + sprite.getPrimitive().bind(0); + textureSlotUniform.set(0); + + textureStartUniform.set(sprite.getStart()); + textureSizeUniform.set(sprite.getSize()); + + GL11.glDrawElements( + GL11.GL_TRIANGLES, + face.getIndexCount(), + GL11.GL_UNSIGNED_SHORT, + face.getByteOffsetOfIndices() + ); + } + + public int getBytesPerVertex() { + return DEFAULT_BYTES_PER_VERTEX; + } + + public static class VertexBuilder { + + private static class Vertex { + final Vec3 position; + final Vec3 colorMultiplier; + final Vec2 textureCoords; + + Vertex(Vec3 position, Vec3 colorMultiplier, Vec2 textureCoords) { + this.position = position; + this.colorMultiplier = colorMultiplier; + this.textureCoords = textureCoords; + } + } + + private final List vertices = new ArrayList<>(); + + public VertexBuilder addVertex( + float x, float y, float z, + float r, float g, float b, + float tx, float ty + ) { + vertices.add(new Vertex( + new Vec3(x, y, z), + new Vec3(r, g, b), + new Vec2(tx, ty) + )); + + return this; + } + + public VertexBuilder addVertex( + Vec3 position, + Vec3 colorMultiplier, + Vec2 textureCoords + ) { + vertices.add(new Vertex( + new Vec3(position), + new Vec3(colorMultiplier), + new Vec2(textureCoords) + )); + + return this; + } + + public ByteBuffer assemble() { + ByteBuffer result = BufferUtils.createByteBuffer( + DEFAULT_BYTES_PER_VERTEX * vertices.size() + ); + + for (Vertex v : vertices) { + result + .putFloat(v.position.x) + .putFloat(v.position.y) + .putFloat(v.position.z) + .putFloat(v.colorMultiplier.x) + .putFloat(v.colorMultiplier.y) + .putFloat(v.colorMultiplier.z) + .putFloat(v.textureCoords.x) + .putFloat(v.textureCoords.y) + .putFloat(Float.NaN) + .putFloat(Float.NaN) + .putFloat(Float.NaN); + } + + result.flip(); + + return result; + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java b/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java new file mode 100644 index 0000000..54dac98 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/Shapes.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.backend.Usage; +import ru.windcorp.optica.client.graphics.texture.Texture; + +public class Shapes { + + public static Shape createParallelepiped( // Try saying that 10 times fast + Vec3 origin, + + Vec3 width, + Vec3 height, + Vec3 depth, + + Vec3 colorMultiplier, + + Texture topTexture, + Texture bottomTexture, + Texture northTexture, + Texture southTexture, + Texture eastTexture, + Texture westTexture + ) { + + Vec3 faceOrigin = new Vec3(); + Vec3 faceWidth = new Vec3(); + + Face top = Faces.createRectangle( + topTexture, colorMultiplier, + faceOrigin.set(origin).add(height).add(width), + faceWidth.set(width).negate(), + depth + ); + + Face bottom = Faces.createRectangle( + bottomTexture, colorMultiplier, + origin, + width, + depth + ); + + Face north = Faces.createRectangle( + northTexture, colorMultiplier, + faceOrigin.set(origin).add(depth), + width, + height + ); + + Face south = Faces.createRectangle( + southTexture, colorMultiplier, + faceOrigin.set(origin).add(width), + faceWidth.set(width).negate(), + height + ); + + Face east = Faces.createRectangle( + eastTexture, colorMultiplier, + origin, + depth, + height + ); + + Face west = Faces.createRectangle( + westTexture, colorMultiplier, + faceOrigin.set(origin).add(width).add(depth), + faceWidth.set(depth).negate(), + height + ); + + Shape result = new Shape( + Usage.STATIC, + top, bottom, north, south, east, west + ); + + return result; + } + + public static class PppBuilder { + + private final Vec3 origin = new Vec3(-0.5f, -0.5f, -0.5f); + + private final Vec3 depth = new Vec3(1, 0, 0); + private final Vec3 width = new Vec3(0, 1, 0); + private final Vec3 height = new Vec3(0, 0, 1); + + private final Vec3 colorMultiplier = new Vec3(1, 1, 1); + + private final Texture topTexture; + private final Texture bottomTexture; + private final Texture northTexture; + private final Texture southTexture; + private final Texture eastTexture; + private final Texture westTexture; + + public PppBuilder( + Texture top, + Texture bottom, + Texture north, + Texture south, + Texture east, + Texture west + ) { + this.topTexture = top; + this.bottomTexture = bottom; + this.northTexture = north; + this.southTexture = south; + this.eastTexture = east; + this.westTexture = west; + } + + public PppBuilder(Texture texture) { + this(texture, texture, texture, texture, texture, texture); + } + + public PppBuilder setOrigin(Vec3 origin) { + this.origin.set(origin); + return this; + } + + public PppBuilder setOrigin(float x, float y, float z) { + this.origin.set(x, y, z); + return this; + } + + public PppBuilder setColorMultiplier(Vec3 colorMultiplier) { + this.colorMultiplier.set(colorMultiplier); + return this; + } + + public PppBuilder setColorMultiplier(float r, float g, float b) { + this.colorMultiplier.set(r, g, b); + return this; + } + + public PppBuilder setDepth(Vec3 vector) { + this.depth.set(vector); + return this; + } + + public PppBuilder setDepth(float x, float y, float z) { + this.depth.set(x, y, z); + return this; + } + + public PppBuilder setDepth(float x) { + this.depth.set(x, 0, 0); + return this; + } + + public PppBuilder setWidth(Vec3 vector) { + this.width.set(vector); + return this; + } + + public PppBuilder setWidth(float x, float y, float z) { + this.width.set(x, y, z); + return this; + } + + public PppBuilder setWidth(float y) { + this.width.set(0, y, 0); + return this; + } + + public PppBuilder setHeight(Vec3 vector) { + this.height.set(vector); + return this; + } + + public PppBuilder setHeight(float x, float y, float z) { + this.height.set(x, y, z); + return this; + } + + public PppBuilder setHeight(float z) { + this.height.set(0, 0, z); + return this; + } + + public PppBuilder setSize(float x, float y, float z) { + return this.setWidth(x).setDepth(y).setHeight(z); + } + + public Shape create() { + return createParallelepiped( + origin, + width, height, depth, + colorMultiplier, + topTexture, + bottomTexture, + northTexture, + southTexture, + eastTexture, + westTexture + ); + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/StaticModel.java b/src/main/java/ru/windcorp/optica/client/graphics/model/StaticModel.java new file mode 100644 index 0000000..84c755c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/StaticModel.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import glm.mat._4.Mat4; + +public class StaticModel extends Model { + + private static final Mat4 IDENTITY = new Mat4(); + + private final Mat4[] transforms; + + public StaticModel( + WorldRenderable[] parts, + Mat4[] transforms + ) { + super(parts); + this.transforms = transforms; + } + + public StaticModel(Builder builder) { + this(builder.getParts(), builder.getTransforms()); + } + + @Override + protected Mat4 getTransform(int partIndex) { + return transforms[partIndex]; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final List parts = new ArrayList<>(); + private final List transforms = new ArrayList<>(); + + protected Builder() {} + + public Builder addPart( + WorldRenderable part, + Mat4 transform + ) { + parts.add(Objects.requireNonNull(part, "part")); + transforms.add(Objects.requireNonNull(transform, "transform")); + + return this; + } + + public Builder addPart( + WorldRenderable part + ) { + return addPart(part, IDENTITY); + } + + private WorldRenderable[] getParts() { + return parts.toArray(new WorldRenderable[parts.size()]); + } + + private Mat4[] getTransforms() { + return transforms.toArray(new Mat4[transforms.size()]); + } + + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java b/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java new file mode 100644 index 0000000..e8fbe90 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/model/WorldRenderable.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.model; + +import ru.windcorp.optica.client.graphics.world.WorldRenderer; + +public interface WorldRenderable { + + void render(WorldRenderer renderer); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java new file mode 100644 index 0000000..2971afc --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL12.*; + +import java.nio.ByteBuffer; + +class Pixels { + + private final ByteBuffer data; + + private final int bufferWidth; + private final int bufferHeight; + + private final boolean filtered; + + public Pixels( + ByteBuffer data, + int bufferWidth, int bufferHeight, + boolean filtered + ) { + this.data = data; + this.bufferWidth = bufferWidth; + this.bufferHeight = bufferHeight; + this.filtered = filtered; + } + + public int load() { + int handle = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, handle); + + if (filtered) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D( + GL_TEXTURE_2D, // Load 2D image + 0, // Not mipmapped + GL_RGBA, // Use RGBA + bufferWidth, // Width + bufferHeight, // Height + 0, // No border + GL_RGBA, // Use RGBA (required) + GL_UNSIGNED_BYTE, // Use unsigned bytes + data // Data buffer + ); + + glBindTexture(GL_TEXTURE_2D, 0); + + return handle; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/SimpleTexture.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/SimpleTexture.java new file mode 100644 index 0000000..0af8360 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/SimpleTexture.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +public class SimpleTexture extends Texture { + + private final Sprite sprite; + + public SimpleTexture(Sprite sprite) { + this.sprite = sprite; + } + + @Override + public Sprite getSprite() { + return sprite; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/Sprite.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/Sprite.java new file mode 100644 index 0000000..24f53df --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/Sprite.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +import java.util.Objects; + +import glm.vec._2.Vec2; + +public class Sprite { + + private static final Vec2 ORIGIN = new Vec2(0, 0); + private static final Vec2 FULL_PRIMITIVE = new Vec2(1, 1); + + private final TexturePrimitive primitive; + + private final Vec2 start; + private final Vec2 size; + + public Sprite(TexturePrimitive primitive, Vec2 start, Vec2 size) { + this.primitive = Objects.requireNonNull(primitive, "primitive"); + this.start = Objects.requireNonNull(start, "start"); + this.size = Objects.requireNonNull(size, "size"); + } + + public Sprite(TexturePrimitive primitive) { + this(primitive, ORIGIN, FULL_PRIMITIVE); + } + + public TexturePrimitive getPrimitive() { + return primitive; + } + + public Vec2 getStart() { + return start; + } + + public Vec2 getSize() { + return size; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/Texture.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/Texture.java new file mode 100644 index 0000000..be36348 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/Texture.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +public abstract class Texture { + + public abstract Sprite getSprite(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java new file mode 100644 index 0000000..419b8e8 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +import java.awt.Graphics; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Hashtable; +import javax.imageio.ImageIO; + +import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; +import ru.windcorp.optica.common.resource.Resource; +import ru.windcorp.optica.common.resource.ResourceManager; + +public class TextureManager { + + private static final ColorModel COLOR_MODEL = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB + new int[] {8, 8, 8, 8}, // Use every bit + true, // Has alpha + false, // Not premultiplied + ComponentColorModel.TRANSLUCENT, // Can have any alpha + DataBuffer.TYPE_BYTE // Alpha is one byte + ); + + private static final Hashtable CANVAS_PROPERTIES = new Hashtable<>(); + private static final java.awt.Color CANVAS_BACKGROUND = + new java.awt.Color(0, 0, 0, 0); + + private static final String TEXTURE_ASSETS_PREFIX = "assets/textures/"; + + private static Resource getResource(String textureName) { + return ResourceManager.getResource( + TEXTURE_ASSETS_PREFIX + textureName + ".png" + ); + } + + public static TexturePrimitive load(String textureName, boolean filtered) { + TexturePrimitive result = loadToByteBuffer(textureName, filtered); + RenderTaskQueue.invokeLater(result::load); + return result; + } + + public static TexturePrimitive loadToByteBuffer( + String textureName, boolean filter + ) { + Resource resource = getResource(textureName); + + BufferedImage source = readImage(resource); + + int bufferWidth = toPowerOf2(source.getWidth()), + bufferHeight = toPowerOf2(source.getHeight()); + + WritableRaster raster = Raster.createInterleavedRaster( + DataBuffer.TYPE_BYTE, // Storage model + bufferWidth, // Buffer width + bufferHeight, // Buffer height + 4, // RGBA + null // Location (here (0; 0)) + ); + + BufferedImage canvas = new BufferedImage( + COLOR_MODEL, // Color model + raster, // Backing raster + false, // Raster is not premultipied + CANVAS_PROPERTIES // Properties + ); + + Graphics g = canvas.createGraphics(); + g.setColor(CANVAS_BACKGROUND); + g.fillRect(0, 0, source.getWidth(), source.getHeight()); + g.drawImage( + source, + 0, 0, source.getWidth(), source.getHeight(), + 0, source.getHeight(), source.getWidth(), 0, // Flip the image + null + ); + g.dispose(); + + byte[] data = ( + (DataBufferByte) canvas.getRaster().getDataBuffer() + ).getData(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(data.length); + buffer.order(ByteOrder.nativeOrder()); + buffer.put(data); + buffer.flip(); + + Pixels pixels = new Pixels(buffer, bufferWidth, bufferHeight, filter); + + TexturePrimitive result = new TexturePrimitive( + pixels, + source.getWidth(), + source.getHeight(), + bufferWidth, + bufferHeight + ); + + return result; + } + + private static BufferedImage readImage(Resource resource) { + try { + return ImageIO.read(resource.getInputStream()); + } catch (Exception e) { + throw new RuntimeException("too bad. refresh project u stupid. must be " + resource.getName(), e); + } + } + + private static int toPowerOf2(int i) { + + // TODO optimize + + int result = 1; + do { + result *= 2; + } while (result < i); + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java new file mode 100644 index 0000000..a2833a4 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.texture; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker; +import ru.windcorp.optica.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; + +public class TexturePrimitive implements OpenGLDeletable { + + private static final int NOT_LOADED = -1; + + private int handle = NOT_LOADED; + private Pixels pixelsToLoad; + + private final int width; + private final int height; + private final int bufferWidth; + private final int bufferHeight; + + protected TexturePrimitive( + Pixels pixels, + int width, int height, + int bufferWidth, int bufferHeight + ) { + this.pixelsToLoad = pixels; + this.width = width; + this.height = height; + this.bufferWidth = bufferWidth; + this.bufferHeight = bufferHeight; + + OpenGLObjectTracker.register(this); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getBufferWidth() { + return bufferWidth; + } + + public int getBufferHeight() { + return bufferHeight; + } + + public boolean isLoaded() { + return handle != NOT_LOADED; + } + + public void bind(int slot) { + if (!isLoaded()) { + load(); + } + + int code = GL_TEXTURE0 + slot; + + glActiveTexture(code); + glBindTexture(GL_TEXTURE_2D, handle); + } + + protected void load() { + if (isLoaded()) return; + + handle = pixelsToLoad.load(); + + if (handle < 0) { + throw new RuntimeException("oops"); + } + + pixelsToLoad = null; + } + + @Override + public void delete() { + if (isLoaded()) + glDeleteTextures(handle); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java new file mode 100644 index 0000000..071575e --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/Camera.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.world; + +import static java.lang.Math.*; + +import glm.Glm; +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; + +public class Camera { + + private final Vec3 position = new Vec3(); + + private float pitch; + private float yaw; + + private float fieldOfView; + + public Camera(Vec3 position, float pitch, float yaw, float fieldOfView) { + teleport(position); + setPitch(pitch); + setYaw(yaw); + setFieldOfView(fieldOfView); + } + + public Camera() {} + + public void apply(WorldRenderer renderer) { + Mat4 previous = renderer.getViewTransform(); + Glm.perspective( + computeFovY(), + GraphicsInterface.getAspectRatio(), + 0.01f, 10000.0f, + renderer.pushViewTransform() + ).mul(previous); + + renderer.pushViewTransform().rotateX(pitch).rotateY(yaw); + + renderer.pushViewTransform().translate(position.negate()); + position.negate(); + } + + private float computeFovY() { + float widthOverHeight = GraphicsInterface.getAspectRatio(); + + if (widthOverHeight >= 1) { + return fieldOfView; + } else { + return (float) (2 * atan( + 1 / widthOverHeight + * + tan(fieldOfView / 2) + )); + } + } + + public Vec3 getPosition() { + return position; + } + + public void teleport(Vec3 pos) { + position.set(pos); + } + + public void move(Vec3 pos) { + position.add(pos); + } + + public float getPitch() { + return pitch; + } + + public void setPitch(float pitch) { + final float maxPitch = (float) (Math.PI / 2); + this.pitch = Glm.clamp(pitch, -maxPitch, +maxPitch); + } + + public float getYaw() { + return yaw; + } + + public void setYaw(float yaw) { + this.yaw = Glm.mod(yaw, 2 * (float) PI); + } + + public void setDirection(float pitch, float yaw) { + setPitch(pitch); + setYaw(yaw); + } + + public void turn(float pitchChange, float yawChange) { + setPitch(getPitch() + pitchChange); + setYaw(getYaw() + yawChange); + } + + public float getFieldOfView() { + return fieldOfView; + } + + public void setFieldOfView(float fieldOfView) { + this.fieldOfView = fieldOfView; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java new file mode 100644 index 0000000..ec6a41a --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/LayerWorld.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.world; + +import org.lwjgl.glfw.GLFW; + +import com.google.common.eventbus.Subscribe; + +import glm.mat._3.Mat3; +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.Layer; +import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; +import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; +import ru.windcorp.optica.client.graphics.input.CursorMoveEvent; +import ru.windcorp.optica.client.graphics.input.KeyEvent; +import ru.windcorp.optica.client.world.WorldRender; +import ru.windcorp.optica.common.world.WorldData; + +public class LayerWorld extends Layer { + + private final Camera camera = new Camera( + new Vec3(8, 8, 8), + 0, 0, + (float) Math.toRadians(70) + ); + + private final Vec3 velocity = new Vec3(); + private final Vec3 tmp = new Vec3(); + + private final Mat3 angMat = new Mat3(); + + private int movementX = 0; + private int movementY = 0; + private int movementZ = 0; + + private final WorldRenderer renderer = new WorldRenderer(); + + private final WorldRender world = new WorldRender(new WorldData()); + + public LayerWorld() { + super("World"); + } + + @Override + protected void initialize() { + // TODO Auto-generated method stub + GraphicsInterface.subscribeToInputEvents(this); + } + + @Override + protected void doRender() { + camera.apply(renderer); + renderWorld(); + renderer.reset(); + + angMat.set().rotateY(-camera.getYaw()); + + tmp.set(movementX, 0, movementZ); + angMat.mul_(tmp); // bug in jglm + tmp.y = movementY; + tmp.mul(0.1f); + tmp.sub(velocity); + tmp.mul(0.1f); + velocity.add(tmp); + tmp.set(velocity); + tmp.mul((float) (GraphicsInterface.getFrameLength() * 60)); + camera.move(tmp); + } + + private void renderWorld() { + world.render(renderer); + } + + public Camera getCamera() { + return camera; + } + + private boolean flag = true; + + @Subscribe + public void onKeyEvent(KeyEvent event) { + if (event.isRepeat()) return; + + int multiplier = event.isPress() ? 1 : -1; + + switch (event.getKey()) { + case GLFW.GLFW_KEY_W: + movementZ += -1 * multiplier; + break; + case GLFW.GLFW_KEY_S: + movementZ += +1 * multiplier; + break; + case GLFW.GLFW_KEY_A: + movementX += -1 * multiplier; + break; + case GLFW.GLFW_KEY_D: + movementX += +1 * multiplier; + break; + case GLFW.GLFW_KEY_SPACE: + movementY += +1 * multiplier; + break; + case GLFW.GLFW_KEY_LEFT_SHIFT: + movementY += -1 * multiplier; + break; + + case GLFW.GLFW_KEY_ESCAPE: + if (!event.isPress()) return; + + if (flag) { + GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); + } else { + GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + flag = !flag; + break; + } + } + + @Subscribe + public void onMouseMoved(CursorMoveEvent event) { + if (!flag) return; + + final float yawScale = 0.002f; + final float pitchScale = yawScale; + + camera.turn( + (float) (event.getChangeY() * pitchScale), + (float) (event.getChangeX() * yawScale) + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java new file mode 100644 index 0000000..1733b4f --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/world/WorldRenderer.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.graphics.world; + +import glm.mat._4.Mat4; +import ru.windcorp.optica.common.util.StashingStack; + +public class WorldRenderer { + + private static final int TRANSFORM_STACK_SIZE = 64; + + private final StashingStack worldTransformStack = new StashingStack<>( + TRANSFORM_STACK_SIZE, Mat4::new + ); + + private final StashingStack viewTransformStack = new StashingStack<>( + TRANSFORM_STACK_SIZE, Mat4::new + ); + + private final Mat4 finalTransform = new Mat4(); + + { + reset(); + } + + public Mat4 pushWorldTransform() { + Mat4 previous = worldTransformStack.getHead(); + return worldTransformStack.push().set(previous); + } + + public void popWorldTransform() { + worldTransformStack.removeHead(); + } + + public Mat4 getWorldTransform() { + return worldTransformStack.getHead(); + } + + public Mat4 pushViewTransform() { + Mat4 previous = viewTransformStack.getHead(); + return viewTransformStack.push().set(previous); + } + + public void popViewTransform() { + viewTransformStack.removeHead(); + } + + public Mat4 getViewTransform() { + return viewTransformStack.getHead(); + } + + public Mat4 getFinalTransform() { + return finalTransform.set(getViewTransform()).mul(getWorldTransform()); + } + + public void reset() { + worldTransformStack.removeAll(); + worldTransformStack.push().identity(); + viewTransformStack.removeAll(); + viewTransformStack.push().identity(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java b/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java new file mode 100644 index 0000000..7282de5 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/ChunkRender.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world; + +import java.util.HashMap; +import java.util.Map; + +import glm.mat._4.Mat4; +import ru.windcorp.optica.client.graphics.model.Model; +import ru.windcorp.optica.client.graphics.model.Shape; +import ru.windcorp.optica.client.graphics.model.StaticModel; +import ru.windcorp.optica.client.graphics.model.StaticModel.Builder; +import ru.windcorp.optica.client.graphics.model.WorldRenderable; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.client.world.renders.BlockRender; +import ru.windcorp.optica.client.world.renders.BlockRenderNone; +import ru.windcorp.optica.client.world.renders.BlockRenders; +import ru.windcorp.optica.client.world.renders.bro.BlockRenderOptimizer; +import ru.windcorp.optica.client.world.renders.bro.BlockRenderOptimizerGenerator; +import ru.windcorp.optica.client.world.renders.bro.BlockRenderOptimizerGenerators; +import ru.windcorp.optica.common.world.ChunkData; + +public class ChunkRender { + + private final WorldRender world; + private final ChunkData data; + + private Model model = null; + + public ChunkRender(WorldRender world, ChunkData data) { + this.world = world; + this.data = data; + } + + public WorldRender getWorld() { + return world; + } + + public ChunkData getData() { + return data; + } + + public BlockRender getBlock(int xInChunk, int yInChunk, int zInChunk) { + return BlockRenders.get( + getData().getBlock(xInChunk, yInChunk, zInChunk).getId() + ); + } + + public void render(WorldRenderer renderer) { + if (model == null) { + buildModel(); + } + + renderer.pushWorldTransform().translate( + data.getX() * ChunkData.BLOCKS_PER_CHUNK, + data.getY() * ChunkData.BLOCKS_PER_CHUNK, + data.getZ() * ChunkData.BLOCKS_PER_CHUNK + ); + + model.render(renderer); + + renderer.popWorldTransform(); + } + + private void buildModel() { + Map optimizers = new HashMap<>(); + + for ( + BlockRenderOptimizerGenerator generator : + BlockRenderOptimizerGenerators.getAll() + ) { + BlockRenderOptimizer optimizer = generator.createOptimizer(); + optimizers.put(generator.getId(), optimizer); + optimizer.startRender(this); + } + + StaticModel.Builder builder = StaticModel.builder(); + + for (int x = 0; x < ChunkData.BLOCKS_PER_CHUNK; ++x) { + for (int y = 0; y < ChunkData.BLOCKS_PER_CHUNK; ++y) { + for (int z = 0; z < ChunkData.BLOCKS_PER_CHUNK; ++z) { + + BlockRender block = getBlock(x, y, z); + + if (block instanceof BlockRenderNone) { + continue; + } + + if (tryToForwardToOptimizers(block, x, y, z, optimizers)) { + continue; + } + + if (tryToCreateRenderable(block, x, y, z, builder)) { + continue; + } + + addRenderAsRenderable(block, x, y, z, builder); + } + } + } + + for (BlockRenderOptimizer optimizer : optimizers.values()) { + Shape result = optimizer.endRender(); + if (result != null) { + builder.addPart(result); + } + } + + model = new StaticModel(builder); + } + + private boolean tryToForwardToOptimizers( + BlockRender block, int x, int y, int z, + Map optimizers + ) { + if (!block.isOptimized()) { + return false; + } + BlockRenderOptimizer optimizer = optimizers.get(block.getOptimizer()); + + if (optimizer == null) { + return false; + } + + optimizer.processBlock(block, x, y, z); + + return true; + } + + private boolean tryToCreateRenderable( + BlockRender block, int x, int y, int z, + Builder builder + ) { + WorldRenderable renderable = block.createRenderable(); + + if (renderable == null) { + return false; + } + + builder.addPart(renderable, new Mat4().identity().translate(x, y, z)); + return true; + } + + private void addRenderAsRenderable( + BlockRender block, int x, int y, int z, + Builder builder + ) { + builder.addPart( + block::render, + new Mat4().identity().translate(x, y, z) + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/WorldRender.java b/src/main/java/ru/windcorp/optica/client/world/WorldRender.java new file mode 100644 index 0000000..505c057 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/WorldRender.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world; + +import java.util.ArrayList; +import java.util.Collection; + +import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.common.world.ChunkData; +import ru.windcorp.optica.common.world.WorldData; + +public class WorldRender { + + private final WorldData data; + + private final Collection chunks = new ArrayList<>(); + + public WorldRender(WorldData data) { + this.data = data; + + for (ChunkData chunkData : data.getChunks()) { + chunks.add(new ChunkRender(this, chunkData)); + } + } + + public WorldData getData() { + return data; + } + + public void render(WorldRenderer renderer) { + renderer.pushWorldTransform().rotateX(-Math.PI / 2); + + for (ChunkRender chunk : chunks) { + chunk.render(renderer); + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java new file mode 100644 index 0000000..ca5731e --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRender.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders; + +import ru.windcorp.optica.client.graphics.model.WorldRenderable; +import ru.windcorp.optica.client.graphics.world.WorldRenderer; +import ru.windcorp.optica.common.util.Namespaced; + +public abstract class BlockRender extends Namespaced { + + private String optimizer = null; + + public BlockRender(String namespace, String name) { + super(namespace, name); + } + + public String getOptimizer() { + return optimizer; + } + + public boolean isOptimized() { + return getOptimizer() != null; + } + + public void setOptimizer(String optimizer) { + this.optimizer = optimizer; + } + + public void render(WorldRenderer renderer) { + throw new UnsupportedOperationException( + "BlockRender.render() not implemented" + ); + } + + public WorldRenderable createRenderable() { + return null; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderNone.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderNone.java new file mode 100644 index 0000000..2111d4c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderNone.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders; + +import ru.windcorp.optica.client.graphics.model.EmptyModel; +import ru.windcorp.optica.client.graphics.model.WorldRenderable; + +public class BlockRenderNone extends BlockRender { + + public BlockRenderNone(String namespace, String name) { + super(namespace, name); + } + + @Override + public WorldRenderable createRenderable() { + return EmptyModel.getInstance(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java new file mode 100644 index 0000000..f555c88 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTexturedCube.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders; + +import static ru.windcorp.optica.common.block.BlockFace.*; + +import java.util.EnumMap; + +import ru.windcorp.optica.client.graphics.model.Shapes; +import ru.windcorp.optica.client.graphics.model.WorldRenderable; +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.common.block.BlockFace; + +public abstract class BlockRenderTexturedCube extends BlockRender { + + private final EnumMap textures = + new EnumMap<>(BlockFace.class); + + public BlockRenderTexturedCube( + String namespace, String name, + Texture topTexture, Texture bottomTexture, + Texture northTexture, Texture southTexture, + Texture eastTexture, Texture westTexture + ) { + super(namespace, name); + + textures.put(TOP, topTexture); + textures.put(BOTTOM, bottomTexture); + textures.put(NORTH, northTexture); + textures.put(SOUTH, southTexture); + textures.put(EAST, eastTexture); + textures.put(WEST, westTexture); + } + + public Texture getTexture(BlockFace face) { + return textures.get(face); + } + + @Override + public WorldRenderable createRenderable() { + return new Shapes.PppBuilder( + getTexture(TOP), getTexture(BOTTOM), + getTexture(NORTH), getTexture(SOUTH), + getTexture(EAST), getTexture(WEST) + ).create(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTransparentCube.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTransparentCube.java new file mode 100644 index 0000000..efca0ec --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenderTransparentCube.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders; + +import ru.windcorp.optica.client.graphics.texture.Texture; + +public class BlockRenderTransparentCube extends BlockRenderTexturedCube { + + public BlockRenderTransparentCube( + String namespace, String name, + Texture topTexture, Texture bottomTexture, + Texture northTexture, Texture southTexture, + Texture eastTexture, Texture westTexture + ) { + super( + namespace, name, + topTexture, bottomTexture, + northTexture, southTexture, + eastTexture, westTexture + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java new file mode 100644 index 0000000..75be839 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders; + +import java.util.HashMap; +import java.util.Map; + +import ru.windcorp.optica.client.graphics.texture.SimpleTexture; +import ru.windcorp.optica.client.graphics.texture.Sprite; +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.graphics.texture.TextureManager; +import ru.windcorp.optica.client.world.renders.bro.BlockRenderOpaqueCube; + +public class BlockRenders { + + private static Texture grassTop = qtex("grass_top"); + private static Texture grassSide = qtex("grass_side"); + private static Texture dirtT = qtex("grass_bottom"); + private static Texture stoneT = qtex("stone"); + private static Texture glassT = qtex("glass_clear"); + + private static final Map BLOCK_RENDERS = + new HashMap<>(); + + private BlockRenders() {} + + static { + register(new BlockRenderOpaqueCube("Grass", "Test", grassTop, dirtT, grassSide, grassSide, grassSide, grassSide)); + register(new BlockRenderOpaqueCube("Dirt", "Test", dirtT, dirtT, dirtT, dirtT, dirtT, dirtT)); + register(new BlockRenderOpaqueCube("Stone", "Test", stoneT, stoneT, stoneT, stoneT, stoneT, stoneT)); + + register(new BlockRenderOpaqueCube("Compass", "Test", qtex("compass"), qtex("compass"), qtex("side_north"), qtex("side_south"), qtex("side_east"), qtex("side_west"))); + + register(new BlockRenderNone("Air", "Test")); + register(new BlockRenderTransparentCube("Glass", "Test", glassT, glassT, glassT, glassT, glassT, glassT)); + } + + public static BlockRender get(String name) { + return BLOCK_RENDERS.get(name); + } + + public static void register(BlockRender blockRender) { + BLOCK_RENDERS.put(blockRender.getId(), blockRender); + } + + private static Texture qtex(String name) { + return new SimpleTexture(new Sprite(TextureManager.load(name, false))); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCube.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCube.java new file mode 100644 index 0000000..2342a9c --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCube.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders.bro; + +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.world.renders.BlockRenderTexturedCube; + +public class BlockRenderOpaqueCube extends BlockRenderTexturedCube { + + public BlockRenderOpaqueCube( + String namespace, String name, + Texture topTexture, Texture bottomTexture, + Texture northTexture, Texture southTexture, + Texture eastTexture, Texture westTexture + ) { + super( + namespace, name, + topTexture, bottomTexture, + northTexture, southTexture, + eastTexture, westTexture + ); + setOptimizer("Default:OpaqueCube"); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java new file mode 100644 index 0000000..5450456 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOpaqueCubeOptimizer.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders.bro; + +import static ru.windcorp.optica.common.world.ChunkData.BLOCKS_PER_CHUNK; + +import java.util.ArrayList; +import java.util.Collection; + +import glm.vec._3.Vec3; +import ru.windcorp.optica.client.graphics.backend.Usage; +import ru.windcorp.optica.client.graphics.model.Face; +import ru.windcorp.optica.client.graphics.model.Faces; +import ru.windcorp.optica.client.graphics.model.Shape; +import ru.windcorp.optica.client.graphics.texture.Texture; +import ru.windcorp.optica.client.world.ChunkRender; +import ru.windcorp.optica.client.world.renders.BlockRender; +import ru.windcorp.optica.common.block.BlockFace; +import ru.windcorp.optica.common.world.ChunkData; + +public class BlockRenderOpaqueCubeOptimizer extends BlockRenderOptimizer { + + private static final int BLOCK_MASK = 1 << 7; + + private static final BlockFace[] GOOD_FACES = new BlockFace[] { + BlockFace.TOP, BlockFace.NORTH, BlockFace.WEST + }; + + private static final Vec3 COLOR_MULTIPLIER = new Vec3(1, 1, 1); + + private final byte[][][] data = new byte[BLOCKS_PER_CHUNK + 1] + [BLOCKS_PER_CHUNK + 1] + [BLOCKS_PER_CHUNK + 1]; + + private ChunkRender chunk; + + private final Vec3 blockCenter = new Vec3(); + + @Override + public void startRender(ChunkRender chunk) { + this.chunk = chunk; + } + + @Override + public void processBlock(BlockRender block, int x, int y, int z) { + addFace(x, y, z, BlockFace.TOP); + addFace(x, y, z, BlockFace.BOTTOM); + addFace(x, y, z, BlockFace.NORTH); + addFace(x, y, z, BlockFace.SOUTH); + addFace(x, y, z, BlockFace.EAST); + addFace(x, y, z, BlockFace.WEST); + addBlock(x, y, z); + } + + protected void addFace(int x, int y, int z, BlockFace face) { + switch (face) { + case BOTTOM: + z -= 1; + face = BlockFace.TOP; + break; + case SOUTH: + x -= 1; + face = BlockFace.NORTH; + break; + case EAST: + y -= 1; + face = BlockFace.WEST; + break; + default: + } + + data[x + 1][y + 1][z + 1] ^= 1 << face.ordinal(); + } + + protected void addBlock(int x, int y, int z) { + data[x + 1][y + 1][z + 1] |= BLOCK_MASK; + } + + protected boolean hasFace(int x, int y, int z, BlockFace face) { + switch (face) { + case BOTTOM: + z -= 1; + face = BlockFace.TOP; + break; + case SOUTH: + x -= 1; + face = BlockFace.NORTH; + break; + case EAST: + y -= 1; + face = BlockFace.WEST; + break; + default: + } + + return (data[x + 1][y + 1][z + 1] & 1 << face.ordinal()) != 0; + } + + protected boolean hasBlock(int x, int y, int z) { + return (data[x + 1][y + 1][z + 1] & BLOCK_MASK) != 0; + } + + @Override + public Shape endRender() { + + Collection shapeFaces = new ArrayList<>( + BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3 + + BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3 + ); + + for (int x = -1; x < ChunkData.BLOCKS_PER_CHUNK; ++x) { + for (int y = -1; y < ChunkData.BLOCKS_PER_CHUNK; ++y) { + for (int z = -1; z < ChunkData.BLOCKS_PER_CHUNK; ++z) { + for (BlockFace face : GOOD_FACES) { + + if (!hasFace(x, y, z, face)) continue; + + Face shapeFace = null; + + if (!hasBlock(x, y, z)) { + switch (face) { + case TOP: + shapeFace = createFace( + x, y, z + 1, + BlockFace.BOTTOM + ); + break; + case NORTH: + shapeFace = createFace( + x + 1, y, z, + BlockFace.SOUTH + ); + break; + case WEST: + shapeFace = createFace( + x, y + 1, z, + BlockFace.EAST + ); + break; + default: + } + } else { + shapeFace = createFace(x, y, z, face); + } + + shapeFaces.add(shapeFace); + + } + } + } + } + + return new Shape( + Usage.STATIC, + shapeFaces.toArray(new Face[shapeFaces.size()]) + ); + } + + private Face createFace(int x, int y, int z, BlockFace face) { + BlockRenderOpaqueCube blockRender = + (BlockRenderOpaqueCube) chunk.getBlock(x, y, z); + Texture texture = blockRender.getTexture(face); + + return Faces.createBlockFace( + texture, + COLOR_MULTIPLIER, + blockCenter.set(x, y, z), + face + ); + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizer.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizer.java new file mode 100644 index 0000000..33a6f8f --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizer.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders.bro; + +import ru.windcorp.optica.client.graphics.model.Shape; +import ru.windcorp.optica.client.world.ChunkRender; +import ru.windcorp.optica.client.world.renders.BlockRender; + +public abstract class BlockRenderOptimizer { + + public abstract void startRender(ChunkRender chunk); + + public abstract void processBlock(BlockRender block, int x, int y, int z); + + public abstract Shape endRender(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerator.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerator.java new file mode 100644 index 0000000..04cd326 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerator.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders.bro; + +import ru.windcorp.optica.common.util.Namespaced; + +public abstract class BlockRenderOptimizerGenerator extends Namespaced { + + public BlockRenderOptimizerGenerator(String namespace, String name) { + super(namespace, name); + } + + public abstract BlockRenderOptimizer createOptimizer(); + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerators.java b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerators.java new file mode 100644 index 0000000..a72db7f --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/world/renders/bro/BlockRenderOptimizerGenerators.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.client.world.renders.bro; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class BlockRenderOptimizerGenerators { + + private BlockRenderOptimizerGenerators() {} + + private static final Map GENERATORS = + new HashMap<>(); + + static { + register(new BlockRenderOptimizerGenerator("Default", "OpaqueCube") { + @Override + public BlockRenderOptimizer createOptimizer() { + return new BlockRenderOpaqueCubeOptimizer(); + } + }); + } + + public static BlockRenderOptimizerGenerator get(String id) { + return GENERATORS.get(id); + } + + public static void register(BlockRenderOptimizerGenerator generator) { + GENERATORS.put(generator.getId(), generator); + } + + public static Collection getAll() { + return GENERATORS.values(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/block/BlockData.java b/src/main/java/ru/windcorp/optica/common/block/BlockData.java new file mode 100644 index 0000000..84b56a0 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/block/BlockData.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.block; + +import ru.windcorp.optica.common.util.Namespaced; + +public class BlockData extends Namespaced { + + public BlockData(String namespace, String name) { + super(namespace, name); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/block/BlockDataRegistry.java b/src/main/java/ru/windcorp/optica/common/block/BlockDataRegistry.java new file mode 100644 index 0000000..0363fc7 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/block/BlockDataRegistry.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.block; + +import java.util.HashMap; +import java.util.Map; + +public class BlockDataRegistry { + + private static final Map REGISTRY = new HashMap<>(); + + public static BlockData get(String name) { + return REGISTRY.get(name); + } + + public static void register(BlockData blockData) { + REGISTRY.put(blockData.getId(), blockData); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/block/BlockFace.java b/src/main/java/ru/windcorp/optica/common/block/BlockFace.java new file mode 100644 index 0000000..93873f5 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/block/BlockFace.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.block; + +public enum BlockFace { + + TOP, BOTTOM, NORTH, SOUTH, EAST, WEST; + +} diff --git a/src/main/java/ru/windcorp/optica/common/resource/Resource.java b/src/main/java/ru/windcorp/optica/common/resource/Resource.java new file mode 100644 index 0000000..83168d3 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/resource/Resource.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import com.google.common.io.CharStreams; + +import ru.windcorp.optica.Optica; + +public class Resource { + + private final String name; + + public Resource(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public InputStream getInputStream() { + // TODO Do proper resource lookup + return Optica.class.getClassLoader().getResourceAsStream(name); + } + + public Reader getReader() { + return new InputStreamReader(getInputStream()); + } + + public String readAsString() { + try (Reader reader = getReader()) { + return CharStreams.toString(reader); + } catch (IOException e) { + throw new RuntimeException(e); // TODO handle gracefully + } + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/resource/ResourceManager.java b/src/main/java/ru/windcorp/optica/common/resource/ResourceManager.java new file mode 100644 index 0000000..1751549 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/resource/ResourceManager.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.resource; + +public class ResourceManager { + + public static Resource getResource(String name) { + return new Resource(name); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/CoordinatePacker.java b/src/main/java/ru/windcorp/optica/common/util/CoordinatePacker.java new file mode 100644 index 0000000..5d2f297 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/CoordinatePacker.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.util; + +public class CoordinatePacker { + + private static final int BITS_3_INTS_INTO_LONG; + private static final long MASK_3_INTS_INTO_LONG; + + static { + BITS_3_INTS_INTO_LONG = 64 / 3; + + /* + * What happens below: + * + * 1. 1 << BITS_3_INTS_INTO_LONG: + * 0000 ... 00100 ... 0000 + * \_________/ - BITS_3_INTS_INTO_LONG zeros + * + * 2. (1 << BITS_3_INTS_INTO_LONG) - 1: + * 0000 ... 00011 ... 1111 + * \_________/ - BITS_3_INTS_INTO_LONG ones - WIN + */ + + MASK_3_INTS_INTO_LONG = (1 << BITS_3_INTS_INTO_LONG) - 1; + } + + public static long pack3IntsIntoLong(int a, int b, int c) { + return + ((a & MASK_3_INTS_INTO_LONG) << (2 * BITS_3_INTS_INTO_LONG)) | + ((b & MASK_3_INTS_INTO_LONG) << (1 * BITS_3_INTS_INTO_LONG)) | + ((c & MASK_3_INTS_INTO_LONG) << (0 * BITS_3_INTS_INTO_LONG)); + } + + public static int unpack3IntsFromLong(long packed, int index) { + if (index < 0 || index >= 3) { + throw new IllegalArgumentException("Invalid index " + index); + } + + int result = (int) ( + (packed >>> ((2 - index) * BITS_3_INTS_INTO_LONG)) + & MASK_3_INTS_INTO_LONG + ); + + final long signMask = ((MASK_3_INTS_INTO_LONG + 1) >> 1); + + if ((result & signMask) != 0) { + result |= ~MASK_3_INTS_INTO_LONG; + } + + return result; + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/Named.java b/src/main/java/ru/windcorp/optica/common/util/Named.java new file mode 100644 index 0000000..c008838 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/Named.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.util; + +import java.util.Objects; + +public abstract class Named { + + private final String name; + + public Named(String name) { + this.name = Objects.requireNonNull(name, "name"); + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " " + getName(); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Named other = (Named) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/Namespaced.java b/src/main/java/ru/windcorp/optica/common/util/Namespaced.java new file mode 100644 index 0000000..c54a2a6 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/Namespaced.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.util; + +import java.util.Objects; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +public abstract class Namespaced extends Named { + + private static final char SEPARATOR = ':'; + + private static final String PART_REGEX = "^[A-Z][a-zA-Z0-9]{2,}$"; + + private static final Predicate PART_CHECKER = + Pattern.compile(PART_REGEX).asPredicate(); + + private final String namespace; + private final String id; + + public Namespaced(String namespace, String name) { + super(name); + this.namespace = Objects.requireNonNull(namespace, "namespace"); + this.id = namespace + SEPARATOR + name; + + if (!PART_CHECKER.test(name)) { + throw new IllegalArgumentException( + "Name \"" + name + "\" is invalid. " + + "Allowed is: " + PART_REGEX + ); + } + + if (!PART_CHECKER.test(namespace)) { + throw new IllegalArgumentException( + "Namespace \"" + namespace + "\" is invalid. " + + "Allowed is: " + PART_REGEX + ); + } + } + + public String getId() { + return id; + } + + public String getNamespace() { + return namespace; + } + + @Override + public String toString() { + return getId(); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Namespaced other = (Namespaced) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/StashingStack.java b/src/main/java/ru/windcorp/optica/common/util/StashingStack.java new file mode 100644 index 0000000..27acbd6 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/StashingStack.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import com.google.common.collect.Iterables; + +/** + * A low-overhead, fixed-capacity stack that does not dispose of popped elements + * but rather stashes them for later pushing. This allows the stack to + * operate without creating new objects. + *

+ * This object always contains references to {@link #getCapacity()} elements, of + * which first {@link #getSize()} elements are present in the stack proper and + * accessable, and the rest are stashed. When an element is popped, is + * becomes stashed. When an element is pushed, it ceases to be stashed. + *

+ * Stashed elements can be replaced with {@link #push(Object)}. + * + * @author Javapony + */ +@SuppressWarnings("unchecked") +public class StashingStack implements Iterable { + + /** + * Stores all elements. Elements with indices + * [0; {@link #head}] + * are present in the stack, elements with indices + * ({@link #head}; contents.length] are stashed. + */ + private final Object[] contents; + + private transient List contentsAsList; + + /** + * Index of the head of the stack in the {@link #contents} array, or + * -1, if the stack is empty. + */ + private int head = -1; + + protected StashingStack(Object[] stash, int dummy) { + this.contents = stash; + } + + /** + * Creates a new stack. Its stash is filled with {@code null}s. + * @param capacity stack's capacity + */ + public StashingStack(int capacity) { + this((T[]) new Object[capacity], 0); + } + + /** + * Creates a new stack with the supplied stash. + * @param contents elements that are put in the stash initially. + */ + public StashingStack(T[] contents) { + this(contents.clone(), 0); + } + + /** + * Creates a new stack with the supplied stash. + * @param contents elements that are put in the stash initially. + */ + public StashingStack(Iterable contents) { + this(Iterables.toArray(contents, Object.class), 0); + } + + /** + * Creates a new stack. Its stash is filled with objects provided by + * {@code generator}. The generator's {@link Supplier#get() get()} method + * will only be invoked {@code capacity} times from within this constructor. + * @param capacity stack's capacity + * @param generator a supplier of objects for the stash + */ + public StashingStack(int capacity, Supplier generator) { + this(capacity); + + for (int i = 0; i < contents.length; ++i) { + contents[i] = generator.get(); + } + } + /** + * Returns the amount of elements this stack can store. + * @return the capacity + */ + public int getCapacity() { + return contents.length; + } + + /** + * Returns the amount of elements that are currently in the stack. + * @return the size + */ + public int getSize() { + return head + 1; + } + + /** + * Checks whether this stack does not contain any elements. + * @return {@code true} is this stack is empty + */ + public boolean isEmpty() { + return getSize() == 0; + } + + /** + * Checks whether this stack is full. + * @return {@code true} is this stack is full + */ + public boolean isFull() { + return getSize() == getCapacity(); + } + + /** + * Returns, but does not remove, the head of this stack. If the stack is + * empty returns {@code null}. + * @return head of this stack or {@code null} + * @see #getHead() + */ + public T peek() { + if (head < 0) return null; + return (T) contents[head]; + } + + /** + * Returns, but does not remove, the head of this stack. If the stack is + * empty throws a {@link NoSuchElementException}. + * @return head of this stack + * @throws NoSuchElementException is the stack is empty + * @see #peek() + */ + public T getHead() { + if (head < 0) throw new NoSuchElementException(); + return (T) contents[head]; + } + + /** + * Returns and removes the head of this stack. If the stack is + * empty returns {@code null}. + * @return head of this stack or {@code null} + * @see #removeHead() + */ + public T pop() { + if (head < 0) return null; + return (T) contents[head--]; + } + + /** + * Returns and removes the head of this stack. If the stack is + * empty throws a {@link NoSuchElementException}. + * @return head of this stack + * @throws NoSuchElementException is the stack is empty + * @see #pop() + */ + public T removeHead() { + if (head < 0) throw new NoSuchElementException(); + return (T) contents[head--]; + } + + /** + * Pushes a new element from the stash onto the stack. If the stack is + * already full throws an {@link IllegalStateException}. The state of the + * new element is not specified. + * @return the new head + */ + public T push() { + if (head == contents.length - 1) { + throw new IllegalStateException(); + } + + return (T) contents[++head]; + } + + /** + * Pushes the specified element onto the stack. A stashed element is + * removed. If the stack is already full throws an + * {@link IllegalStateException}. + * @param newElement the element to push + * @return the new head + */ + public T push(T newElement) { + if (head == contents.length - 1) { + throw new IllegalStateException(); + } + + contents[++head] = newElement; + return newElement; + } + + /** + * Removes all elements from the stack. + */ + public void removeAll() { + head = -1; + } + + @Override + public Iterator iterator() { + if (contentsAsList == null) { + contentsAsList = Arrays.asList((T[]) contents); + } + + return contentsAsList.subList(0, getSize()).iterator(); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/ThrowingRunnable.java b/src/main/java/ru/windcorp/optica/common/util/ThrowingRunnable.java new file mode 100644 index 0000000..5602d0d --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/ThrowingRunnable.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.util; + +import java.util.function.Consumer; + +import com.google.common.base.Throwables; + +@FunctionalInterface +public interface ThrowingRunnable { + + void run() throws T; + + default Runnable withCatcher( + Consumer catcher, + Class throwableClass + ) { + return () -> { + + try { + ThrowingRunnable.this.run(); + } catch (Throwable t) { + if (t.getClass() == throwableClass) { + catcher.accept(throwableClass.cast(t)); + } + + Throwables.throwIfUnchecked(t); + + // This should never happen + throw new AssertionError("This should not have been thrown", t); + } + + }; + } + + default Runnable withCatcher( + Consumer catcher + ) { + return () -> { + try { + ThrowingRunnable.this.run(); + } catch (Throwable t) { + catcher.accept(t); + } + }; + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/world/ChunkData.java b/src/main/java/ru/windcorp/optica/common/world/ChunkData.java new file mode 100644 index 0000000..ca5f363 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/world/ChunkData.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.world; + +import glm.vec._3.i.Vec3i; +import ru.windcorp.optica.common.block.BlockData; + +public class ChunkData { + + public static final int BLOCKS_PER_CHUNK = 16; + + private final int x; + private final int y; + private final int z; + + private final BlockData[][][] blocks = new BlockData[BLOCKS_PER_CHUNK] + [BLOCKS_PER_CHUNK] + [BLOCKS_PER_CHUNK]; + + private final BlockData grass = new BlockData("Grass", "Test"); + private final BlockData dirt = new BlockData("Dirt", "Test"); + private final BlockData stone = new BlockData("Stone", "Test"); + private final BlockData air = new BlockData("Air", "Test"); +// private final BlockData glass = new BlockData("Glass", "Test"); +// private final BlockData compass = new BlockData("Compass", "Test"); + + public ChunkData(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + + tmp_generate(); + } + + private void tmp_generate() { + Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2); + Vec3i pos = new Vec3i(); + + for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) { + for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) { + for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) { + + pos.set(x, y, z); + + float f = aPoint.sub(pos, pos).length(); + + if (f > 17) { + blocks[x][y][z] = stone; + } else if (f > 14) { + blocks[x][y][z] = dirt; + } else { + blocks[x][y][z] = air; + } + + } + } + } + + for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) { + for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) { + int z; + for (z = BLOCKS_PER_CHUNK - 1; z >= 0 && blocks[x][y][z] == air; --z); + + blocks[x][y][z] = grass; + } + } + } + + public BlockData getBlock(int xInChunk, int yInChunk, int zInChunk) { + if (!isInBounds(xInChunk, yInChunk, zInChunk)) { + throw new IllegalArgumentException( + "Coordinates (" + x + "; " + y + "; " + z + ") " + + "are not legal chunk coordinates" + ); + } + + return blocks[xInChunk][yInChunk][zInChunk]; + } + + private boolean isInBounds(int xInChunk, int yInChunk, int zInChunk) { + return + xInChunk >= 0 && xInChunk < BLOCKS_PER_CHUNK || + yInChunk >= 0 && yInChunk < BLOCKS_PER_CHUNK || + zInChunk >= 0 && zInChunk < BLOCKS_PER_CHUNK; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/world/WorldData.java b/src/main/java/ru/windcorp/optica/common/world/WorldData.java new file mode 100644 index 0000000..3db4455 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/world/WorldData.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Optica + * Copyright (C) 2020 Wind Corporation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *******************************************************************************/ +package ru.windcorp.optica.common.world; + +import java.util.Collection; +import java.util.Collections; + +import gnu.trove.map.TLongObjectMap; +import gnu.trove.map.hash.TLongObjectHashMap; +import ru.windcorp.optica.common.util.CoordinatePacker; + +public class WorldData { + + private final TLongObjectMap chunks = new TLongObjectHashMap<>(); + + public WorldData() { + final int size = 1; + + for (int x = -(size / 2); x <= (size / 2); ++x) { + for (int y = -(size / 2); y <= (size / 2); ++y) { + chunks.put(CoordinatePacker.pack3IntsIntoLong(x, y, 0), new ChunkData(x, y, 0)); + } + } + } + + public ChunkData getChunk(int x, int y, int z) { + long key = CoordinatePacker.pack3IntsIntoLong(x, y, z); + return chunks.get(key); + } + + public Collection getChunks() { + return Collections.unmodifiableCollection(chunks.valueCollection()); + } + +} diff --git a/src/main/resources/assets/shaders/Shape.fragment.glsl b/src/main/resources/assets/shaders/Shape.fragment.glsl new file mode 100644 index 0000000..360358d --- /dev/null +++ b/src/main/resources/assets/shaders/Shape.fragment.glsl @@ -0,0 +1,53 @@ +#version 120 + +varying vec3 varyingColorMultiplier; +varying vec2 varyingTextureCoords; +varying vec3 varyingNormals; + +uniform sampler2D textureSlot; +uniform vec2 textureStart; +uniform vec2 textureSize; + +void applyTexture() { + gl_FragColor = texture2D( + textureSlot, + vec2( + varyingTextureCoords[0] * textureSize[0] + textureStart[0], + varyingTextureCoords[1] * textureSize[1] + textureStart[1] + ) + ); +} + +void multiply(inout vec4 vector, float scalar) { + vector.x *= scalar; + vector.y *= scalar; + vector.z *= scalar; + vector.w *= scalar; +} + +void linearMultiply(inout vec4 vector, vec4 scalars) { + vector.x *= scalars.x; + vector.y *= scalars.y; + vector.z *= scalars.z; + vector.w *= scalars.w; +} + +void applyColorMultiplier() { + linearMultiply(gl_FragColor, vec4(varyingColorMultiplier, 1.0)); +} + +void applyShading() { + vec3 light = normalize(vec3(0.5, 1.0, 0.2)); + vec3 normal = varyingNormals; + + float angleCos = dot(normal, light); + float lightness = (angleCos + 1.5) / 2; + + linearMultiply(gl_FragColor, vec4(lightness.xxx, 1.0)); +} + +void applyAlpha() { + if (gl_FragColor.w < 0.01) { + discard; + } +} \ No newline at end of file diff --git a/src/main/resources/assets/shaders/Shape.vertex.glsl b/src/main/resources/assets/shaders/Shape.vertex.glsl new file mode 100644 index 0000000..3ce95ff --- /dev/null +++ b/src/main/resources/assets/shaders/Shape.vertex.glsl @@ -0,0 +1,27 @@ +#version 120 + +attribute vec3 inputPositions; + +attribute vec3 inputColorMultiplier; +varying vec3 varyingColorMultiplier; + +attribute vec2 inputTextureCoords; +varying vec2 varyingTextureCoords; + +attribute vec3 inputNormals; +varying vec3 varyingNormals; + +uniform mat4 worldTransform; +uniform mat4 finalTransform; + +vec4 applyFinalTransform(vec4 vector) { + return finalTransform * vector; +} + +void transferToFragment() { + varyingColorMultiplier = inputColorMultiplier; + varyingTextureCoords = inputTextureCoords; + + mat3 worldRotation = mat3(worldTransform); + varyingNormals = normalize(worldRotation * inputNormals); +} \ No newline at end of file diff --git a/src/main/resources/assets/shaders/WorldDefault.fragment.glsl b/src/main/resources/assets/shaders/WorldDefault.fragment.glsl new file mode 100644 index 0000000..94be7fd --- /dev/null +++ b/src/main/resources/assets/shaders/WorldDefault.fragment.glsl @@ -0,0 +1,8 @@ +#version 120 + +void main(void) { + applyTexture(); + applyColorMultiplier(); + applyShading(); + applyAlpha(); +} \ No newline at end of file diff --git a/src/main/resources/assets/shaders/WorldDefault.vertex.glsl b/src/main/resources/assets/shaders/WorldDefault.vertex.glsl new file mode 100644 index 0000000..11faad1 --- /dev/null +++ b/src/main/resources/assets/shaders/WorldDefault.vertex.glsl @@ -0,0 +1,6 @@ +#version 120 + +void main(void) { + gl_Position = applyFinalTransform(vec4(inputPositions, 1.0)); + transferToFragment(); +} \ No newline at end of file diff --git a/src/main/resources/assets/textures/compass.png b/src/main/resources/assets/textures/compass.png new file mode 100644 index 0000000000000000000000000000000000000000..642b73a3f295ff3dee2b6188b10f1821e73055e7 GIT binary patch literal 256 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/textures/side_east.png b/src/main/resources/assets/textures/side_east.png new file mode 100644 index 0000000000000000000000000000000000000000..696e93a03f7690eb9723cadf420d8460159cbd5b GIT binary patch literal 588 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 literal 0 HcmV?d00001 diff --git a/src/test/java/ru/windcorp/optica/util/CoordinatePackerTest.java b/src/test/java/ru/windcorp/optica/util/CoordinatePackerTest.java new file mode 100644 index 0000000..0c74ec3 --- /dev/null +++ b/src/test/java/ru/windcorp/optica/util/CoordinatePackerTest.java @@ -0,0 +1,54 @@ +package ru.windcorp.optica.util; + +import static org.junit.Assert.assertEquals; + +import java.util.Random; + +import org.junit.Test; + +import ru.windcorp.optica.common.util.CoordinatePacker; + +public class CoordinatePackerTest { + + @Test + public void cornerCases() { + check(0, 0, 0); + check(0, 0, 42); + check(0, 42, 0); + check(42, 0, 0); + check(1, 1, 1); + check(-1, -1, -1); + check(1 << 19, 1 << 19, 1 << 19); + check((1 << 20) - 1, (1 << 20) - 1, (1 << 20) - 1); + check(-(1 << 19), -(1 << 19), -(1 << 19)); + } + + @Test + public void randomValues() { + Random random = new Random(0); + int bound = 1 << 20; + + for (int i = 0; i < 1000000; ++i) { + check( + random.nextInt(bound) * (random.nextBoolean() ? 1 : -1), + random.nextInt(bound) * (random.nextBoolean() ? 1 : -1), + random.nextInt(bound) * (random.nextBoolean() ? 1 : -1) + ); + } + } + + private void check(int a, int b, int c) { + + long packed = CoordinatePacker.pack3IntsIntoLong(a, b, c); + + int unpackedA = CoordinatePacker.unpack3IntsFromLong(packed, 0); + int unpackedB = CoordinatePacker.unpack3IntsFromLong(packed, 1); + int unpackedC = CoordinatePacker.unpack3IntsFromLong(packed, 2); + + assertEquals(a, unpackedA); + assertEquals(b, unpackedB); + assertEquals(c, unpackedC); + + } + +} diff --git a/src/test/java/ru/windcorp/optica/util/NamespacedTest.java b/src/test/java/ru/windcorp/optica/util/NamespacedTest.java new file mode 100644 index 0000000..10fd1cd --- /dev/null +++ b/src/test/java/ru/windcorp/optica/util/NamespacedTest.java @@ -0,0 +1,153 @@ +package ru.windcorp.optica.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Random; + +import org.junit.Test; + +import junit.framework.AssertionFailedError; +import ru.windcorp.optica.common.util.Namespaced; + +public class NamespacedTest { + + class TestNamespaced extends Namespaced { + + public TestNamespaced(String namespace, String name) { + super(namespace, name); + } + + } + + void shouldReject(String a, String b) { + try { + new TestNamespaced(a, b); + } catch (IllegalArgumentException | NullPointerException e) { + try { + new TestNamespaced(b, a); + } catch (IllegalArgumentException | NullPointerException e1) { + return; + } + } + + throw new AssertionFailedError("Expected NPE or IAE for: \"" + a + "\":\"" + b + "\""); + } + + @Test + public void shouldAllow() { + new TestNamespaced("Something", "Usual"); + new TestNamespaced("Vry", "Sml"); + new TestNamespaced("ALL", "CAPS"); + new TestNamespaced("WithDigits12345", "MoreDigits67890"); + } + + @Test + public void shouldRejectNulls() { + shouldReject(null, "Normal"); + shouldReject(null, null); + } + + @Test + public void shouldRejectInvalid() { + shouldReject("Contains-hyphens", "Normal"); + shouldReject("Contains_underscores", "Normal"); + shouldReject("ALL_CAPS_WITH_UNDERSCORES", "Normal"); + shouldReject("Contains whitespace", "Normal"); + shouldReject("0StartsWithDigit", "Normal"); + shouldReject("lowerCamelCase", "Normal"); + shouldReject("XS", "Normal"); + shouldReject("", "Normal"); + shouldReject("Contains:separators", "Normal"); + shouldReject("СодержитРусский", "Normal"); + } + + @Test + public void shouldRejectGarbage() { + Random random = new Random(0); + + byte[] bytes = new byte[1024]; + for (int attempt = 0; attempt < 10000; ++attempt) { + random.nextBytes(bytes); + bytes[0] = 'a'; // Make sure it is invalid + shouldReject(new String(bytes), "ContainsUtterGarbage"); + } + } + + @Test + public void testHashCodeAndEquals() { + HashSet hashSet = new HashSet<>(); + + Collection contains = new ArrayList<>(); + Collection doesNotContain = new ArrayList<>(); + + Random random = new Random(0); + + for (int i = 0; i < 256; ++i) { + String namespace = getRandomValidString(random); + String name = getRandomValidString(random); + + TestNamespaced a = new TestNamespaced(namespace, name); + TestNamespaced b = new TestNamespaced(namespace, name); + + contains.add(a); + hashSet.add(b); + } + + for (int i = 0; i < 256; ++i) { + String namespace = getRandomValidString(random); + String name = getRandomValidString(random); + + TestNamespaced c = new TestNamespaced(namespace, name); + + doesNotContain.add(c); + } + + for (TestNamespaced x : contains) { + Iterator it = doesNotContain.iterator(); + while (it.hasNext()) { + TestNamespaced next = it.next(); + if (next.getName().equals(x.getName()) && next.getNamespace().equals(x.getNamespace())) { + it.remove(); + } + } + } + + for (TestNamespaced test : contains) { + assertTrue(hashSet.contains(test)); + } + + for (TestNamespaced test : doesNotContain) { + assertFalse(hashSet.contains(test)); + } + } + + String getRandomValidString(Random random) { + char[] chars = new char[random.nextInt(100) + 3]; + + for (int i = 0; i < chars.length; ++i) { + switch (random.nextInt(3)) { + case 0: + if (i != 0) { + chars[i] = (char) ('a' + random.nextInt('z' - 'a')); + break; + } + case 1: + if (i != 0) { + chars[i] = (char) ('0' + random.nextInt('9' - '0')); + break; + } + case 2: + chars[i] = (char) ('A' + random.nextInt('Z' - 'A')); + break; + } + } + + return new String(chars); + } + +} diff --git a/src/test/java/ru/windcorp/optica/util/StashingStackTest.java b/src/test/java/ru/windcorp/optica/util/StashingStackTest.java new file mode 100644 index 0000000..d363b1b --- /dev/null +++ b/src/test/java/ru/windcorp/optica/util/StashingStackTest.java @@ -0,0 +1,83 @@ +package ru.windcorp.optica.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Random; + +import org.junit.Test; + +import com.google.common.collect.Iterators; + +import ru.windcorp.optica.common.util.StashingStack; + +public class StashingStackTest { + + @Test + public void normalOperation() { + final int size = 256; + final int operations = 2 << 16; + final long seed = 0; + + Random random = new Random(seed); + + Deque spares = new LinkedList<>(); + for (int i = 0; i < size; ++i) { + spares.add(Integer.toString(i)); + } + + StashingStack stashing = new StashingStack<>(spares); + Deque reference = new LinkedList<>(); + + for (int i = 0; i < operations; ++i) { + boolean isFull = stashing.isFull(); + boolean isEmpty = stashing.isEmpty(); + + assertTrue("isFull", isFull == (reference.size() == size)); + assertTrue("isEmpty", isEmpty == reference.isEmpty()); + assertEquals("size", reference.size(), stashing.getSize()); + + if (isFull || (!isEmpty && random.nextBoolean())) { + if (random.nextBoolean()) { + String popped = reference.pop(); + assertEquals("pop", popped, stashing.pop()); + spares.push(popped); + } else { + String peeked = reference.peek(); + assertEquals("peek", peeked, stashing.peek()); + } + } else { + reference.push(spares.pop()); + stashing.push(); + } + } + + assertTrue("remaining", Iterators.elementsEqual( + reference.descendingIterator(), + stashing.iterator() + )); + } + + @Test + public void cornerCases() { + StashingStack stack = new StashingStack<>(10); + assertNull(stack.peek()); + assertNull(stack.pop()); + assertNull(stack.push()); + } + + @Test(expected = NoSuchElementException.class) + public void noSuchElementWhenGetHead() { + new StashingStack<>(10).getHead(); + } + + @Test(expected = NoSuchElementException.class) + public void noSuchElementWhenRemoveHead() { + new StashingStack<>(10).removeHead(); + } + +}