diff --git a/.classpath b/.classpath deleted file mode 100644 index 85d2273f..00000000 --- a/.classpath +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..0fd12408 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,49 @@ +name: "CodeQL" + +on: + schedule: + - cron: '33 23 * * 0-6' + +jobs: + analyse: + name: Analyse + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..a1a9cd1f --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,30 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build + - uses: actions/upload-artifact@v2 + with: + name: CraftGame + path: build/libs/CraftGame.jar diff --git a/.gitignore b/.gitignore index c75f3c2c..4e39946d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,22 @@ # Ignore Gradle build output directory build + +bin + +# Ignore working directory and debug output +output +log + +.idea + +.vscode +.VSCodeCounter + +.settings +.classpath +.project + +# Ignore my temporary directory and game working directory +.craftgame +EasingsAnim diff --git a/.project b/.project deleted file mode 100644 index e9063b21..00000000 --- a/.project +++ /dev/null @@ -1,22 +0,0 @@ - - - CraftGame - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - - - org.eclipse.jdt.core.javabuilder - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 408b507c..00000000 --- a/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir= -eclipse.preferences.version=1 diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 1f887b61..00000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index c95baf6d..00000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -# -#Sat Mar 07 16:18:37 CST 2020 -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.source=1.8 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md index 52f7204d..bfa612fd 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,47 @@ -# CraftGame -a minecraft-like game programmed in Java with LWJGL 2. -The game is in beta now. So if you have some suggestion, just leave in the comment. -**** - -#### How to build and run - -* Windows - -To build, just type -> gradlew.bat build - -then -> gradlew.bat run - -There you go! - -* Linux && MacOS - -Pretty easy too! Just type -> ./gradlew build - -and then -> ./gradlew run - -There you go! - -# Have fun! +![Splash](./splash/splash.png) + +This is a fork of LovelyZeeiam/CraftGame, however, (s)he'd deleted it before I detached this fork with its maintenance changed temporarily :-(. And this fork is detached now, but can be attached to LovelyZeeiam's upstream again if (s)he wants(and I'll undo the maintenance change). + +# Minecraft-Classic-Remake + +The goal is to make a "Minecraft PE 0.10.5 Remake" programmed in Java 17 with LWJGL 3. Currently, the game is being refactored and cannot be played. But when the game is done, there will be a skeleton summary of this project. + +## Current Job + +* Implement the terrain generation of Minecraft PE 0.1.0. + +## The goal + +- [ ] All blocks that are in Minecraft PE 0.10.5 +- [ ] Implement a simple world using terrain generation including default world and simple biomes like Minecraft. +- [ ] Implement Survival Mode as default. +- [ ] Day-night Circle. +- [ ] Animals and monsters. + +## How to Test the Game + +First install Java 17, and... + +#### Windows + +> gradlew.bat run + + +#### Linux && MacOS + +I have no computer running MacOS, but I got a dual system, Windows and Ubuntu. +I'm convinced that it is able to run by typing the following 3 commands. + +> chmod +x gradlew +> +> ./gradlew build +> +> ./gradlew run + +#### GraalVM Native Image + +The programmer has tried build native image for the game but it failed. + +# Have fun! + + diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index 827c9503..00000000 --- a/bin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/xueLi/ -/default/ -/main/ diff --git a/build.gradle b/build.gradle index cf392544..55ade669 100644 --- a/build.gradle +++ b/build.gradle @@ -5,37 +5,167 @@ * For more details take a look at the Java Quickstart chapter in the Gradle * User Manual available at https://docs.gradle.org/6.0/userguide/tutorial_java_projects.html */ - plugins { // Apply the java plugin to add support for Java id 'java' // Apply the application plugin to add support for building a CLI application. id 'application' + + // id 'org.lwjgl.plugin' version '0.0.34' + id 'com.google.osdetector' version '1.7.1' + // id 'org.graalvm.buildtools.native' version '0.9.12' + +} + +application { + // Define the main class for the application. + mainClassName = 'xueli.mcremake.client.CraftGameMain' + + // For building Swing application + // mainClassName = 'xueli.clock.ClockFrame' + // applicationDefaultJvmArgs = [ + // '-agentlib:native-image-agent=config-merge-dir=build/agent-info/' + // ] + +} + +compileJava { + options.release = 17 + options.encoding = "UTF-8" } repositories { // Use jcenter for resolving dependencies. // You can declare any Maven/Ivy/file repository here. - jcenter() + + maven { url "https://maven.aliyun.com/repository/central" } + maven { url "https://maven.aliyun.com/repository/jcenter" } + + mavenCentral() + + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "https://oss.sonatype.org/content/repositories/releases/" } + +} + +ext { + currentPlatform = getCurrentPlatform() + lwjgl_version = '3.3.1' + nativeImageDirName = "native-image-$currentPlatform" } dependencies { - compile fileTree(dir:'libs/lwjgl',include:['*.jar']) + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + + implementation 'com.google.code.gson:gson:2.8.6' + + implementation 'org.fusesource.jansi:jansi:2.3.4' + + implementation group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, changing: true + implementation group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, changing: true, classifier: 'natives-windows' + implementation group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, changing: true, classifier: 'natives-linux' + implementation group: 'org.lwjgl', name: 'lwjgl', version: lwjgl_version, changing: true, classifier: 'natives-macos' + + implementation group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, changing: true + implementation group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, changing: true, classifier: 'natives-windows' + implementation group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, changing: true, classifier: 'natives-linux' + implementation group: 'org.lwjgl', name: 'lwjgl-glfw', version: lwjgl_version, changing: true, classifier: 'natives-macos' + + implementation group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, changing: true + implementation group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, changing: true, classifier: 'natives-windows' + implementation group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, changing: true, classifier: 'natives-linux' + implementation group: 'org.lwjgl', name: 'lwjgl-opengl', version: lwjgl_version, changing: true, classifier: 'natives-macos' + + implementation group: 'org.lwjgl', name: 'lwjgl-openal', version: lwjgl_version, changing: true + implementation group: 'org.lwjgl', name: 'lwjgl-openal', version: lwjgl_version, changing: true, classifier: 'natives-windows' + implementation group: 'org.lwjgl', name: 'lwjgl-openal', version: lwjgl_version, changing: true, classifier: 'natives-macos' + implementation group: 'org.lwjgl', name: 'lwjgl-openal', version: lwjgl_version, changing: true, classifier: 'natives-linux' + + implementation group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, changing: true + implementation group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, changing: true, classifier: 'natives-windows' + implementation group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, changing: true, classifier: 'natives-linux' + implementation group: 'org.lwjgl', name: 'lwjgl-nanovg', version: lwjgl_version, changing: true, classifier: 'natives-macos' + + // implementation group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, changing: true + // implementation group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, changing: true, classifier: 'natives-windows' + // implementation group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, changing: true, classifier: 'natives-linux' + // implementation group: 'org.lwjgl', name: 'lwjgl-stb', version: lwjgl_version, changing: true, classifier: 'natives-macos' + + implementation group: 'org.iq80.leveldb', name: 'leveldb', version: '0.12' + implementation group: 'org.iq80.snappy', name: 'snappy', version: '0.4' + implementation 'com.flowpowered:flow-nbt:1.0.0' + + implementation 'io.netty:netty-all:4.1.82.Final' + + implementation 'com.formdev:flatlaf:1.5' + // implementation group: 'com.github.oshi', name: 'oshi-core', version: '6.4.0' + + // implementation 'org.jsoup:jsoup:1.16.1' + +// implementation group: 'net.java.dev.jna', name: 'jna', version: '5.13.0' +// implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.13.0' - // This dependency is used by the application. - implementation 'com.google.guava:guava:28.0-jre' - implementation 'com.google.code.gson:gson:2.6.2' - // Use JUnit test framework - testImplementation 'junit:junit:4.12' } -application { - // Define the main class for the application. - apply plugin: 'application' - mainClassName = 'xueLi.craftGame.Main' - applicationDefaultJvmArgs = [ - '-Djava.library.path="libs/lwjgl/natives"'] +jar { + manifest { + attributes 'Implementation-Title': 'CraftGame', + 'Implementation-Version': "WorldAlpha-${releaseTime()}" + } + +} + +// graalvmNative { +// binaries.all { +// resources.autodetect() +// buildArgs.add('--initialize-at-build-time=org.slf4j.LoggerFactory') + // buildArgs.add('--initialize-at-run-time=org.lwjgl') + // buildArgs.add('--initialize-at-build-time=sun.awt.Toolkit') + // buildArgs.add('--initialize-at-build-time=java.awt.Toolkit') + + // buildArgs.add('--no-fallback') + // buildArgs.add('--allow-incomplete-classpath') + // buildArgs.add('-H:+AddAllCharsets') + // buildArgs.add('-H:+ReportExceptionStackTraces') + // buildArgs.add('-H:ConfigurationFileDirectories=build/agent-info') + + // buildArgs.add('--native-image-info') + // buildArgs.add('--verbose') + +// } +// toolchainDetection = false +// } + +// === Functions and Tasks === +def releaseTime() { + return new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08:00")) } + +def getCurrentPlatform() { + def os = osdetector.os + def lwjglOS = os == 'linux' ? 'linux' : + os == 'osx' ? 'macos' : + os == 'windows' ? 'windows' : + 'unknown' + if (lwjglOS == 'unknown') + throw new GradleException('Unsupported OS: ' + os) + + def arch = osdetector.arch + //consider 64-bit architectures only + def lwjglArch = arch == 'x86_64' ? '' : + arch == 'aarch_64' ? 'arm64' : + 'unknown' + if (lwjglArch == 'unknown') + throw new GradleException('Unsupported Architecture: ' + arch) + + return (lwjglArch == '' ? lwjglOS : (lwjglOS + '-' + lwjglArch)) +} + +test { + useJUnitPlatform() +} + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f2..6b4de624 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Thu Apr 20 18:43:05 CST 2023 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/libs/lwjgl/ibxm.jar b/libs/lwjgl/ibxm.jar deleted file mode 100644 index 619d26e1..00000000 Binary files a/libs/lwjgl/ibxm.jar and /dev/null differ diff --git a/libs/lwjgl/jogg-0.0.7.jar b/libs/lwjgl/jogg-0.0.7.jar deleted file mode 100644 index ecb02603..00000000 Binary files a/libs/lwjgl/jogg-0.0.7.jar and /dev/null differ diff --git a/libs/lwjgl/jorbis-0.0.15.jar b/libs/lwjgl/jorbis-0.0.15.jar deleted file mode 100644 index 4cf51f90..00000000 Binary files a/libs/lwjgl/jorbis-0.0.15.jar and /dev/null differ diff --git a/libs/lwjgl/lwjgl.jar b/libs/lwjgl/lwjgl.jar deleted file mode 100644 index 5da88d33..00000000 Binary files a/libs/lwjgl/lwjgl.jar and /dev/null differ diff --git a/libs/lwjgl/lwjgl_util.jar b/libs/lwjgl/lwjgl_util.jar deleted file mode 100644 index d377c51a..00000000 Binary files a/libs/lwjgl/lwjgl_util.jar and /dev/null differ diff --git a/libs/lwjgl/lwjgl_util_applet.jar b/libs/lwjgl/lwjgl_util_applet.jar deleted file mode 100644 index 99a6ccbe..00000000 Binary files a/libs/lwjgl/lwjgl_util_applet.jar and /dev/null differ diff --git a/libs/lwjgl/natives-linux.jar b/libs/lwjgl/natives-linux.jar deleted file mode 100644 index a543ee02..00000000 Binary files a/libs/lwjgl/natives-linux.jar and /dev/null differ diff --git a/libs/lwjgl/natives-mac.jar b/libs/lwjgl/natives-mac.jar deleted file mode 100644 index 220012ba..00000000 Binary files a/libs/lwjgl/natives-mac.jar and /dev/null differ diff --git a/libs/lwjgl/natives-windows.jar b/libs/lwjgl/natives-windows.jar deleted file mode 100644 index 2fb036fb..00000000 Binary files a/libs/lwjgl/natives-windows.jar and /dev/null differ diff --git a/libs/lwjgl/natives/linux/libjinput-linux.so b/libs/lwjgl/natives/linux/libjinput-linux.so deleted file mode 100644 index 3cdc4397..00000000 Binary files a/libs/lwjgl/natives/linux/libjinput-linux.so and /dev/null differ diff --git a/libs/lwjgl/natives/linux/libjinput-linux64.so b/libs/lwjgl/natives/linux/libjinput-linux64.so deleted file mode 100644 index de1ee5f3..00000000 Binary files a/libs/lwjgl/natives/linux/libjinput-linux64.so and /dev/null differ diff --git a/libs/lwjgl/natives/linux/liblwjgl.so b/libs/lwjgl/natives/linux/liblwjgl.so deleted file mode 100644 index 3fe6b16c..00000000 Binary files a/libs/lwjgl/natives/linux/liblwjgl.so and /dev/null differ diff --git a/libs/lwjgl/natives/linux/liblwjgl64.so b/libs/lwjgl/natives/linux/liblwjgl64.so deleted file mode 100644 index 690f6354..00000000 Binary files a/libs/lwjgl/natives/linux/liblwjgl64.so and /dev/null differ diff --git a/libs/lwjgl/natives/linux/libopenal.so b/libs/lwjgl/natives/linux/libopenal.so deleted file mode 100644 index 0a3a619b..00000000 Binary files a/libs/lwjgl/natives/linux/libopenal.so and /dev/null differ diff --git a/libs/lwjgl/natives/linux/libopenal64.so b/libs/lwjgl/natives/linux/libopenal64.so deleted file mode 100644 index e0693c01..00000000 Binary files a/libs/lwjgl/natives/linux/libopenal64.so and /dev/null differ diff --git a/libs/lwjgl/natives/mac/libjinput-osx.dylib b/libs/lwjgl/natives/mac/libjinput-osx.dylib deleted file mode 100644 index 59a3eab5..00000000 Binary files a/libs/lwjgl/natives/mac/libjinput-osx.dylib and /dev/null differ diff --git a/libs/lwjgl/natives/mac/liblwjgl.dylib b/libs/lwjgl/natives/mac/liblwjgl.dylib deleted file mode 100644 index a6083b95..00000000 Binary files a/libs/lwjgl/natives/mac/liblwjgl.dylib and /dev/null differ diff --git a/libs/lwjgl/natives/mac/openal.dylib b/libs/lwjgl/natives/mac/openal.dylib deleted file mode 100644 index 3c6d0f7f..00000000 Binary files a/libs/lwjgl/natives/mac/openal.dylib and /dev/null differ diff --git a/libs/lwjgl/natives/windows/OpenAL32.dll b/libs/lwjgl/natives/windows/OpenAL32.dll deleted file mode 100644 index 1f69e945..00000000 Binary files a/libs/lwjgl/natives/windows/OpenAL32.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/OpenAL64.dll b/libs/lwjgl/natives/windows/OpenAL64.dll deleted file mode 100644 index 6f2a2fe1..00000000 Binary files a/libs/lwjgl/natives/windows/OpenAL64.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/jinput-dx8.dll b/libs/lwjgl/natives/windows/jinput-dx8.dll deleted file mode 100644 index 6d27ad5e..00000000 Binary files a/libs/lwjgl/natives/windows/jinput-dx8.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/jinput-dx8_64.dll b/libs/lwjgl/natives/windows/jinput-dx8_64.dll deleted file mode 100644 index 67305896..00000000 Binary files a/libs/lwjgl/natives/windows/jinput-dx8_64.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/jinput-raw.dll b/libs/lwjgl/natives/windows/jinput-raw.dll deleted file mode 100644 index ce1d1620..00000000 Binary files a/libs/lwjgl/natives/windows/jinput-raw.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/jinput-raw_64.dll b/libs/lwjgl/natives/windows/jinput-raw_64.dll deleted file mode 100644 index 3d2b3ada..00000000 Binary files a/libs/lwjgl/natives/windows/jinput-raw_64.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/lwjgl.dll b/libs/lwjgl/natives/windows/lwjgl.dll deleted file mode 100644 index 7cca39f8..00000000 Binary files a/libs/lwjgl/natives/windows/lwjgl.dll and /dev/null differ diff --git a/libs/lwjgl/natives/windows/lwjgl64.dll b/libs/lwjgl/natives/windows/lwjgl64.dll deleted file mode 100644 index 99cbd988..00000000 Binary files a/libs/lwjgl/natives/windows/lwjgl64.dll and /dev/null differ diff --git a/libs/lwjgl/slick-util.jar b/libs/lwjgl/slick-util.jar deleted file mode 100644 index 10b552a9..00000000 Binary files a/libs/lwjgl/slick-util.jar and /dev/null differ diff --git a/res/fonts/minecraft-ten.ttf b/res/fonts/minecraft-ten.ttf new file mode 100644 index 00000000..a8110223 Binary files /dev/null and b/res/fonts/minecraft-ten.ttf differ diff --git a/res/gui/CraftGame.png b/res/gui/CraftGame.png new file mode 100644 index 00000000..5fb599da Binary files /dev/null and b/res/gui/CraftGame.png differ diff --git a/res/gui/main_menu/multi_player.png b/res/gui/main_menu/multi_player.png new file mode 100644 index 00000000..60d20c2d Binary files /dev/null and b/res/gui/main_menu/multi_player.png differ diff --git a/res/gui/main_menu/settings.png b/res/gui/main_menu/settings.png new file mode 100644 index 00000000..39a85fb6 Binary files /dev/null and b/res/gui/main_menu/settings.png differ diff --git a/res/gui/main_menu/single_player.png b/res/gui/main_menu/single_player.png new file mode 100644 index 00000000..b2058c89 Binary files /dev/null and b/res/gui/main_menu/single_player.png differ diff --git a/res/gui/options_background.png b/res/gui/options_background.png new file mode 100644 index 00000000..ccd17786 Binary files /dev/null and b/res/gui/options_background.png differ diff --git a/res/gui/player_icon.jpg b/res/gui/player_icon.jpg new file mode 100644 index 00000000..d62d9e8e Binary files /dev/null and b/res/gui/player_icon.jpg differ diff --git a/res/gui/server_select/server_dialog.png b/res/gui/server_select/server_dialog.png new file mode 100644 index 00000000..7cc73c5d Binary files /dev/null and b/res/gui/server_select/server_dialog.png differ diff --git a/res/gui/splash.jpg b/res/gui/splash.jpg new file mode 100644 index 00000000..3e60db7d Binary files /dev/null and b/res/gui/splash.jpg differ diff --git a/res/gui/toast/toast_1.png b/res/gui/toast/toast_1.png new file mode 100644 index 00000000..7faa633e Binary files /dev/null and b/res/gui/toast/toast_1.png differ diff --git a/res/gui/toast/toast_2.png b/res/gui/toast/toast_2.png new file mode 100644 index 00000000..33cd79f4 Binary files /dev/null and b/res/gui/toast/toast_2.png differ diff --git a/res/gui/toast/toast_3.png b/res/gui/toast/toast_3.png new file mode 100644 index 00000000..62ec2b43 Binary files /dev/null and b/res/gui/toast/toast_3.png differ diff --git a/res/gui/toast/toast_4.png b/res/gui/toast/toast_4.png new file mode 100644 index 00000000..53998f45 Binary files /dev/null and b/res/gui/toast/toast_4.png differ diff --git a/res/gui/toast/toast_confusion.png b/res/gui/toast/toast_confusion.png new file mode 100644 index 00000000..ab1a2ba8 Binary files /dev/null and b/res/gui/toast/toast_confusion.png differ diff --git a/res/gui/toast/toast_error.png b/res/gui/toast/toast_error.png new file mode 100644 index 00000000..de28343c Binary files /dev/null and b/res/gui/toast/toast_error.png differ diff --git a/res/gui/toast/toast_exception.png b/res/gui/toast/toast_exception.png new file mode 100644 index 00000000..c397dc2c Binary files /dev/null and b/res/gui/toast/toast_exception.png differ diff --git a/res/gui/toast/toast_fine.png b/res/gui/toast/toast_fine.png new file mode 100644 index 00000000..148ed3db Binary files /dev/null and b/res/gui/toast/toast_fine.png differ diff --git a/res/gui/widgets/chosen_close.png b/res/gui/widgets/chosen_close.png new file mode 100644 index 00000000..31aeadd4 Binary files /dev/null and b/res/gui/widgets/chosen_close.png differ diff --git a/res/gui/widgets/close.png b/res/gui/widgets/close.png new file mode 100644 index 00000000..7e563756 Binary files /dev/null and b/res/gui/widgets/close.png differ diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 00000000..25a097c5 Binary files /dev/null and b/res/icon.png differ diff --git a/res/lang/zh-ch.lang b/res/lang/zh-ch.lang new file mode 100644 index 00000000..187b7e0b --- /dev/null +++ b/res/lang/zh-ch.lang @@ -0,0 +1,43 @@ +# Game Start +game.version=alpha +game.copyright=Licensed under AGPL 3.0 + +# Main Menu +main_menu.single_player=Single Player +main_menu.multi_player=Multiplayer +main_menu.setting=Setting +esc_menu.back_to_game=Back To Game +esc_menu.quit=Save And Quit + +# Select World Menu +world_select_menu.text=Select World +world_select_menu.play=Play +world_select_menu.edit=Edit +world_select_menu.delete=Delete +world_select_menu.cancel=Cancel +world_select_menu.create=Create + +# Select Server Menu +server_select_menu.text=Select Server +server_select_menu.join=Join Server +server_select_menu.direct=Direct Connect +server_select_menu.add=Add Server +server_select_menu.edit=Edit +server_select_menu.delete=Delete +server_select_menu.refresh=Refresh +server_select_menu.cancel=Cancel +server_select_edit_dialog.title=Sorry but currently the view doesn't work! +server_select_edit_dialog.name=Server Name +server_select_edit_dialog.name.hint=Please enter server name +server_select_edit_dialog.addr=Server Address +server_select_edit_dialog.addr.hint=Please enter IP or Address +server_select_edit_dialog.port=Port +server_select_edit_dialog.remove=Remove +server_select_edit_dialog.save=Save + +# World Loading +world_loading.text=Loading Level... + +# World Saving +world_saving.text=Saving Level... + diff --git a/res/logging.properties b/res/logging.properties new file mode 100644 index 00000000..f2c9fd21 --- /dev/null +++ b/res/logging.properties @@ -0,0 +1,65 @@ +############################################################ +# Default Logging Configuration File +# +# You can use a different file by specifying a filename +# with the java.util.logging.config.file system property. +# For example java -Djava.util.logging.config.file=myfile +############################################################ + +############################################################ +# Global properties +############################################################ + +# "handlers" specifies a comma separated list of log Handler +# classes. These handlers will be installed during VM startup. +# Note that these classes must be on the system classpath. +# By default we only configure a ConsoleHandler, which will only +# show messages at the INFO and above levels. +#handlers= java.util.logging.ConsoleHandler + +# To also add the FileHandler, use the following line instead. +handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler + +# Default global logging level. +# This specifies which kinds of events are logged across +# all loggers. For any given facility this global level +# can be overriden by a facility specific level +# Note that the ConsoleHandler also has a separate level +# setting to limit messages printed to the console. +.level= ALL + +############################################################ +# Handler specific properties. +# Describes specific configuration info for Handlers. +############################################################ + +# default file output is in user's home directory. +java.util.logging.FileHandler.pattern = log/java%u.csv +java.util.logging.FileHandler.limit = 999999 +java.util.logging.FileHandler.count = 1 +# Default number of locks FileHandler can obtain synchronously. +# This specifies maximum number of attempts to obtain lock file by FileHandler +# implemented by incrementing the unique field %u as per FileHandler API documentation. +java.util.logging.FileHandler.maxLocks = 100 +java.util.logging.FileHandler.formatter = xueli.utils.formatter.MyFormatter + +# Limit the message that are printed on the console to INFO and above. +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter + +java.util.logging.SimpleFormatter.format=%1$tF %1$tH:%1$tM:%1$tS %2$s%n%4$s: %5$s%6$s%n + +# Example to customize the SimpleFormatter output format +# to print one-line log message like this: +# : [] +# +# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n + +############################################################ +# Facility specific properties. +# Provides extra control for each logger. +############################################################ + +# For example, set the com.xyz.foo logger to only log SEVERE +# messages: +# com.xyz.foo.level = SEVERE diff --git a/res/music/BV1HD4y1x7Gy.nbs b/res/music/BV1HD4y1x7Gy.nbs new file mode 100644 index 00000000..b9773fd4 Binary files /dev/null and b/res/music/BV1HD4y1x7Gy.nbs differ diff --git a/res/music/BowOfMoon.nbs b/res/music/BowOfMoon.nbs new file mode 100644 index 00000000..e94d2c35 Binary files /dev/null and b/res/music/BowOfMoon.nbs differ diff --git a/res/music/Ninelie.nbs b/res/music/Ninelie.nbs new file mode 100644 index 00000000..b5f93663 Binary files /dev/null and b/res/music/Ninelie.nbs differ diff --git a/res/music/One Summer Day.nbs b/res/music/One Summer Day.nbs new file mode 100644 index 00000000..558464a6 Binary files /dev/null and b/res/music/One Summer Day.nbs differ diff --git a/res/music/Together Forever-RickAstley.nbs b/res/music/Together Forever-RickAstley.nbs new file mode 100644 index 00000000..7cc8273a Binary files /dev/null and b/res/music/Together Forever-RickAstley.nbs differ diff --git a/res/music/Together Forever-RickAstley_piano.nbs b/res/music/Together Forever-RickAstley_piano.nbs new file mode 100644 index 00000000..f6b66749 Binary files /dev/null and b/res/music/Together Forever-RickAstley_piano.nbs differ diff --git a/res/music/always.nbs b/res/music/always.nbs new file mode 100644 index 00000000..0fdd5a0a Binary files /dev/null and b/res/music/always.nbs differ diff --git a/res/music/aurora.nbs b/res/music/aurora.nbs new file mode 100644 index 00000000..87053bc0 Binary files /dev/null and b/res/music/aurora.nbs differ diff --git a/res/music/beings.nbs b/res/music/beings.nbs new file mode 100644 index 00000000..04c8fa38 Binary files /dev/null and b/res/music/beings.nbs differ diff --git a/res/music/departures.nbs b/res/music/departures.nbs new file mode 100644 index 00000000..1384d96c Binary files /dev/null and b/res/music/departures.nbs differ diff --git a/res/music/dong_feng_po.nbs b/res/music/dong_feng_po.nbs new file mode 100644 index 00000000..a4199df4 Binary files /dev/null and b/res/music/dong_feng_po.nbs differ diff --git a/res/music/easteregg.nbs b/res/music/easteregg.nbs new file mode 100644 index 00000000..58ccbc0b Binary files /dev/null and b/res/music/easteregg.nbs differ diff --git a/res/music/everything goes on.nbs b/res/music/everything goes on.nbs new file mode 100644 index 00000000..cf128a1e Binary files /dev/null and b/res/music/everything goes on.nbs differ diff --git a/res/music/flower-yuezhengling.nbs b/res/music/flower-yuezhengling.nbs new file mode 100644 index 00000000..5769f6d3 Binary files /dev/null and b/res/music/flower-yuezhengling.nbs differ diff --git a/res/music/lucky_star.nbs b/res/music/lucky_star.nbs new file mode 100644 index 00000000..9155c088 Binary files /dev/null and b/res/music/lucky_star.nbs differ diff --git a/res/music/luhan-oncall.nbs b/res/music/luhan-oncall.nbs new file mode 100644 index 00000000..bcf9749f Binary files /dev/null and b/res/music/luhan-oncall.nbs differ diff --git a/res/music/nali_doushini_duizhang.nbs b/res/music/nali_doushini_duizhang.nbs new file mode 100644 index 00000000..a2e93549 Binary files /dev/null and b/res/music/nali_doushini_duizhang.nbs differ diff --git a/res/music/ni_ting_de_dao.nbs b/res/music/ni_ting_de_dao.nbs new file mode 100644 index 00000000..27777959 Binary files /dev/null and b/res/music/ni_ting_de_dao.nbs differ diff --git a/res/music/ok x lionhearted.nbs b/res/music/ok x lionhearted.nbs new file mode 100644 index 00000000..fcea4cfc Binary files /dev/null and b/res/music/ok x lionhearted.nbs differ diff --git a/res/music/pay no mind x easy.nbs b/res/music/pay no mind x easy.nbs new file mode 100644 index 00000000..346d743b Binary files /dev/null and b/res/music/pay no mind x easy.nbs differ diff --git a/res/music/school lofi (original.nbs b/res/music/school lofi (original.nbs new file mode 100644 index 00000000..c7f761a0 Binary files /dev/null and b/res/music/school lofi (original.nbs differ diff --git a/res/music/something comforing.nbs b/res/music/something comforing.nbs new file mode 100644 index 00000000..144f9197 Binary files /dev/null and b/res/music/something comforing.nbs differ diff --git a/res/music/thief.nbs b/res/music/thief.nbs new file mode 100644 index 00000000..d0a76807 Binary files /dev/null and b/res/music/thief.nbs differ diff --git a/res/music/woheni.nbs b/res/music/woheni.nbs new file mode 100644 index 00000000..6149d576 Binary files /dev/null and b/res/music/woheni.nbs differ diff --git a/res/music/yanhua_yileng.nbs b/res/music/yanhua_yileng.nbs new file mode 100644 index 00000000..72ddc820 Binary files /dev/null and b/res/music/yanhua_yileng.nbs differ diff --git a/res/music/zuiweidadezuopin.nbs b/res/music/zuiweidadezuopin.nbs new file mode 100644 index 00000000..5eb41a24 Binary files /dev/null and b/res/music/zuiweidadezuopin.nbs differ diff --git a/res/shaders/block_view/frag.txt b/res/shaders/block_view/frag.txt new file mode 100644 index 00000000..4d27793a --- /dev/null +++ b/res/shaders/block_view/frag.txt @@ -0,0 +1,20 @@ +#version 330 + +in vec2 otexPos; +in vec3 opos; + +uniform sampler2D texSampler; + +out vec4 out_color; + +float random() { + return clamp((opos.x + opos.y + opos.z) / 6.0, 0.0, 1.0); +} + +void main(){ + vec4 ambient = texture(texSampler, otexPos); + if(ambient.w == 0) discard; + out_color = vec4(ambient.xyz * vec3(mix(0.7,1.0,1 - random())), ambient.w); + + +} \ No newline at end of file diff --git a/res/shaders/block_view/vert.txt b/res/shaders/block_view/vert.txt new file mode 100644 index 00000000..813674b7 --- /dev/null +++ b/res/shaders/block_view/vert.txt @@ -0,0 +1,19 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; +layout (location = 4) in vec3 normal; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec2 otexPos; +out vec3 opos; + +void main() { + gl_Position = projMatrix * viewMatrix * vec4(pos, 1.0); + otexPos = texPos; + opos = pos; + +} + diff --git a/res/shaders/entity/frag.txt b/res/shaders/entity/frag.txt new file mode 100644 index 00000000..482718ba --- /dev/null +++ b/res/shaders/entity/frag.txt @@ -0,0 +1,12 @@ +#version 330 + +in vec2 otexPos; + +uniform sampler2D texSampler; + +out vec4 out_color; + +void main(){ + out_color = texture(texSampler, otexPos); + +} \ No newline at end of file diff --git a/res/shaders/entity/vert.txt b/res/shaders/entity/vert.txt new file mode 100644 index 00000000..75df8cfc --- /dev/null +++ b/res/shaders/entity/vert.txt @@ -0,0 +1,18 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; +uniform mat4 transMatrix; +uniform mat4 boneMatrix; + +out vec2 otexPos; + +void main(){ + gl_Position = projMatrix * viewMatrix * transMatrix * boneMatrix * vec4(pos, 1.0); + otexPos = texPos; + +} + diff --git a/res/shaders/g_buffer/frag.txt b/res/shaders/g_buffer/frag.txt new file mode 100644 index 00000000..2316fe23 --- /dev/null +++ b/res/shaders/g_buffer/frag.txt @@ -0,0 +1,19 @@ +#version 330 + +in vec3 fragPos; +in vec2 otexPos; +in vec3 ocolor; +in vec3 onormal; + +uniform sampler2D texSampler; + +layout (location = 0) out vec3 gPosition; +layout (location = 1) out vec3 gNormal; +layout (location = 2) out vec4 gTex; + +void main(){ + gPosition = fragPos; + gNormal = onormal; + gTex = texture(texSampler, otexPos) * vec4(ocolor, 1.0); + +} \ No newline at end of file diff --git a/res/shaders/g_buffer/vert.txt b/res/shaders/g_buffer/vert.txt new file mode 100644 index 00000000..f7402b5e --- /dev/null +++ b/res/shaders/g_buffer/vert.txt @@ -0,0 +1,36 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; +layout (location = 2) in vec3 color; +layout (location = 3) in float sunLight; +layout (location = 4) in vec3 normal; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec3 fragPos; +out vec2 otexPos; +out vec3 ocolor; +out float osunLight; +out vec3 onormal; + +const float density = 0.05; +const float gradient = 4; +out float visibility; + +void main(){ + vec4 posCam = viewMatrix * vec4(pos ,1.0); + gl_Position = projMatrix * posCam; + otexPos = texPos; + ocolor = color; + osunLight = sunLight; + fragPos = pos; + onormal = normal; + + float distance = length(posCam.xyz); + visibility = exp(-pow(distance * density, gradient)); + visibility = clamp(visibility, 0.0, 1.0); + +} + diff --git a/res/shaders/screen_quad/frag.txt b/res/shaders/screen_quad/frag.txt new file mode 100644 index 00000000..b36eb4be --- /dev/null +++ b/res/shaders/screen_quad/frag.txt @@ -0,0 +1,13 @@ +#version 330 + +in vec2 otexPos; + +uniform sampler2D tex; +uniform sampler2D depth; + +out vec4 out_color; + +void main(){ + out_color = texture(tex, otexPos); + +} \ No newline at end of file diff --git a/res/shaders/screen_quad/vert.txt b/res/shaders/screen_quad/vert.txt new file mode 100644 index 00000000..cb523df2 --- /dev/null +++ b/res/shaders/screen_quad/vert.txt @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) in vec2 pos; +layout (location = 1) in vec2 texPos; + +out vec2 otexPos; + +void main(){ + gl_Position = vec4(pos, 0.0, 1.0); + otexPos = texPos; + +} + diff --git a/res/shaders/screen_quad_ssao/frag.txt b/res/shaders/screen_quad_ssao/frag.txt new file mode 100644 index 00000000..ccb9f0f8 --- /dev/null +++ b/res/shaders/screen_quad_ssao/frag.txt @@ -0,0 +1,51 @@ +#version 330 + +const float shadowSize = 0.01f; +const vec2 noiseScale = vec2(800.0f/4.0f, 600.0f/4.0f); + +in vec2 otexPos; + +uniform sampler2D tex; +uniform sampler2D noise; +uniform sampler2D ssaoColor; +uniform sampler2D normal; +uniform sampler2D frag; +uniform vec3 samplers[64]; +uniform mat4 projection; + +out vec4 out_color; + +float calculateSSAO() { + vec3 normal = texture(normal, otexPos).rgb; + vec3 randomVec = texture(noise, otexPos * noiseScale).xyz; + vec3 fragPos = texture(frag, otexPos).xyz; + + vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 TBN = mat3(tangent, bitangent, normal); + + float occlusion = 0.0; + for(int i = 0; i < 64; ++i) + { + vec3 sample = TBN * samplers[i]; + sample = fragPos + sample * 1; + + vec4 offset = vec4(sample, 1.0); + offset = projection * offset; + offset.xyz /= offset.w; + offset.xyz = offset.xyz * 0.5 + 0.5; + + float sampleDepth = -texture(ssaoColor, offset.xy).w; + occlusion += (sampleDepth >= sample.z ? 1.0 : 0.0); + + } + + return 1.0 - (occlusion / 64); +} + +void main() { + float occlusion = calculateSSAO(); + out_color = vec4(occlusion,occlusion,occlusion,1.0); + + +} \ No newline at end of file diff --git a/res/shaders/screen_quad_ssao/vert.txt b/res/shaders/screen_quad_ssao/vert.txt new file mode 100644 index 00000000..cb523df2 --- /dev/null +++ b/res/shaders/screen_quad_ssao/vert.txt @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) in vec2 pos; +layout (location = 1) in vec2 texPos; + +out vec2 otexPos; + +void main(){ + gl_Position = vec4(pos, 0.0, 1.0); + otexPos = texPos; + +} + diff --git a/res/shaders/test_shadow_mapping/frag.txt b/res/shaders/test_shadow_mapping/frag.txt new file mode 100644 index 00000000..33275529 --- /dev/null +++ b/res/shaders/test_shadow_mapping/frag.txt @@ -0,0 +1,46 @@ +#version 330 + +in vec3 ocolor; + +in vec3 FragPos; +in vec4 FragPosLightSpace; +uniform sampler2D shadowMap; +uniform vec3 lightPos; + +out vec4 out_color; + +float ShadowCalculation(vec4 fragPosLightSpace) { + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + float closestDepth = texture(shadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; + + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(shadowMap, 0); + for(int x = -1; x <= 1; ++x) + { + for(int y = -1; y <= 1; ++y) + { + float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth > pcfDepth ? 1.0 : 0.0; + } + } + shadow /= 9.0; + + if(projCoords.z > 1.0) + shadow = 0.0; + + return shadow; + +} + +void main(){ + float shadow = ShadowCalculation(FragPosLightSpace); + shadow = 1 - clamp(shadow, 0, 0.6); + + vec4 color = shadow * vec4(ocolor, 1.0); + if(color.w == 0) discard; + out_color = color; + + +} \ No newline at end of file diff --git a/res/shaders/test_shadow_mapping/vert.txt b/res/shaders/test_shadow_mapping/vert.txt new file mode 100644 index 00000000..0a4de616 --- /dev/null +++ b/res/shaders/test_shadow_mapping/vert.txt @@ -0,0 +1,25 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 2) in vec3 color; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec3 ocolor; + +uniform mat4 lightSpaceMatrix; +out vec4 FragPosLightSpace; + +out vec3 FragPos; + +void main(){ + vec4 posCam = viewMatrix * vec4(pos ,1.0); + gl_Position = projMatrix * posCam; + ocolor = color; + FragPos = pos; + + FragPosLightSpace = lightSpaceMatrix * vec4(pos, 1.0); + +} + diff --git a/res/shaders/test_shadow_mapping_depth/frag.txt b/res/shaders/test_shadow_mapping_depth/frag.txt new file mode 100644 index 00000000..2a584a91 --- /dev/null +++ b/res/shaders/test_shadow_mapping_depth/frag.txt @@ -0,0 +1,10 @@ +#version 330 + +in vec3 ocolor; + +out vec4 outColor; + +void main(){ + outColor = vec4(ocolor, 1.0); + +} \ No newline at end of file diff --git a/res/shaders/test_shadow_mapping_depth/vert.txt b/res/shaders/test_shadow_mapping_depth/vert.txt new file mode 100644 index 00000000..66cf55b0 --- /dev/null +++ b/res/shaders/test_shadow_mapping_depth/vert.txt @@ -0,0 +1,18 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 2) in vec3 color; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec3 ocolor; + +void main(){ + vec4 posCam = viewMatrix * vec4(pos ,1.0); + gl_Position = projMatrix * posCam; + + ocolor = color; + +} + diff --git a/res/shaders/test_shadow_mapping_depth_show/frag.txt b/res/shaders/test_shadow_mapping_depth_show/frag.txt new file mode 100644 index 00000000..882fed6a --- /dev/null +++ b/res/shaders/test_shadow_mapping_depth_show/frag.txt @@ -0,0 +1,14 @@ +#version 330 + +uniform sampler2D depthMap; +uniform sampler2D viewTexture; + +in vec2 otexpos; + +out vec4 outcolor; + +void main(){ + float depthValue = texture(depthMap, otexpos).r; + outcolor = texture(depthMap, otexpos) * vec4(vec3(1 - depthValue * 0.4), 1.0); + +} \ No newline at end of file diff --git a/res/shaders/test_shadow_mapping_depth_show/vert.txt b/res/shaders/test_shadow_mapping_depth_show/vert.txt new file mode 100644 index 00000000..cf684424 --- /dev/null +++ b/res/shaders/test_shadow_mapping_depth_show/vert.txt @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; + +out vec2 otexpos; + +void main(){ + gl_Position = vec4(pos, 1.0); + otexpos = texPos; + +} + diff --git a/res/shaders/world/world.frag b/res/shaders/world/world.frag new file mode 100644 index 00000000..4faa86e1 --- /dev/null +++ b/res/shaders/world/world.frag @@ -0,0 +1,22 @@ +#version 330 + +in float visibility; + +in vec3 fragPos; +in vec2 otexPos; +in vec4 ocolor; + +uniform vec3 skyColor; +uniform vec3 sunDirection; +uniform sampler2D texSampler; + +out vec4 out_color; + +void main(){ + vec4 ambient = texture(texSampler, otexPos); + if(ambient.w == 0) discard; + + out_color = mix(vec4(skyColor, 1.0), ambient * ocolor, visibility); + + +} diff --git a/res/shaders/world/world.vert b/res/shaders/world/world.vert new file mode 100644 index 00000000..826061bf --- /dev/null +++ b/res/shaders/world/world.vert @@ -0,0 +1,30 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; +layout (location = 2) in vec4 color; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec3 fragPos; +out vec2 otexPos; +out vec4 ocolor; + +const float density = 0.004; +const float gradient = 6.0; +out float visibility; + +void main(){ + vec4 posCam = viewMatrix * vec4(pos ,1.0); + gl_Position = projMatrix * posCam; + otexPos = texPos; + fragPos = pos; + ocolor = color; + + float distance = length(posCam.xyz); + visibility = exp(-pow(distance * density, gradient)); + visibility = clamp(visibility, 0.0, 1.0); + +} + diff --git a/res/shaders/world_ssao/frag.txt b/res/shaders/world_ssao/frag.txt new file mode 100644 index 00000000..4a1352f1 --- /dev/null +++ b/res/shaders/world_ssao/frag.txt @@ -0,0 +1,40 @@ +#version 330 + +const vec3 sunColor = vec3(1.0, 1.0, 1.0); + +in vec3 fragPos; +in vec2 otexPos; +in vec3 ocolor; +in float osunLight; +in float visibility; +in vec3 onormal; + +uniform vec3 skyColor; +uniform vec3 sunDirection; +uniform sampler2D texSampler; + +layout (location = 0) out vec4 out_color; +layout (location = 1) out vec3 out_gNormal; +layout (location = 2) out vec3 out_gFragPos; + +float diff() { + vec3 norm = normalize(onormal); + vec3 lightDir = normalize(sunDirection); + + return -max(dot(norm, lightDir), 0.0); +} + +void main(){ + vec3 sunLightColor = sunColor * osunLight; + vec3 real_sun_color = mix(ocolor, sunLightColor, 1.0); + + vec4 ambient = texture(texSampler, otexPos); + if(ambient.w == 0) discard; + + vec3 color = ambient.xyz * vec3(mix(0.8,2.0,diff())); + + out_color = mix(vec4(skyColor, 1.0), vec4(color, 1.0), visibility); + out_gNormal = onormal; + out_gFragPos = fragPos; + +} \ No newline at end of file diff --git a/res/shaders/world_ssao/vert.txt b/res/shaders/world_ssao/vert.txt new file mode 100644 index 00000000..cfc268f5 --- /dev/null +++ b/res/shaders/world_ssao/vert.txt @@ -0,0 +1,36 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texPos; +layout (location = 2) in vec3 color; +layout (location = 3) in float sunLight; +layout (location = 4) in vec3 normal; + +uniform mat4 projMatrix; +uniform mat4 viewMatrix; + +out vec3 fragPos; +out vec2 otexPos; +out vec3 ocolor; +out float osunLight; +out vec3 onormal; + +const float density = 0.004; +const float gradient = 6.0; +out float visibility; + +void main(){ + vec4 posCam = viewMatrix * vec4(pos ,1.0); + gl_Position = projMatrix * posCam; + otexPos = texPos; + ocolor = color; + osunLight = sunLight; + fragPos = pos; + onormal = normal; + + float distance = length(posCam.xyz); + visibility = exp(-pow(distance * density, gradient)); + visibility = clamp(visibility, 0.0, 1.0); + +} + diff --git a/res/text/splash.txt b/res/text/splash.txt new file mode 100644 index 00000000..372a1fac --- /dev/null +++ b/res/text/splash.txt @@ -0,0 +1,15 @@ +īģŋ嘤嘤嘤 +æ­ŖåŊ“夊才一čˆŦįš„æˆ‘æ‰“åŧ€äē†å°åą +æˆ‘åžˆå¯įˆą 蝎į왿ˆ‘é’ą +草īŧˆä¸­æ—Ĩ同蝭īŧ‰ +æĨč‡Ē四æŦĄå…ƒįš„æĨč‡Ē四æŦĄå…ƒįš„é›ĒčŽ‰å‚ä¸ŽåˆļäŊœįš„æĨč‡Ē四æŦĄå…ƒįš„æ¸¸æˆ +J A V A H E R O B R I N E +å“ŧ哈哈 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 +Nobody knows CraftGame better than me! +Xueli, the fake fake programmer~ +awa +įŠŋį€åĨŗčŖ…写äģŖį  +Ah, fine. I guess u r my little pogchamp. Come here~ +Never gonna give u up, never gonna let u down +čŋ™ä¸ĒåĨŊč€ļ~ ãƒŊ(âœŋīžŸâ–ŊīžŸ)ノ +awa是大åŽļįš„ \ No newline at end of file diff --git a/res/text/xuanxue1.txt b/res/text/xuanxue1.txt new file mode 100644 index 00000000..70a836f7 --- /dev/null +++ b/res/text/xuanxue1.txt @@ -0,0 +1,22 @@ +Q: ä¸–į•Œä¸Šäŧšä¸äŧšæœ‰ä¸€į§åˇĨå…ˇæ˜¯æœ€įģå¸¸čĸĢäŊŋį”¨įš„å‘ĸ?åĻ‚æžœæœ‰,ä¸ēäģ€äšˆäŧščĸĢæœ€įģå¸¸äŊŋᔍ. +A: åŧĻæž„成夸克和į”ĩ子ᭉåŸēæœŦធ子 +夏克ᭉåŸēæœŦį˛’å­æž„æˆč´¨å­å’Œä¸­å­ +贍子䏭子į”ĩ子构成原子 +原子构成分子 +åˆ†å­æž„æˆå„į§į‰Šč´¨ +å„į§į‰Šč´¨æž„æˆåˇĨå…ˇ +åŧĻæž„成所有东čĨŋīŧŒåŒ…æ‹ŦåˇĨå…ˇ +äŊ å¯čƒŊäŧšé—Ž +æœ‰įš„åˇĨå…ˇæ˛Ąæœ‰åŽžäŊ“åŊĸåŧ +äŊ†æ˜¯čŋ™äē›åˇĨå…ˇįš„å­˜åœ¨éœ€čĻäžé™„äēŽå…ļäģ–åˇĨå…ˇ +比åĻ‚ä¸‡æœ‰åŧ•力厚型 +æ˛Ąæœ‰į‰Šč´¨īŧŒčŋ™įŽŠæ„å„ŋäšŸæ˛Ąæœ‰äģģäŊ•意䚉 +å†įœ‹įœ‹æ•°å­Ļå…Ŧåŧ +比åĻ‚ +e^(i*pi)+1=0 +我äģŦå‘įŽ°åŽƒäšŸæ˜¯åŸēäēŽį‰Šč´¨ +所äģĨæ˛Ąæœ‰į‰Šč´¨å°ąå‘įŽ°ä¸äē†åރ +å‘įŽ°ä¸äē†įš„äē‹į‰Šæ˜¯æ˛Ąæœ‰æ„äš‰įš„ +比åς光é”Ĩ䚋外有äģ€äšˆčŋ™äē›ä¸œčĨŋ +éƒŊæ˜¯æ— æ„äš‰įš„ +åˇŽä¸å¤šå°ąæ˜¯čŋ™äē› \ No newline at end of file diff --git a/res/text/xuanxue2.txt b/res/text/xuanxue2.txt new file mode 100644 index 00000000..033c53e4 --- /dev/null +++ b/res/text/xuanxue2.txt @@ -0,0 +1,9 @@ +įœ‹čŋ™ä¸ĒåŋĢ速排åēįš„äģŖį īŧŒ +äģ–可äģĨå€ŸåŠŠį­›æŗ•æ‰žåˆ°į´ æ•°īŧŒ +我äģŦå‘įŽ°į´ æ•°éĩåžĒį€ä¸€ä¸Ēč§„åž‹īŧŒ +不éĩåžĒčŋ™ä¸Ēč§„åž‹įš„æ•°å°ąä¸æ˜¯į´ æ•°īŧŒ +ä¸æ˜¯į´ æ•°įš„æ•°äšŸæ˜¯æœ‰æ„äš‰įš„īŧŒ +比åς蝴4īŧŒ +č€Œä¸æ˜¯į´ æ•°įš„æ•°å¯äģĨåˆ†č§Ŗč´¨å› æ•°īŧŒ +垗到tan(x+y)=(tanx+tany)/(1-tanxtany)īŧŒ +我äģŦ再把čŋ™į섿•°æŽäģŖå…ĨīŧŒå¯äģĨ垗到äģ–æ˜¯tree diff --git a/res/textures.png b/res/textures.png deleted file mode 100644 index d36112f4..00000000 Binary files a/res/textures.png and /dev/null differ diff --git a/res/textures/hud/cross.png b/res/textures/hud/cross.png new file mode 100644 index 00000000..89369bd6 Binary files /dev/null and b/res/textures/hud/cross.png differ diff --git a/res/textures/hud/inventory.png b/res/textures/hud/inventory.png new file mode 100644 index 00000000..b56a8950 Binary files /dev/null and b/res/textures/hud/inventory.png differ diff --git a/res/textures/hud/inventory_chosen.png b/res/textures/hud/inventory_chosen.png new file mode 100644 index 00000000..b088e7f3 Binary files /dev/null and b/res/textures/hud/inventory_chosen.png differ diff --git a/save.dat b/save.dat deleted file mode 100644 index adedd395..00000000 Binary files a/save.dat and /dev/null differ diff --git a/settings.gradle b/settings.gradle index 21e54987..04e5c6f4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,5 @@ * in the user manual at https://docs.gradle.org/6.0/userguide/multi_project_builds.html */ -rootProject.name = 'CraftGame' +rootProject.name = 'RealCraftGame' + diff --git a/splash/splash.pdn b/splash/splash.pdn new file mode 100644 index 00000000..dce5bfef Binary files /dev/null and b/splash/splash.pdn differ diff --git a/splash/splash.png b/splash/splash.png new file mode 100644 index 00000000..5a008e29 Binary files /dev/null and b/splash/splash.png differ diff --git a/src/main/java/ch/project/inter/SimplexNoise.java b/src/main/java/ch/project/inter/SimplexNoise.java new file mode 100644 index 00000000..cf701972 --- /dev/null +++ b/src/main/java/ch/project/inter/SimplexNoise.java @@ -0,0 +1,457 @@ +package ch.project.inter; + +/** + * Simplex Noise in 2D, 3D and 4D + */ +public class SimplexNoise { + + private static int grad3[][] = { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, + { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 } }; + + private static int grad4[][] = { { 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 }, + { 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 }, { 1, 0, 1, 1 }, { 1, 0, 1, -1 }, + { 1, 0, -1, 1 }, { 1, 0, -1, -1 }, { -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 }, + { 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 }, { -1, 1, 0, 1 }, { -1, 1, 0, -1 }, + { -1, -1, 0, 1 }, { -1, -1, 0, -1 }, { 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 }, + { -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 } }; + + private static int p[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, + 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, + 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, + 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, + 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, + 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, + 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, + 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, + 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, + 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180 }; + + // To remove the need for index wrapping, double the permutation table length + private static int perm[] = new int[512]; + + static { + for (int i = 0; i < 512; i++) + perm[i] = p[i & 255]; + } + + // A lookup table to traverse the simplex around a given point in 4D. + // Details can be found where this table is used, in the 4D noise method. + private static int simplex[][] = { { 0, 1, 2, 3 }, { 0, 1, 3, 2 }, { 0, 0, 0, 0 }, { 0, 2, 3, 1 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 2, 3, 0 }, { 0, 2, 1, 3 }, { 0, 0, 0, 0 }, { 0, 3, 1, 2 }, + { 0, 3, 2, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 3, 2, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 1, 2, 0, 3 }, { 0, 0, 0, 0 }, { 1, 3, 0, 2 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 2, 3, 0, 1 }, { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 1, 0, 3, 2 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 2, 0, 3, 1 }, { 0, 0, 0, 0 }, { 2, 1, 3, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 2, 0, 1, 3 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 3, 0, 1, 2 }, + { 3, 0, 2, 1 }, { 0, 0, 0, 0 }, { 3, 1, 2, 0 }, { 2, 1, 0, 3 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 3, 1, 0, 2 }, { 0, 0, 0, 0 }, { 3, 2, 0, 1 }, { 3, 2, 1, 0 } }; + + // This method is a *lot* faster than using (int)Math.floor(x) + private static int fastfloor(double x) { + return x > 0 ? (int) x : (int) x - 1; + } + + private static double dot(int g[], double x, double y) { + return g[0] * x + g[1] * y; + } + + private static double dot(int g[], double x, double y, double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + private static double dot(int g[], double x, double y, double z, double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + /** + * Simplex Noise. + */ + public static double noise(double xin, double yin, double frequencyX, double frequencyY, double persistance, + int octaves) { + double resultNoise = 0.0; + double amplitude = 1.0; + double maxValue = 0.0; + for (int i = 0; i < octaves; i++) { + resultNoise += noise(xin * frequencyX, yin * frequencyY) * amplitude; + frequencyX *= 2; + frequencyY *= 2; + maxValue += amplitude; + amplitude *= persistance; + } + return resultNoise / maxValue; + } + + /** + * 2D Simplex Noise. + */ + public static double noise(double xin, double yin) { + double n0, n1, n2; + // Noise contributions from the three corners + // Skew the input space to determine which simplex cell we're in + final double F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + double s = (xin + yin) * F2; + // Hairy factor for 2D + int i = fastfloor(xin + s); + int j = fastfloor(yin + s); + final double G2 = (3.0 - Math.sqrt(3.0)) / 6.0; + double t = (i + j) * G2; + double X0 = i - t; + // Unskew the cell origin back to (x,y) space + double Y0 = j - t; + double x0 = xin - X0; + // The x,y distances from the cell origin + double y0 = yin - Y0; + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; + // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { + i1 = 1; + j1 = 0; + } + // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } + // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + double x1 = x0 - i1 + G2; + // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 - 1.0 + 2.0 * G2; + // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 - 1.0 + 2.0 * G2; + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + int gi0 = perm[ii + perm[jj]] % 12; + int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) + n0 = 0.0; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); + // (x,y) of grad3 used for 2D gradient + } + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) + n1 = 0.0; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) + n2 = 0.0; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + /** + * 3D Simplex Noise. + */ + public static double noise(double xin, double yin, double zin) { + double n0, n1, n2, n3; + // Noise contributions from the four corners + // Skew the input space to determine which simplex cell we're in + final double F3 = 1.0 / 3.0; + double s = (xin + yin + zin) * F3; + // Very nice and simple skew factor for 3D + int i = fastfloor(xin + s); + int j = fastfloor(yin + s); + int k = fastfloor(zin + s); + final double G3 = 1.0 / 6.0; + // Very nice and simple unskew factor, too + double t = (i + j + k) * G3; + double X0 = i - t; + // Unskew the cell origin back to (x,y,z) space + double Y0 = j - t; + double Z0 = k - t; + double x0 = xin - X0; + // The x,y,z distances from the cell origin + double y0 = yin - Y0; + double z0 = zin - Z0; + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; + // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; + // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } + // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } + // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } + // Z X Y order + } else { + // x0 y0) ? 32 : 0; + int c2 = (x0 > z0) ? 16 : 0; + int c3 = (y0 > z0) ? 8 : 0; + int c4 = (x0 > w0) ? 4 : 0; + int c5 = (y0 > w0) ? 2 : 0; + int c6 = (z0 > w0) ? 1 : 0; + int c = c1 + c2 + c3 + c4 + c5 + c6; + int i1, j1, k1, l1; + // The integer offsets for the second simplex corner + int i2, j2, k2, l2; + // The integer offsets for the third simplex corner + int i3, j3, k3, l3; + // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = simplex[c][1] >= 3 ? 1 : 0; + k1 = simplex[c][2] >= 3 ? 1 : 0; + l1 = simplex[c][3] >= 3 ? 1 : 0; + // The number 2 in the "simplex" array is at the second largest coordinate. + i2 = simplex[c][0] >= 2 ? 1 : 0; + j2 = simplex[c][1] >= 2 ? 1 : 0; + k2 = simplex[c][2] >= 2 ? 1 : 0; + l2 = simplex[c][3] >= 2 ? 1 : 0; + // The number 1 in the "simplex" array is at the second smallest coordinate. + i3 = simplex[c][0] >= 1 ? 1 : 0; + j3 = simplex[c][1] >= 1 ? 1 : 0; + k3 = simplex[c][2] >= 1 ? 1 : 0; + l3 = simplex[c][3] >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + double x1 = x0 - i1 + G4; + // Offsets for second corner in (x,y,z,w) coords + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + double x2 = x0 - i2 + 2.0 * G4; + // Offsets for third corner in (x,y,z,w) coords + double y2 = y0 - j2 + 2.0 * G4; + double z2 = z0 - k2 + 2.0 * G4; + double w2 = w0 - l2 + 2.0 * G4; + double x3 = x0 - i3 + 3.0 * G4; + // Offsets for fourth corner in (x,y,z,w) coords + double y3 = y0 - j3 + 3.0 * G4; + double z3 = z0 - k3 + 3.0 * G4; + double w3 = w0 - l3 + 3.0 * G4; + double x4 = x0 - 1.0 + 4.0 * G4; + // Offsets for last corner in (x,y,z,w) coords + double y4 = y0 - 1.0 + 4.0 * G4; + double z4 = z0 - 1.0 + 4.0 * G4; + double w4 = w0 - 1.0 + 4.0 * G4; + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 255; + int jj = j & 255; + int kk = k & 255; + int ll = l & 255; + int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0) + n0 = 0.0; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0) + n1 = 0.0; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0) + n2 = 0.0; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0) + n3 = 0.0; + else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0) + n4 = 0.0; + else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/javaherobrine/GameUtils.java b/src/main/java/io/github/javaherobrine/GameUtils.java new file mode 100644 index 00000000..d0cdba65 --- /dev/null +++ b/src/main/java/io/github/javaherobrine/GameUtils.java @@ -0,0 +1,15 @@ +package io.github.javaherobrine; +import org.lwjgl.system.NativeType; +/** + * Code from Java_Herobrine, but only memory-related functions are provided + */ +public final class GameUtils { + static { + System.load("/home/javaherobrine/libJNI.so"); + } + public static native long address(byte[] b); + /** + * Tell JVM that the memory can be freed + */ + public static native void allowGC(@NativeType("void*") long addr,byte[] b); +} diff --git a/src/main/java/org/lwjgl/utils/vector/Matrix.java b/src/main/java/org/lwjgl/utils/vector/Matrix.java new file mode 100644 index 00000000..d3ffa375 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Matrix.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.io.Serializable; +import java.nio.FloatBuffer; + +/** + * Base class for matrices. When a matrix is constructed it will be the identity + * matrix unless otherwise stated. + * + * @author cix_foo + * @version $Revision$ $Id$ + */ +public abstract class Matrix implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Constructor for Matrix. + */ + protected Matrix() { + super(); + } + + /** + * Set this matrix to be the identity matrix. + * + * @return this + */ + public abstract Matrix setIdentity(); + + /** + * Invert this matrix + * + * @return this + */ + public abstract Matrix invert(); + + /** + * Load from a float buffer. The buffer stores the matrix in column major + * (OpenGL) order. + * + * @param buf A float buffer to read from + * @return this + */ + public abstract Matrix load(FloatBuffer buf); + + /** + * Load from a float buffer. The buffer stores the matrix in row major + * (mathematical) order. + * + * @param buf A float buffer to read from + * @return this + */ + public abstract Matrix loadTranspose(FloatBuffer buf); + + /** + * Negate this matrix + * + * @return this + */ + public abstract Matrix negate(); + + /** + * Store this matrix in a float buffer. The matrix is stored in column major + * (openGL) order. + * + * @param buf The buffer to store this matrix in + * @return this + */ + public abstract Matrix store(FloatBuffer buf); + + /** + * Store this matrix in a float buffer. The matrix is stored in row major + * (maths) order. + * + * @param buf The buffer to store this matrix in + * @return this + */ + public abstract Matrix storeTranspose(FloatBuffer buf); + + /** + * Transpose this matrix + * + * @return this + */ + public abstract Matrix transpose(); + + /** + * Set this matrix to 0. + * + * @return this + */ + public abstract Matrix setZero(); + + /** + * @return the determinant of the matrix + */ + public abstract float determinant(); + +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Matrix2f.java b/src/main/java/org/lwjgl/utils/vector/Matrix2f.java new file mode 100644 index 00000000..71ff27fa --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Matrix2f.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +/** + * Holds a 2x2 matrix + * + * @author cix_foo + * @version $Revision$ $Id$ + */ + +public class Matrix2f extends Matrix { + + private static final long serialVersionUID = 1L; + + public float m00, m01, m10, m11; + + /** + * Constructor for Matrix2f. The matrix is initialised to the identity. + */ + public Matrix2f() { + setIdentity(); + } + + /** + * Constructor + */ + public Matrix2f(Matrix2f src) { + load(src); + } + + /** + * Copy the source matrix to the destination matrix. + * + * @param src The source matrix + * @param dest The destination matrix, or null if a new one should be created. + * @return The copied matrix + */ + public static Matrix2f load(Matrix2f src, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + dest.m00 = src.m00; + dest.m01 = src.m01; + dest.m10 = src.m10; + dest.m11 = src.m11; + + return dest; + } + + /** + * Add two matrices together and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix2f add(Matrix2f left, Matrix2f right, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + dest.m00 = left.m00 + right.m00; + dest.m01 = left.m01 + right.m01; + dest.m10 = left.m10 + right.m10; + dest.m11 = left.m11 + right.m11; + + return dest; + } + + /** + * Subtract the right matrix from the left and place the result in a third + * matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix2f sub(Matrix2f left, Matrix2f right, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + dest.m00 = left.m00 - right.m00; + dest.m01 = left.m01 - right.m01; + dest.m10 = left.m10 - right.m10; + dest.m11 = left.m11 - right.m11; + + return dest; + } + + /** + * Multiply the right matrix by the left and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix2f mul(Matrix2f left, Matrix2f right, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + float m00 = left.m00 * right.m00 + left.m10 * right.m01; + float m01 = left.m01 * right.m00 + left.m11 * right.m01; + float m10 = left.m00 * right.m10 + left.m10 * right.m11; + float m11 = left.m01 * right.m10 + left.m11 * right.m11; + + dest.m00 = m00; + dest.m01 = m01; + dest.m10 = m10; + dest.m11 = m11; + + return dest; + } + + /** + * Transform a Vector by a matrix and return the result in a destination vector. + * + * @param left The left matrix + * @param right The right vector + * @param dest The destination vector, or null if a new one is to be created + * @return the destination vector + */ + public static Vector2f transform(Matrix2f left, Vector2f right, Vector2f dest) { + if (dest == null) + dest = new Vector2f(); + + float x = left.m00 * right.x + left.m10 * right.y; + float y = left.m01 * right.x + left.m11 * right.y; + + dest.x = x; + dest.y = y; + + return dest; + } + + /** + * Transpose the source matrix and place the result in the destination matrix. + * + * @param src The source matrix or null if a new matrix is to be created + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public static Matrix2f transpose(Matrix2f src, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + float m01 = src.m10; + float m10 = src.m01; + + dest.m01 = m01; + dest.m10 = m10; + + return dest; + } + + /** + * Invert the source matrix and place the result in the destination matrix. + * + * @param src The source matrix to be inverted + * @param dest The destination matrix or null if a new matrix is to be created + * @return The inverted matrix, or null if source can't be reverted. + */ + public static Matrix2f invert(Matrix2f src, Matrix2f dest) { + /* + * inv(A) = 1/det(A) * adj(A); + */ + + float determinant = src.determinant(); + if (determinant != 0) { + if (dest == null) + dest = new Matrix2f(); + float determinant_inv = 1f / determinant; + float t00 = src.m11 * determinant_inv; + float t01 = -src.m01 * determinant_inv; + float t11 = src.m00 * determinant_inv; + float t10 = -src.m10 * determinant_inv; + + dest.m00 = t00; + dest.m01 = t01; + dest.m10 = t10; + dest.m11 = t11; + return dest; + } else + return null; + } + + /** + * Negate the source matrix and stash the result in the destination matrix. + * + * @param src The source matrix to be negated + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public static Matrix2f negate(Matrix2f src, Matrix2f dest) { + if (dest == null) + dest = new Matrix2f(); + + dest.m00 = -src.m00; + dest.m01 = -src.m01; + dest.m10 = -src.m10; + dest.m11 = -src.m11; + + return dest; + } + + /** + * Set the source matrix to be the identity matrix. + * + * @param src The matrix to set to the identity. + * @return The source matrix + */ + public static Matrix2f setIdentity(Matrix2f src) { + src.m00 = 1.0f; + src.m01 = 0.0f; + src.m10 = 0.0f; + src.m11 = 1.0f; + return src; + } + + public static Matrix2f setZero(Matrix2f src) { + src.m00 = 0.0f; + src.m01 = 0.0f; + src.m10 = 0.0f; + src.m11 = 0.0f; + return src; + } + + /** + * Load from another matrix + * + * @param src The source matrix + * @return this + */ + public Matrix2f load(Matrix2f src) { + return load(src, this); + } + + /** + * Load from a float buffer. The buffer stores the matrix in column major + * (OpenGL) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix load(FloatBuffer buf) { + + m00 = buf.get(); + m01 = buf.get(); + m10 = buf.get(); + m11 = buf.get(); + + return this; + } + + /** + * Load from a float buffer. The buffer stores the matrix in row major + * (mathematical) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix loadTranspose(FloatBuffer buf) { + + m00 = buf.get(); + m10 = buf.get(); + m01 = buf.get(); + m11 = buf.get(); + + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in column major + * (openGL) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix store(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m10); + buf.put(m11); + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in row major + * (maths) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix storeTranspose(FloatBuffer buf) { + buf.put(m00); + buf.put(m10); + buf.put(m01); + buf.put(m11); + return this; + } + + /** + * Transpose this matrix + * + * @return this + */ + public Matrix transpose() { + return transpose(this); + } + + /** + * Transpose this matrix and place the result in another matrix. + * + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public Matrix2f transpose(Matrix2f dest) { + return transpose(this, dest); + } + + /** + * Invert this matrix + * + * @return this if successful, null otherwise + */ + public Matrix invert() { + return invert(this, this); + } + + /** + * Returns a string representation of this matrix + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(m00).append(' ').append(m10).append(' ').append('\n'); + buf.append(m01).append(' ').append(m11).append(' ').append('\n'); + return buf.toString(); + } + + /** + * Negate this matrix + * + * @return this + */ + public Matrix negate() { + return negate(this); + } + + /** + * Negate this matrix and stash the result in another matrix. + * + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public Matrix2f negate(Matrix2f dest) { + return negate(this, dest); + } + + /** + * Set this matrix to be the identity matrix. + * + * @return this + */ + public Matrix setIdentity() { + return setIdentity(this); + } + + /** + * Set this matrix to 0. + * + * @return this + */ + public Matrix setZero() { + return setZero(this); + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Matrix#determinant() + */ + public float determinant() { + return m00 * m11 - m01 * m10; + } +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Matrix3f.java b/src/main/java/org/lwjgl/utils/vector/Matrix3f.java new file mode 100644 index 00000000..14105f35 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Matrix3f.java @@ -0,0 +1,481 @@ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +public class Matrix3f extends Matrix { + + private static final long serialVersionUID = 1L; + + public float m00, + m01, + m02, + m10, + m11, + m12, + m20, + m21, + m22; + + /** + * Constructor for Matrix3f. Matrix is initialised to the identity. + */ + public Matrix3f() { + super(); + setIdentity(); + } + + /** + * Load from another matrix + * @param src The source matrix + * @return this + */ + public Matrix3f load(Matrix3f src) { + return load(src, this); + } + + /** + * Copy source matrix to destination matrix + * @param src The source matrix + * @param dest The destination matrix, or null of a new matrix is to be created + * @return The copied matrix + */ + public static Matrix3f load(Matrix3f src, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + dest.m00 = src.m00; + dest.m10 = src.m10; + dest.m20 = src.m20; + dest.m01 = src.m01; + dest.m11 = src.m11; + dest.m21 = src.m21; + dest.m02 = src.m02; + dest.m12 = src.m12; + dest.m22 = src.m22; + + return dest; + } + + /** + * Load from a float buffer. The buffer stores the matrix in column major + * (OpenGL) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix load(FloatBuffer buf) { + + m00 = buf.get(); + m01 = buf.get(); + m02 = buf.get(); + m10 = buf.get(); + m11 = buf.get(); + m12 = buf.get(); + m20 = buf.get(); + m21 = buf.get(); + m22 = buf.get(); + + return this; + } + + /** + * Load from a float buffer. The buffer stores the matrix in row major + * (maths) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix loadTranspose(FloatBuffer buf) { + + m00 = buf.get(); + m10 = buf.get(); + m20 = buf.get(); + m01 = buf.get(); + m11 = buf.get(); + m21 = buf.get(); + m02 = buf.get(); + m12 = buf.get(); + m22 = buf.get(); + + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in column + * major (openGL) order. + * @param buf The buffer to store this matrix in + */ + public Matrix store(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m02); + buf.put(m10); + buf.put(m11); + buf.put(m12); + buf.put(m20); + buf.put(m21); + buf.put(m22); + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in row + * major (maths) order. + * @param buf The buffer to store this matrix in + */ + public Matrix storeTranspose(FloatBuffer buf) { + buf.put(m00); + buf.put(m10); + buf.put(m20); + buf.put(m01); + buf.put(m11); + buf.put(m21); + buf.put(m02); + buf.put(m12); + buf.put(m22); + return this; + } + + /** + * Add two matrices together and place the result in a third matrix. + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix3f add(Matrix3f left, Matrix3f right, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + dest.m00 = left.m00 + right.m00; + dest.m01 = left.m01 + right.m01; + dest.m02 = left.m02 + right.m02; + dest.m10 = left.m10 + right.m10; + dest.m11 = left.m11 + right.m11; + dest.m12 = left.m12 + right.m12; + dest.m20 = left.m20 + right.m20; + dest.m21 = left.m21 + right.m21; + dest.m22 = left.m22 + right.m22; + + return dest; + } + + /** + * Subtract the right matrix from the left and place the result in a third matrix. + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix3f sub(Matrix3f left, Matrix3f right, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + dest.m00 = left.m00 - right.m00; + dest.m01 = left.m01 - right.m01; + dest.m02 = left.m02 - right.m02; + dest.m10 = left.m10 - right.m10; + dest.m11 = left.m11 - right.m11; + dest.m12 = left.m12 - right.m12; + dest.m20 = left.m20 - right.m20; + dest.m21 = left.m21 - right.m21; + dest.m22 = left.m22 - right.m22; + + return dest; + } + + /** + * Multiply the right matrix by the left and place the result in a third matrix. + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix3f mul(Matrix3f left, Matrix3f right, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + float m00 = + left.m00 * right.m00 + left.m10 * right.m01 + left.m20 * right.m02; + float m01 = + left.m01 * right.m00 + left.m11 * right.m01 + left.m21 * right.m02; + float m02 = + left.m02 * right.m00 + left.m12 * right.m01 + left.m22 * right.m02; + float m10 = + left.m00 * right.m10 + left.m10 * right.m11 + left.m20 * right.m12; + float m11 = + left.m01 * right.m10 + left.m11 * right.m11 + left.m21 * right.m12; + float m12 = + left.m02 * right.m10 + left.m12 * right.m11 + left.m22 * right.m12; + float m20 = + left.m00 * right.m20 + left.m10 * right.m21 + left.m20 * right.m22; + float m21 = + left.m01 * right.m20 + left.m11 * right.m21 + left.m21 * right.m22; + float m22 = + left.m02 * right.m20 + left.m12 * right.m21 + left.m22 * right.m22; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + + return dest; + } + + /** + * Transform a Vector by a matrix and return the result in a destination + * vector. + * @param left The left matrix + * @param right The right vector + * @param dest The destination vector, or null if a new one is to be created + * @return the destination vector + */ + public static Vector3f transform(Matrix3f left, Vector3f right, Vector3f dest) { + if (dest == null) + dest = new Vector3f(); + + float x = left.m00 * right.x + left.m10 * right.y + left.m20 * right.z; + float y = left.m01 * right.x + left.m11 * right.y + left.m21 * right.z; + float z = left.m02 * right.x + left.m12 * right.y + left.m22 * right.z; + + dest.x = x; + dest.y = y; + dest.z = z; + + return dest; + } + + /** + * Transpose this matrix + * @return this + */ + public Matrix transpose() { + return transpose(this, this); + } + + /** + * Transpose this matrix and place the result in another matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public Matrix3f transpose(Matrix3f dest) { + return transpose(this, dest); + } + + /** + * Transpose the source matrix and place the result into the destination matrix + * @param src The source matrix to be transposed + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public static Matrix3f transpose(Matrix3f src, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + float m00 = src.m00; + float m01 = src.m10; + float m02 = src.m20; + float m10 = src.m01; + float m11 = src.m11; + float m12 = src.m21; + float m20 = src.m02; + float m21 = src.m12; + float m22 = src.m22; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + return dest; + } + + /** + * @return the determinant of the matrix + */ + public float determinant() { + float f = + m00 * (m11 * m22 - m12 * m21) + + m01 * (m12 * m20 - m10 * m22) + + m02 * (m10 * m21 - m11 * m20); + return f; + } + + /** + * Returns a string representation of this matrix + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(m00).append(' ').append(m10).append(' ').append(m20).append(' ').append('\n'); + buf.append(m01).append(' ').append(m11).append(' ').append(m21).append(' ').append('\n'); + buf.append(m02).append(' ').append(m12).append(' ').append(m22).append(' ').append('\n'); + return buf.toString(); + } + + /** + * Invert this matrix + * @return this if successful, null otherwise + */ + public Matrix invert() { + return invert(this, this); + } + + /** + * Invert the source matrix and put the result into the destination matrix + * @param src The source matrix to be inverted + * @param dest The destination matrix, or null if a new one is to be created + * @return The inverted matrix if successful, null otherwise + */ + public static Matrix3f invert(Matrix3f src, Matrix3f dest) { + float determinant = src.determinant(); + + if (determinant != 0) { + if (dest == null) + dest = new Matrix3f(); + /* do it the ordinary way + * + * inv(A) = 1/det(A) * adj(T), where adj(T) = transpose(Conjugate Matrix) + * + * m00 m01 m02 + * m10 m11 m12 + * m20 m21 m22 + */ + float determinant_inv = 1f/determinant; + + // get the conjugate matrix + float t00 = src.m11 * src.m22 - src.m12* src.m21; + float t01 = - src.m10 * src.m22 + src.m12 * src.m20; + float t02 = src.m10 * src.m21 - src.m11 * src.m20; + float t10 = - src.m01 * src.m22 + src.m02 * src.m21; + float t11 = src.m00 * src.m22 - src.m02 * src.m20; + float t12 = - src.m00 * src.m21 + src.m01 * src.m20; + float t20 = src.m01 * src.m12 - src.m02 * src.m11; + float t21 = -src.m00 * src.m12 + src.m02 * src.m10; + float t22 = src.m00 * src.m11 - src.m01 * src.m10; + + dest.m00 = t00*determinant_inv; + dest.m11 = t11*determinant_inv; + dest.m22 = t22*determinant_inv; + dest.m01 = t10*determinant_inv; + dest.m10 = t01*determinant_inv; + dest.m20 = t02*determinant_inv; + dest.m02 = t20*determinant_inv; + dest.m12 = t21*determinant_inv; + dest.m21 = t12*determinant_inv; + return dest; + } else + return null; + } + + + /** + * Negate this matrix + * @return this + */ + public Matrix negate() { + return negate(this); + } + + /** + * Negate this matrix and place the result in a destination matrix. + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public Matrix3f negate(Matrix3f dest) { + return negate(this, dest); + } + + /** + * Negate the source matrix and place the result in the destination matrix. + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public static Matrix3f negate(Matrix3f src, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + dest.m00 = -src.m00; + dest.m01 = -src.m02; + dest.m02 = -src.m01; + dest.m10 = -src.m10; + dest.m11 = -src.m12; + dest.m12 = -src.m11; + dest.m20 = -src.m20; + dest.m21 = -src.m22; + dest.m22 = -src.m21; + return dest; + } + + /** + * Set this matrix to be the identity matrix. + * @return this + */ + public Matrix setIdentity() { + return setIdentity(this); + } + + /** + * Set the matrix to be the identity matrix. + * @param m The matrix to be set to the identity + * @return m + */ + public static Matrix3f setIdentity(Matrix3f m) { + m.m00 = 1.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m10 = 0.0f; + m.m11 = 1.0f; + m.m12 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 1.0f; + return m; + } + + /** + * Set this matrix to 0. + * @return this + */ + public Matrix setZero() { + return setZero(this); + } + + /** + * Set the matrix matrix to 0. + * @param m The matrix to be set to 0 + * @return m + */ + public static Matrix3f setZero(Matrix3f m) { + m.m00 = 0.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m10 = 0.0f; + m.m11 = 0.0f; + m.m12 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 0.0f; + return m; + } + + public static Matrix3f translate(Vector2f vec, Matrix3f src, Matrix3f dest) { + if (dest == null) + dest = new Matrix3f(); + + dest.m20 += src.m00 * vec.x + src.m10 * vec.y; + dest.m21 += src.m01 * vec.x + src.m11 * vec.y; + dest.m22 += src.m02 * vec.x + src.m12 * vec.y; + + return dest; + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Matrix4f.java b/src/main/java/org/lwjgl/utils/vector/Matrix4f.java new file mode 100644 index 00000000..16d576ec --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Matrix4f.java @@ -0,0 +1,871 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +/** + * Holds a 4x4 float matrix. + * + * @author foo + */ +public class Matrix4f extends Matrix { + private static final long serialVersionUID = 1L; + + public float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; + + /** + * Construct a new matrix, initialized to the identity. + */ + public Matrix4f() { + super(); + setIdentity(); + } + + public Matrix4f(final Matrix4f src) { + super(); + load(src); + } + + /** + * Set the given matrix to be the identity matrix. + * + * @param m The matrix to set to the identity + * @return m + */ + public static Matrix4f setIdentity(Matrix4f m) { + m.m00 = 1.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m03 = 0.0f; + m.m10 = 0.0f; + m.m11 = 1.0f; + m.m12 = 0.0f; + m.m13 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 1.0f; + m.m23 = 0.0f; + m.m30 = 0.0f; + m.m31 = 0.0f; + m.m32 = 0.0f; + m.m33 = 1.0f; + + return m; + } + + /** + * Set the given matrix to 0. + * + * @param m The matrix to set to 0 + * @return m + */ + public static Matrix4f setZero(Matrix4f m) { + m.m00 = 0.0f; + m.m01 = 0.0f; + m.m02 = 0.0f; + m.m03 = 0.0f; + m.m10 = 0.0f; + m.m11 = 0.0f; + m.m12 = 0.0f; + m.m13 = 0.0f; + m.m20 = 0.0f; + m.m21 = 0.0f; + m.m22 = 0.0f; + m.m23 = 0.0f; + m.m30 = 0.0f; + m.m31 = 0.0f; + m.m32 = 0.0f; + m.m33 = 0.0f; + + return m; + } + + /** + * Copy the source matrix to the destination matrix + * + * @param src The source matrix + * @param dest The destination matrix, or null of a new one is to be created + * @return The copied matrix + */ + public static Matrix4f load(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + dest.m00 = src.m00; + dest.m01 = src.m01; + dest.m02 = src.m02; + dest.m03 = src.m03; + dest.m10 = src.m10; + dest.m11 = src.m11; + dest.m12 = src.m12; + dest.m13 = src.m13; + dest.m20 = src.m20; + dest.m21 = src.m21; + dest.m22 = src.m22; + dest.m23 = src.m23; + dest.m30 = src.m30; + dest.m31 = src.m31; + dest.m32 = src.m32; + dest.m33 = src.m33; + + return dest; + } + + /** + * Add two matrices together and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f add(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = left.m00 + right.m00; + dest.m01 = left.m01 + right.m01; + dest.m02 = left.m02 + right.m02; + dest.m03 = left.m03 + right.m03; + dest.m10 = left.m10 + right.m10; + dest.m11 = left.m11 + right.m11; + dest.m12 = left.m12 + right.m12; + dest.m13 = left.m13 + right.m13; + dest.m20 = left.m20 + right.m20; + dest.m21 = left.m21 + right.m21; + dest.m22 = left.m22 + right.m22; + dest.m23 = left.m23 + right.m23; + dest.m30 = left.m30 + right.m30; + dest.m31 = left.m31 + right.m31; + dest.m32 = left.m32 + right.m32; + dest.m33 = left.m33 + right.m33; + + return dest; + } + + /** + * Subtract the right matrix from the left and place the result in a third + * matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f sub(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = left.m00 - right.m00; + dest.m01 = left.m01 - right.m01; + dest.m02 = left.m02 - right.m02; + dest.m03 = left.m03 - right.m03; + dest.m10 = left.m10 - right.m10; + dest.m11 = left.m11 - right.m11; + dest.m12 = left.m12 - right.m12; + dest.m13 = left.m13 - right.m13; + dest.m20 = left.m20 - right.m20; + dest.m21 = left.m21 - right.m21; + dest.m22 = left.m22 - right.m22; + dest.m23 = left.m23 - right.m23; + dest.m30 = left.m30 - right.m30; + dest.m31 = left.m31 - right.m31; + dest.m32 = left.m32 - right.m32; + dest.m33 = left.m33 - right.m33; + + return dest; + } + + /** + * Multiply the right matrix by the left and place the result in a third matrix. + * + * @param left The left source matrix + * @param right The right source matrix + * @param dest The destination matrix, or null if a new one is to be created + * @return the destination matrix + */ + public static Matrix4f mul(Matrix4f left, Matrix4f right, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + float m00 = left.m00 * right.m00 + left.m10 * right.m01 + left.m20 * right.m02 + left.m30 * right.m03; + float m01 = left.m01 * right.m00 + left.m11 * right.m01 + left.m21 * right.m02 + left.m31 * right.m03; + float m02 = left.m02 * right.m00 + left.m12 * right.m01 + left.m22 * right.m02 + left.m32 * right.m03; + float m03 = left.m03 * right.m00 + left.m13 * right.m01 + left.m23 * right.m02 + left.m33 * right.m03; + float m10 = left.m00 * right.m10 + left.m10 * right.m11 + left.m20 * right.m12 + left.m30 * right.m13; + float m11 = left.m01 * right.m10 + left.m11 * right.m11 + left.m21 * right.m12 + left.m31 * right.m13; + float m12 = left.m02 * right.m10 + left.m12 * right.m11 + left.m22 * right.m12 + left.m32 * right.m13; + float m13 = left.m03 * right.m10 + left.m13 * right.m11 + left.m23 * right.m12 + left.m33 * right.m13; + float m20 = left.m00 * right.m20 + left.m10 * right.m21 + left.m20 * right.m22 + left.m30 * right.m23; + float m21 = left.m01 * right.m20 + left.m11 * right.m21 + left.m21 * right.m22 + left.m31 * right.m23; + float m22 = left.m02 * right.m20 + left.m12 * right.m21 + left.m22 * right.m22 + left.m32 * right.m23; + float m23 = left.m03 * right.m20 + left.m13 * right.m21 + left.m23 * right.m22 + left.m33 * right.m23; + float m30 = left.m00 * right.m30 + left.m10 * right.m31 + left.m20 * right.m32 + left.m30 * right.m33; + float m31 = left.m01 * right.m30 + left.m11 * right.m31 + left.m21 * right.m32 + left.m31 * right.m33; + float m32 = left.m02 * right.m30 + left.m12 * right.m31 + left.m22 * right.m32 + left.m32 * right.m33; + float m33 = left.m03 * right.m30 + left.m13 * right.m31 + left.m23 * right.m32 + left.m33 * right.m33; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m03 = m03; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m13 = m13; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + dest.m23 = m23; + dest.m30 = m30; + dest.m31 = m31; + dest.m32 = m32; + dest.m33 = m33; + + return dest; + } + + /** + * Transform a Vector by a matrix and return the result in a destination vector. + * + * @param left The left matrix + * @param right The right vector + * @param dest The destination vector, or null if a new one is to be created + * @return the destination vector + */ + public static Vector4f transform(Matrix4f left, Vector4f right, Vector4f dest) { + if (dest == null) + dest = new Vector4f(); + + float x = left.m00 * right.x + left.m10 * right.y + left.m20 * right.z + left.m30 * right.w; + float y = left.m01 * right.x + left.m11 * right.y + left.m21 * right.z + left.m31 * right.w; + float z = left.m02 * right.x + left.m12 * right.y + left.m22 * right.z + left.m32 * right.w; + float w = left.m03 * right.x + left.m13 * right.y + left.m23 * right.z + left.m33 * right.w; + + dest.x = x; + dest.y = y; + dest.z = z; + dest.w = w; + + return dest; + } + + /** + * Scales the source matrix and put the result in the destination matrix + * + * @param vec The vector to scale by + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The scaled matrix + */ + public static Matrix4f scale(Vector3f vec, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + dest.m00 = src.m00 * vec.x; + dest.m01 = src.m01 * vec.x; + dest.m02 = src.m02 * vec.x; + dest.m03 = src.m03 * vec.x; + dest.m10 = src.m10 * vec.y; + dest.m11 = src.m11 * vec.y; + dest.m12 = src.m12 * vec.y; + dest.m13 = src.m13 * vec.y; + dest.m20 = src.m20 * vec.z; + dest.m21 = src.m21 * vec.z; + dest.m22 = src.m22 * vec.z; + dest.m23 = src.m23 * vec.z; + return dest; + } + + /** + * Rotates the source matrix around the given axis the specified angle and put + * the result in the destination matrix. + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @param src The matrix to rotate + * @param dest The matrix to put the result, or null if a new matrix is to be + * created + * @return The rotated matrix + */ + public static Matrix4f rotate(float angle, Vector3f axis, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + float c = (float) Math.cos(angle); + float s = (float) Math.sin(angle); + float oneminusc = 1.0f - c; + float xy = axis.x * axis.y; + float yz = axis.y * axis.z; + float xz = axis.x * axis.z; + float xs = axis.x * s; + float ys = axis.y * s; + float zs = axis.z * s; + + float f00 = axis.x * axis.x * oneminusc + c; + float f01 = xy * oneminusc + zs; + float f02 = xz * oneminusc - ys; + // n[3] not used + float f10 = xy * oneminusc - zs; + float f11 = axis.y * axis.y * oneminusc + c; + float f12 = yz * oneminusc + xs; + // n[7] not used + float f20 = xz * oneminusc + ys; + float f21 = yz * oneminusc - xs; + float f22 = axis.z * axis.z * oneminusc + c; + + float t00 = src.m00 * f00 + src.m10 * f01 + src.m20 * f02; + float t01 = src.m01 * f00 + src.m11 * f01 + src.m21 * f02; + float t02 = src.m02 * f00 + src.m12 * f01 + src.m22 * f02; + float t03 = src.m03 * f00 + src.m13 * f01 + src.m23 * f02; + float t10 = src.m00 * f10 + src.m10 * f11 + src.m20 * f12; + float t11 = src.m01 * f10 + src.m11 * f11 + src.m21 * f12; + float t12 = src.m02 * f10 + src.m12 * f11 + src.m22 * f12; + float t13 = src.m03 * f10 + src.m13 * f11 + src.m23 * f12; + dest.m20 = src.m00 * f20 + src.m10 * f21 + src.m20 * f22; + dest.m21 = src.m01 * f20 + src.m11 * f21 + src.m21 * f22; + dest.m22 = src.m02 * f20 + src.m12 * f21 + src.m22 * f22; + dest.m23 = src.m03 * f20 + src.m13 * f21 + src.m23 * f22; + dest.m00 = t00; + dest.m01 = t01; + dest.m02 = t02; + dest.m03 = t03; + dest.m10 = t10; + dest.m11 = t11; + dest.m12 = t12; + dest.m13 = t13; + return dest; + } + + /** + * Translate the source matrix and stash the result in the destination matrix + * + * @param vec The vector to translate by + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return The translated matrix + */ + public static Matrix4f translate(Vector3f vec, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m30 += src.m00 * vec.x + src.m10 * vec.y + src.m20 * vec.z; + dest.m31 += src.m01 * vec.x + src.m11 * vec.y + src.m21 * vec.z; + dest.m32 += src.m02 * vec.x + src.m12 * vec.y + src.m22 * vec.z; + dest.m33 += src.m03 * vec.x + src.m13 * vec.y + src.m23 * vec.z; + + return dest; + } + + /** + * Translate the source matrix and stash the result in the destination matrix + * + * @param vec The vector to translate by + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return The translated matrix + */ + public static Matrix4f translate(Vector2f vec, Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m30 += src.m00 * vec.x + src.m10 * vec.y; + dest.m31 += src.m01 * vec.x + src.m11 * vec.y; + dest.m32 += src.m02 * vec.x + src.m12 * vec.y; + dest.m33 += src.m03 * vec.x + src.m13 * vec.y; + + return dest; + } + + /** + * Transpose the source matrix and place the result in the destination matrix + * + * @param src The source matrix + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public static Matrix4f transpose(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + float m00 = src.m00; + float m01 = src.m10; + float m02 = src.m20; + float m03 = src.m30; + float m10 = src.m01; + float m11 = src.m11; + float m12 = src.m21; + float m13 = src.m31; + float m20 = src.m02; + float m21 = src.m12; + float m22 = src.m22; + float m23 = src.m32; + float m30 = src.m03; + float m31 = src.m13; + float m32 = src.m23; + float m33 = src.m33; + + dest.m00 = m00; + dest.m01 = m01; + dest.m02 = m02; + dest.m03 = m03; + dest.m10 = m10; + dest.m11 = m11; + dest.m12 = m12; + dest.m13 = m13; + dest.m20 = m20; + dest.m21 = m21; + dest.m22 = m22; + dest.m23 = m23; + dest.m30 = m30; + dest.m31 = m31; + dest.m32 = m32; + dest.m33 = m33; + + return dest; + } + + /** + * Calculate the determinant of a 3x3 matrix + * + * @return result + */ + + private static float determinant3x3(float t00, float t01, float t02, float t10, float t11, float t12, float t20, + float t21, float t22) { + return t00 * (t11 * t22 - t12 * t21) + t01 * (t12 * t20 - t10 * t22) + t02 * (t10 * t21 - t11 * t20); + } + + /** + * Invert the source matrix and put the result in the destination + * + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The inverted matrix if successful, null otherwise + */ + public static Matrix4f invert(Matrix4f src, Matrix4f dest) { + float determinant = src.determinant(); + + if (determinant != 0) { + /* + * m00 m01 m02 m03 m10 m11 m12 m13 m20 m21 m22 m23 m30 m31 m32 m33 + */ + if (dest == null) + dest = new Matrix4f(); + float determinant_inv = 1f / determinant; + + // first row + float t00 = determinant3x3(src.m11, src.m12, src.m13, src.m21, src.m22, src.m23, src.m31, src.m32, src.m33); + float t01 = -determinant3x3(src.m10, src.m12, src.m13, src.m20, src.m22, src.m23, src.m30, src.m32, + src.m33); + float t02 = determinant3x3(src.m10, src.m11, src.m13, src.m20, src.m21, src.m23, src.m30, src.m31, src.m33); + float t03 = -determinant3x3(src.m10, src.m11, src.m12, src.m20, src.m21, src.m22, src.m30, src.m31, + src.m32); + // second row + float t10 = -determinant3x3(src.m01, src.m02, src.m03, src.m21, src.m22, src.m23, src.m31, src.m32, + src.m33); + float t11 = determinant3x3(src.m00, src.m02, src.m03, src.m20, src.m22, src.m23, src.m30, src.m32, src.m33); + float t12 = -determinant3x3(src.m00, src.m01, src.m03, src.m20, src.m21, src.m23, src.m30, src.m31, + src.m33); + float t13 = determinant3x3(src.m00, src.m01, src.m02, src.m20, src.m21, src.m22, src.m30, src.m31, src.m32); + // third row + float t20 = determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m31, src.m32, src.m33); + float t21 = -determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m30, src.m32, + src.m33); + float t22 = determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m30, src.m31, src.m33); + float t23 = -determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m30, src.m31, + src.m32); + // fourth row + float t30 = -determinant3x3(src.m01, src.m02, src.m03, src.m11, src.m12, src.m13, src.m21, src.m22, + src.m23); + float t31 = determinant3x3(src.m00, src.m02, src.m03, src.m10, src.m12, src.m13, src.m20, src.m22, src.m23); + float t32 = -determinant3x3(src.m00, src.m01, src.m03, src.m10, src.m11, src.m13, src.m20, src.m21, + src.m23); + float t33 = determinant3x3(src.m00, src.m01, src.m02, src.m10, src.m11, src.m12, src.m20, src.m21, src.m22); + + // transpose and divide by the determinant + dest.m00 = t00 * determinant_inv; + dest.m11 = t11 * determinant_inv; + dest.m22 = t22 * determinant_inv; + dest.m33 = t33 * determinant_inv; + dest.m01 = t10 * determinant_inv; + dest.m10 = t01 * determinant_inv; + dest.m20 = t02 * determinant_inv; + dest.m02 = t20 * determinant_inv; + dest.m12 = t21 * determinant_inv; + dest.m21 = t12 * determinant_inv; + dest.m03 = t30 * determinant_inv; + dest.m30 = t03 * determinant_inv; + dest.m13 = t31 * determinant_inv; + dest.m31 = t13 * determinant_inv; + dest.m32 = t23 * determinant_inv; + dest.m23 = t32 * determinant_inv; + return dest; + } else + return null; + } + + /** + * Negate this matrix and place the result in a destination matrix. + * + * @param src The source matrix + * @param dest The destination matrix, or null if a new matrix is to be created + * @return The negated matrix + */ + public static Matrix4f negate(Matrix4f src, Matrix4f dest) { + if (dest == null) + dest = new Matrix4f(); + + dest.m00 = -src.m00; + dest.m01 = -src.m01; + dest.m02 = -src.m02; + dest.m03 = -src.m03; + dest.m10 = -src.m10; + dest.m11 = -src.m11; + dest.m12 = -src.m12; + dest.m13 = -src.m13; + dest.m20 = -src.m20; + dest.m21 = -src.m21; + dest.m22 = -src.m22; + dest.m23 = -src.m23; + dest.m30 = -src.m30; + dest.m31 = -src.m31; + dest.m32 = -src.m32; + dest.m33 = -src.m33; + + return dest; + } + + /** + * Returns a string representation of this matrix + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(m00).append(' ').append(m10).append(' ').append(m20).append(' ').append(m30).append('\n'); + buf.append(m01).append(' ').append(m11).append(' ').append(m21).append(' ').append(m31).append('\n'); + buf.append(m02).append(' ').append(m12).append(' ').append(m22).append(' ').append(m32).append('\n'); + buf.append(m03).append(' ').append(m13).append(' ').append(m23).append(' ').append(m33).append('\n'); + return buf.toString(); + } + + /** + * Set this matrix to be the identity matrix. + * + * @return this + */ + public Matrix setIdentity() { + return setIdentity(this); + } + + /** + * Set this matrix to 0. + * + * @return this + */ + public Matrix setZero() { + return setZero(this); + } + + /** + * Load from another matrix4f + * + * @param src The source matrix + * @return this + */ + public Matrix4f load(Matrix4f src) { + return load(src, this); + } + + /** + * Load from a float buffer. The buffer stores the matrix in column major + * (OpenGL) order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix load(FloatBuffer buf) { + + m00 = buf.get(); + m01 = buf.get(); + m02 = buf.get(); + m03 = buf.get(); + m10 = buf.get(); + m11 = buf.get(); + m12 = buf.get(); + m13 = buf.get(); + m20 = buf.get(); + m21 = buf.get(); + m22 = buf.get(); + m23 = buf.get(); + m30 = buf.get(); + m31 = buf.get(); + m32 = buf.get(); + m33 = buf.get(); + + return this; + } + + /** + * Load from a float buffer. The buffer stores the matrix in row major (maths) + * order. + * + * @param buf A float buffer to read from + * @return this + */ + public Matrix loadTranspose(FloatBuffer buf) { + + m00 = buf.get(); + m10 = buf.get(); + m20 = buf.get(); + m30 = buf.get(); + m01 = buf.get(); + m11 = buf.get(); + m21 = buf.get(); + m31 = buf.get(); + m02 = buf.get(); + m12 = buf.get(); + m22 = buf.get(); + m32 = buf.get(); + m03 = buf.get(); + m13 = buf.get(); + m23 = buf.get(); + m33 = buf.get(); + + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in column major + * (openGL) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix store(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m02); + buf.put(m03); + buf.put(m10); + buf.put(m11); + buf.put(m12); + buf.put(m13); + buf.put(m20); + buf.put(m21); + buf.put(m22); + buf.put(m23); + buf.put(m30); + buf.put(m31); + buf.put(m32); + buf.put(m33); + return this; + } + + /** + * Store this matrix in a float buffer. The matrix is stored in row major + * (maths) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix storeTranspose(FloatBuffer buf) { + buf.put(m00); + buf.put(m10); + buf.put(m20); + buf.put(m30); + buf.put(m01); + buf.put(m11); + buf.put(m21); + buf.put(m31); + buf.put(m02); + buf.put(m12); + buf.put(m22); + buf.put(m32); + buf.put(m03); + buf.put(m13); + buf.put(m23); + buf.put(m33); + return this; + } + + /** + * Store the rotation portion of this matrix in a float buffer. The matrix is + * stored in column major (openGL) order. + * + * @param buf The buffer to store this matrix in + */ + public Matrix store3f(FloatBuffer buf) { + buf.put(m00); + buf.put(m01); + buf.put(m02); + buf.put(m10); + buf.put(m11); + buf.put(m12); + buf.put(m20); + buf.put(m21); + buf.put(m22); + return this; + } + + /** + * Transpose this matrix + * + * @return this + */ + public Matrix transpose() { + return transpose(this); + } + + /** + * Translate this matrix + * + * @param vec The vector to translate by + * @return this + */ + public Matrix4f translate(Vector2f vec) { + return translate(vec, this); + } + + /** + * Translate this matrix + * + * @param vec The vector to translate by + * @return this + */ + public Matrix4f translate(Vector3f vec) { + return translate(vec, this); + } + + /** + * Scales this matrix + * + * @param vec The vector to scale by + * @return this + */ + public Matrix4f scale(Vector3f vec) { + return scale(vec, this, this); + } + + /** + * Rotates the matrix around the given axis the specified angle + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @return this + */ + public Matrix4f rotate(float angle, Vector3f axis) { + return rotate(angle, axis, this); + } + + /** + * Rotates the matrix around the given axis the specified angle + * + * @param angle the angle, in radians. + * @param axis The vector representing the rotation axis. Must be normalized. + * @param dest The matrix to put the result, or null if a new matrix is to be + * created + * @return The rotated matrix + */ + public Matrix4f rotate(float angle, Vector3f axis, Matrix4f dest) { + return rotate(angle, axis, this, dest); + } + + /** + * Translate this matrix and stash the result in another matrix + * + * @param vec The vector to translate by + * @param dest The destination matrix or null if a new matrix is to be created + * @return the translated matrix + */ + public Matrix4f translate(Vector3f vec, Matrix4f dest) { + return translate(vec, this, dest); + } + + /** + * Translate this matrix and stash the result in another matrix + * + * @param vec The vector to translate by + * @param dest The destination matrix or null if a new matrix is to be created + * @return the translated matrix + */ + public Matrix4f translate(Vector2f vec, Matrix4f dest) { + return translate(vec, this, dest); + } + + /** + * Transpose this matrix and place the result in another matrix + * + * @param dest The destination matrix or null if a new matrix is to be created + * @return the transposed matrix + */ + public Matrix4f transpose(Matrix4f dest) { + return transpose(this, dest); + } + + /** + * @return the determinant of the matrix + */ + public float determinant() { + float f = m00 * ((m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32) - m13 * m22 * m31 - m11 * m23 * m32 + - m12 * m21 * m33); + f -= m01 * ((m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32) - m13 * m22 * m30 - m10 * m23 * m32 + - m12 * m20 * m33); + f += m02 * ((m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31) - m13 * m21 * m30 - m10 * m23 * m31 + - m11 * m20 * m33); + f -= m03 * ((m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31) - m12 * m21 * m30 - m10 * m22 * m31 + - m11 * m20 * m32); + return f; + } + + /** + * Invert this matrix + * + * @return this if successful, null otherwise + */ + public Matrix invert() { + return invert(this, this); + } + + /** + * Negate this matrix + * + * @return this + */ + public Matrix negate() { + return negate(this); + } + + /** + * Negate this matrix and place the result in a destination matrix. + * + * @param dest The destination matrix, or null if a new matrix is to be created + * @return the negated matrix + */ + public Matrix4f negate(Matrix4f dest) { + return negate(this, dest); + } +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/ReadableVector.java b/src/main/java/org/lwjgl/utils/vector/ReadableVector.java new file mode 100644 index 00000000..adef6d23 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/ReadableVector.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import xueli.utils.buffer.LotsOfByteBuffer; + +/** + * @author foo + */ +public interface ReadableVector { + /** + * @return the length of the vector + */ + float length(); + + /** + * @return the length squared of the vector + */ + float lengthSquared(); + + /** + * Store this vector in a FloatBuffer + * + * @param buf The buffer to store it in, at the current position + */ + void store(LotsOfByteBuffer buf); +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/ReadableVector2f.java b/src/main/java/org/lwjgl/utils/vector/ReadableVector2f.java new file mode 100644 index 00000000..72648a2b --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/ReadableVector2f.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * @author foo + */ +public interface ReadableVector2f extends ReadableVector { + /** + * @return x + */ + float getX(); + + /** + * @return y + */ + float getY(); +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/ReadableVector3f.java b/src/main/java/org/lwjgl/utils/vector/ReadableVector3f.java new file mode 100644 index 00000000..bd1c8342 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/ReadableVector3f.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * @author foo + */ +public interface ReadableVector3f extends ReadableVector2f { + /** + * @return z + */ + float getZ(); +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/ReadableVector4f.java b/src/main/java/org/lwjgl/utils/vector/ReadableVector4f.java new file mode 100644 index 00000000..08ca34cd --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/ReadableVector4f.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * @author foo + */ +public interface ReadableVector4f extends ReadableVector3f { + + /** + * @return w + */ + float getW(); + +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Vector.java b/src/main/java/org/lwjgl/utils/vector/Vector.java new file mode 100644 index 00000000..5b32c4c8 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.io.Serializable; +import java.nio.FloatBuffer; + +import xueli.game2.renderer.legacy.buffer.BufferStorable; + +/** + * Base class for vectors. + * + * @author cix_foo + * @version $Revision$ $Id$ + */ +public abstract class Vector implements Serializable, ReadableVector, BufferStorable { + + private static final long serialVersionUID = 1L; + + /** + * Constructor for Vector. + */ + protected Vector() { + super(); + } + + /** + * @return the length of the vector + */ + public final float length() { + return (float) Math.sqrt(lengthSquared()); + } + + /** + * @return the length squared of the vector + */ + public abstract float lengthSquared(); + + /** + * Load this vector from a FloatBuffer + * + * @param buf The buffer to load it from, at the current position + * @return this + */ + public abstract Vector load(FloatBuffer buf); + + /** + * Negate a vector + * + * @return this + */ + public abstract Vector negate(); + + /** + * Normalise this vector + * + * @return this + */ + public final Vector normalise() { + float len = length(); + if (len != 0.0f) { + float l = 1.0f / len; + return scale(l); + } else + throw new IllegalStateException("Zero length vector"); + } + + /** + * Scale this vector + * + * @param scale The scale factor + * @return this + */ + public abstract Vector scale(float scale); + + public abstract int getSize(); + +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Vector2d.java b/src/main/java/org/lwjgl/utils/vector/Vector2d.java new file mode 100644 index 00000000..05aba892 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector2d.java @@ -0,0 +1,42 @@ +package org.lwjgl.utils.vector; + +import java.util.Objects; + +public class Vector2d { + + public double x, y; + + public Vector2d() { + this.x = this.y = 0; + } + + public Vector2d(double x, double y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Vector2d vector2d = (Vector2d) o; + return Double.compare(vector2d.x, x) == 0 && Double.compare(vector2d.y, y) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public String toString() { + return "Vector2d{" + "x=" + x + ", y=" + y + '}'; + } + + public static Vector2d add(Vector2d v1, Vector2d v2) { + return new Vector2d(v1.x + v2.x, v1.y + v2.y); + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector2f.java b/src/main/java/org/lwjgl/utils/vector/Vector2f.java new file mode 100644 index 00000000..ba4c4b76 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector2f.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +import xueli.utils.buffer.LotsOfByteBuffer; + +/** + * Holds a 2-tuple vector. + * + * @author cix_foo + * @version $Revision$ $Id$ + */ + +public class Vector2f extends Vector implements ReadableVector2f, WritableVector2f { + + private static final long serialVersionUID = 1L; + + public float x, y; + + /** + * Constructor for Vector2f. + */ + public Vector2f() { + super(); + } + + /** + * Constructor. + */ + public Vector2f(ReadableVector2f src) { + set(src); + } + + /** + * Constructor. + */ + public Vector2f(float x, float y) { + set(x, y); + } + + /** + * The dot product of two vectors is calculated as v1.x * v2.x + v1.y * v2.y + + * v1.z * v2.z + * + * @param left The LHS vector + * @param right The RHS vector + * @return left dot right + */ + public static float dot(Vector2f left, Vector2f right) { + return left.x * right.x + left.y * right.y; + } + + /** + * Calculate the angle between two vectors, in radians + * + * @param a A vector + * @param b The other vector + * @return the angle between the two vectors, in radians + */ + public static float angle(Vector2f a, Vector2f b) { + float dls = dot(a, b) / (a.length() * b.length()); + if (dls < -1f) + dls = -1f; + else if (dls > 1.0f) + dls = 1.0f; + return (float) Math.acos(dls); + } + + /** + * Add a vector to another vector and place the result in a destination vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return the sum of left and right in dest + */ + public static Vector2f add(Vector2f left, Vector2f right, Vector2f dest) { + if (dest == null) + return new Vector2f(left.x + right.x, left.y + right.y); + else { + dest.set(left.x + right.x, left.y + right.y); + return dest; + } + } + + /** + * Subtract a vector from another vector and place the result in a destination + * vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return left minus right in dest + */ + public static Vector2f sub(Vector2f left, Vector2f right, Vector2f dest) { + if (dest == null) + return new Vector2f(left.x - right.x, left.y - right.y); + else { + dest.set(left.x - right.x, left.y - right.y); + return dest; + } + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector2f#set(float, float) + */ + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Load from another Vector2f + * + * @param src The source vector + * @return this + */ + public Vector2f set(ReadableVector2f src) { + x = src.getX(); + y = src.getY(); + return this; + } + + /** + * @return the length squared of the vector + */ + public float lengthSquared() { + return x * x + y * y; + } + + /** + * Translate a vector + * + * @param x The translation in x + * @param y the translation in y + * @return this + */ + public Vector2f translate(float x, float y) { + this.x += x; + this.y += y; + return this; + } + + /** + * Negate a vector + * + * @return this + */ + public Vector negate() { + x = -x; + y = -y; + return this; + } + + /** + * Negate a vector and place the result in a destination vector. + * + * @param dest The destination vector or null if a new vector is to be created + * @return the negated vector + */ + public Vector2f negate(Vector2f dest) { + if (dest == null) + dest = new Vector2f(); + dest.x = -x; + dest.y = -y; + return dest; + } + + /** + * Normalise this vector and place the result in another vector. + * + * @param dest The destination vector, or null if a new vector is to be created + * @return the normalised vector + */ + public Vector2f normalise(Vector2f dest) { + float l = length(); + + if (dest == null) + dest = new Vector2f(x / l, y / l); + else + dest.set(x / l, y / l); + + return dest; + } + + /** + * Store this vector in a FloatBuffer + * + * @param buf The buffer to store it in, at the current position + */ + public void store(LotsOfByteBuffer buf) { + buf.putFloat(x); + buf.putFloat(y); + } + + /** + * Load this vector from a FloatBuffer + * + * @param buf The buffer to load it from, at the current position + * @return this + */ + public Vector load(FloatBuffer buf) { + x = buf.get(); + y = buf.get(); + return this; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#scale(float) + */ + public Vector scale(float scale) { + + x *= scale; + y *= scale; + + return this; + } + + @Override + public int getSize() { + return 2 * Float.BYTES; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder sb = new StringBuilder(64); + + sb.append("Vector2f["); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(']'); + return sb.toString(); + } + + /** + * @return x + */ + public final float getX() { + return x; + } + + /** + * Set X + * + * @param x + */ + public final void setX(float x) { + this.x = x; + } + + /** + * @return y + */ + public final float getY() { + return y; + } + + /** + * Set Y + * + * @param y + */ + public final void setY(float y) { + this.y = y; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector2f other = (Vector2f) obj; + + if (x == other.x && y == other.y) + return true; + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Vector2i.java b/src/main/java/org/lwjgl/utils/vector/Vector2i.java new file mode 100644 index 00000000..de070576 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector2i.java @@ -0,0 +1,45 @@ +package org.lwjgl.utils.vector; + +import java.util.Objects; + +public class Vector2i implements Comparable { + + public int x, y; + + public Vector2i() { + this.x = this.y = 0; + } + + public Vector2i(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "Vector2i{" + "x=" + x + ", y=" + y + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Vector2i vector2i = (Vector2i) o; + return x == vector2i.x && y == vector2i.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public int compareTo(Vector2i o) { + if (o == null) + return this.hashCode(); + return this.hashCode() - o.hashCode(); + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector2s.java b/src/main/java/org/lwjgl/utils/vector/Vector2s.java new file mode 100644 index 00000000..744dfa0d --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector2s.java @@ -0,0 +1,16 @@ +package org.lwjgl.utils.vector; + +import java.io.Serializable; + +public class Vector2s implements Serializable { + + private static final long serialVersionUID = 5420508255322410063L; + + public short x, y; + + public Vector2s(short x, short y) { + this.x = x; + this.y = y; + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector3b.java b/src/main/java/org/lwjgl/utils/vector/Vector3b.java new file mode 100644 index 00000000..a5d0a4f9 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector3b.java @@ -0,0 +1,61 @@ +package org.lwjgl.utils.vector; + +import java.util.Objects; + +public class Vector3b { + + public byte x, y, z; + + public Vector3b(byte x, byte y, byte z) { + this.x = x; + this.y = y; + this.z = z; + } + + public byte getX() { + return x; + } + + public void setX(byte x) { + this.x = x; + } + + public byte getY() { + return y; + } + + public void setY(byte y) { + this.y = y; + } + + public byte getZ() { + return z; + } + + public void setZ(byte z) { + this.z = z; + } + + @Override + public String toString() { + return "Vector3b [x=" + x + ", y=" + y + ", z=" + z + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3b other = (Vector3b) obj; + return x == other.x && y == other.y && z == other.z; + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector3d.java b/src/main/java/org/lwjgl/utils/vector/Vector3d.java new file mode 100644 index 00000000..f0fb097c --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector3d.java @@ -0,0 +1,51 @@ +package org.lwjgl.utils.vector; + +import java.util.Objects; + +public class Vector3d { + + public double x, y, z; + + public Vector3d() { + this.x = this.y = this.z = 0; + } + + public Vector3d(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void normalize() { + double length = Math.sqrt(x * x + y * y + z * z); + this.x /= length; + this.y /= length; + this.z /= length; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Vector3d vector3d = (Vector3d) o; + return Double.compare(vector3d.x, x) == 0 && Double.compare(vector3d.y, y) == 0 + && Double.compare(vector3d.z, z) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public String toString() { + return "Vector3d{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; + } + + public static Vector3d add(Vector3d v1, Vector3d v2) { + return new Vector3d(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector3f.java b/src/main/java/org/lwjgl/utils/vector/Vector3f.java new file mode 100644 index 00000000..00d1d771 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector3f.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +import xueli.utils.buffer.LotsOfByteBuffer; + +/** + * Holds a 3-tuple vector. + * + * @author cix_foo + * @version $Revision$ $Id$ + */ + +public class Vector3f extends Vector implements ReadableVector3f, WritableVector3f { + + private static final long serialVersionUID = 1L; + + public float x, y, z; + + /** + * Constructor for Vector3f. + */ + public Vector3f() { + super(); + } + + /** + * Constructor + */ + public Vector3f(ReadableVector3f src) { + set(src); + } + + /** + * Constructor + */ + public Vector3f(float x, float y, float z) { + set(x, y, z); + } + + /** + * Add a vector to another vector and place the result in a destination vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return the sum of left and right in dest + */ + public static Vector3f add(Vector3f left, Vector3f right, Vector3f dest) { + if (dest == null) + return new Vector3f(left.x + right.x, left.y + right.y, left.z + right.z); + else { + dest.set(left.x + right.x, left.y + right.y, left.z + right.z); + return dest; + } + } + + /** + * Subtract a vector from another vector and place the result in a destination + * vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return left minus right in dest + */ + public static Vector3f sub(Vector3f left, Vector3f right, Vector3f dest) { + if (dest == null) + return new Vector3f(left.x - right.x, left.y - right.y, left.z - right.z); + else { + dest.set(left.x - right.x, left.y - right.y, left.z - right.z); + return dest; + } + } + + /** + * The cross product of two vectors. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination result, or null if a new vector is to be created + * @return left cross right + */ + public static Vector3f cross(Vector3f left, Vector3f right, Vector3f dest) { + + if (dest == null) + dest = new Vector3f(); + + dest.set(left.y * right.z - left.z * right.y, right.x * left.z - right.z * left.x, + left.x * right.y - left.y * right.x); + + return dest; + } + + /** + * The dot product of two vectors is calculated as v1.x * v2.x + v1.y * v2.y + + * v1.z * v2.z + * + * @param left The LHS vector + * @param right The RHS vector + * @return left dot right + */ + public static float dot(Vector3f left, Vector3f right) { + return left.x * right.x + left.y * right.y + left.z * right.z; + } + + /** + * Calculate the angle between two vectors, in radians + * + * @param a A vector + * @param b The other vector + * @return the angle between the two vectors, in radians + */ + public static float angle(Vector3f a, Vector3f b) { + float dls = dot(a, b) / (a.length() * b.length()); + if (dls < -1f) + dls = -1f; + else if (dls > 1.0f) + dls = 1.0f; + return (float) Math.acos(dls); + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector2f#set(float, float) + */ + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector3f#set(float, float, float) + */ + public void set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Load from another Vector3f + * + * @param src The source vector + * @return this + */ + public Vector3f set(ReadableVector3f src) { + x = src.getX(); + y = src.getY(); + z = src.getZ(); + return this; + } + + /** + * @return the length squared of the vector + */ + public float lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * Translate a vector + * + * @param x The translation in x + * @param y the translation in y + * @return this + */ + public Vector3f translate(float x, float y, float z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + /** + * Negate a vector + * + * @return this + */ + public Vector negate() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * Negate a vector and place the result in a destination vector. + * + * @param dest The destination vector or null if a new vector is to be created + * @return the negated vector + */ + public Vector3f negate(Vector3f dest) { + if (dest == null) + dest = new Vector3f(); + dest.x = -x; + dest.y = -y; + dest.z = -z; + return dest; + } + + /** + * Normalise this vector and place the result in another vector. + * + * @param dest The destination vector, or null if a new vector is to be created + * @return the normalised vector + */ + public Vector3f normalise(Vector3f dest) { + float l = length(); + + if (dest == null) + dest = new Vector3f(x / l, y / l, z / l); + else + dest.set(x / l, y / l, z / l); + + return dest; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#load(FloatBuffer) + */ + public Vector load(FloatBuffer buf) { + x = buf.get(); + y = buf.get(); + z = buf.get(); + return this; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#scale(float) + */ + public Vector scale(float scale) { + x *= scale; + y *= scale; + z *= scale; + + return this; + + } + + @Override + public int getSize() { + return 3 * Float.BYTES; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#store(FloatBuffer) + */ + public void store(LotsOfByteBuffer buf) { + buf.putFloat(x); + buf.putFloat(y); + buf.putFloat(z); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder sb = new StringBuilder(64); + + sb.append("Vector3f["); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(z); + sb.append(']'); + return sb.toString(); + } + + /** + * @return x + */ + public final float getX() { + return x; + } + + /** + * Set X + * + * @param x + */ + public final void setX(float x) { + this.x = x; + } + + /** + * @return y + */ + public final float getY() { + return y; + } + + /** + * Set Y + * + * @param y + */ + public final void setY(float y) { + this.y = y; + } + + /* + * (Overrides) + * + * @see org.lwjgl.vector.ReadableVector3f#getZ() + */ + public float getZ() { + return z; + } + + /** + * Set Z + * + * @param z + */ + public void setZ(float z) { + this.z = z; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3f other = (Vector3f) obj; + + if (x == other.x && y == other.y && z == other.z) + return true; + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/Vector3i.java b/src/main/java/org/lwjgl/utils/vector/Vector3i.java new file mode 100644 index 00000000..b300653f --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector3i.java @@ -0,0 +1,73 @@ +package org.lwjgl.utils.vector; + +import java.util.Objects; + +public class Vector3i { + + public int x, y, z; + + public Vector3i(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3i(Vector3d doubleVector) { + this.x = (int) Math.floor(doubleVector.x); + this.y = (int) Math.floor(doubleVector.y); + this.z = (int) Math.floor(doubleVector.z); + + } + + public Vector3i(Vector3f floatVector) { + this.x = (int) Math.floor(floatVector.x); + this.y = (int) Math.floor(floatVector.y); + this.z = (int) Math.floor(floatVector.z); + + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + @Override + public String toString() { + return "Vector3i [x=" + x + ", y=" + y + ", z=" + z + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector3i other = (Vector3i) obj; + if (x != other.x) + return false; + if (y != other.y) + return false; + if (z != other.z) + return false; + return true; + } + + public static Vector3i add(Vector3i v1, Vector3i v2) { + return new Vector3i(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector4b.java b/src/main/java/org/lwjgl/utils/vector/Vector4b.java new file mode 100644 index 00000000..f63c9aa2 --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector4b.java @@ -0,0 +1,14 @@ +package org.lwjgl.utils.vector; + +public class Vector4b { + + public byte x, y, z, w; + + public Vector4b(byte x, byte y, byte z, byte w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + +} diff --git a/src/main/java/org/lwjgl/utils/vector/Vector4f.java b/src/main/java/org/lwjgl/utils/vector/Vector4f.java new file mode 100644 index 00000000..7c4c4eee --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/Vector4f.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +import java.nio.FloatBuffer; + +import xueli.utils.buffer.LotsOfByteBuffer; + +/** + * Holds a 4-tuple vector. + * + * @author cix_foo + * @version $Revision$ $Id$ + */ + +public class Vector4f extends Vector implements ReadableVector4f, WritableVector4f { + + private static final long serialVersionUID = 1L; + + public float x, y, z, w; + + /** + * Constructor for Vector4f. + */ + public Vector4f() { + super(); + } + + /** + * Constructor + */ + public Vector4f(ReadableVector4f src) { + set(src); + } + + /** + * Constructor + */ + public Vector4f(float x, float y, float z, float w) { + set(x, y, z, w); + } + + public Vector4f(float f) { + this(f, f, f, f); + } + + /** + * Add a vector to another vector and place the result in a destination vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return the sum of left and right in dest + */ + public static Vector4f add(Vector4f left, Vector4f right, Vector4f dest) { + if (dest == null) + return new Vector4f(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); + else { + dest.set(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); + return dest; + } + } + + /** + * Subtract a vector from another vector and place the result in a destination + * vector. + * + * @param left The LHS vector + * @param right The RHS vector + * @param dest The destination vector, or null if a new vector is to be created + * @return left minus right in dest + */ + public static Vector4f sub(Vector4f left, Vector4f right, Vector4f dest) { + if (dest == null) + return new Vector4f(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); + else { + dest.set(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); + return dest; + } + } + + /** + * The dot product of two vectors is calculated as v1.x * v2.x + v1.y * v2.y + + * v1.z * v2.z + v1.w * v2.w + * + * @param left The LHS vector + * @param right The RHS vector + * @return left dot right + */ + public static float dot(Vector4f left, Vector4f right) { + return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w; + } + + /** + * Calculate the angle between two vectors, in radians + * + * @param a A vector + * @param b The other vector + * @return the angle between the two vectors, in radians + */ + public static float angle(Vector4f a, Vector4f b) { + float dls = dot(a, b) / (a.length() * b.length()); + if (dls < -1f) + dls = -1f; + else if (dls > 1.0f) + dls = 1.0f; + return (float) Math.acos(dls); + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector2f#set(float, float) + */ + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector3f#set(float, float, float) + */ + public void set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.util.vector.WritableVector4f#set(float, float, float, float) + */ + public void set(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /** + * Load from another Vector4f + * + * @param src The source vector + * @return this + */ + public Vector4f set(ReadableVector4f src) { + x = src.getX(); + y = src.getY(); + z = src.getZ(); + w = src.getW(); + return this; + } + + /** + * @return the length squared of the vector + */ + public float lengthSquared() { + return x * x + y * y + z * z + w * w; + } + + /** + * Translate a vector + * + * @param x The translation in x + * @param y the translation in y + * @return this + */ + public Vector4f translate(float x, float y, float z, float w) { + this.x += x; + this.y += y; + this.z += z; + this.w += w; + return this; + } + + /** + * Negate a vector + * + * @return this + */ + public Vector negate() { + x = -x; + y = -y; + z = -z; + w = -w; + return this; + } + + /** + * Negate a vector and place the result in a destination vector. + * + * @param dest The destination vector or null if a new vector is to be created + * @return the negated vector + */ + public Vector4f negate(Vector4f dest) { + if (dest == null) + dest = new Vector4f(); + dest.x = -x; + dest.y = -y; + dest.z = -z; + dest.w = -w; + return dest; + } + + /** + * Normalise this vector and place the result in another vector. + * + * @param dest The destination vector, or null if a new vector is to be created + * @return the normalised vector + */ + public Vector4f normalise(Vector4f dest) { + float l = length(); + + if (dest == null) + dest = new Vector4f(x / l, y / l, z / l, w / l); + else + dest.set(x / l, y / l, z / l, w / l); + + return dest; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#load(FloatBuffer) + */ + public Vector load(FloatBuffer buf) { + x = buf.get(); + y = buf.get(); + z = buf.get(); + w = buf.get(); + return this; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#scale(float) + */ + public Vector scale(float scale) { + x *= scale; + y *= scale; + z *= scale; + w *= scale; + return this; + } + + @Override + public int getSize() { + return 4 * Float.BYTES; + } + + /* + * (non-Javadoc) + * + * @see org.lwjgl.vector.Vector#store(FloatBuffer) + */ + public void store(LotsOfByteBuffer buf) { + buf.putFloat(x); + buf.putFloat(y); + buf.putFloat(z); + buf.putFloat(w); + + } + + public String toString() { + return "Vector4f: " + x + " " + y + " " + z + " " + w; + } + + /** + * @return x + */ + public final float getX() { + return x; + } + + /** + * Set X + * + * @param x + */ + public final void setX(float x) { + this.x = x; + } + + /** + * @return y + */ + public final float getY() { + return y; + } + + /** + * Set Y + * + * @param y + */ + public final void setY(float y) { + this.y = y; + } + + /* + * (Overrides) + * + * @see org.lwjgl.vector.ReadableVector3f#getZ() + */ + public float getZ() { + return z; + } + + /** + * Set Z + * + * @param z + */ + public void setZ(float z) { + this.z = z; + } + + /* + * (Overrides) + * + * @see org.lwjgl.vector.ReadableVector3f#getZ() + */ + public float getW() { + return w; + } + + /** + * Set W + * + * @param w + */ + public void setW(float w) { + this.w = w; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Vector4f other = (Vector4f) obj; + + if (x == other.x && y == other.y && z == other.z && w == other.w) + return true; + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/WritableVector2f.java b/src/main/java/org/lwjgl/utils/vector/WritableVector2f.java new file mode 100644 index 00000000..15e66c6c --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/WritableVector2f.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * Writable interface to Vector2fs + * + * @author $author$ + * @version $revision$ $Id$ + */ +public interface WritableVector2f { + + /** + * Set the X value + * + * @param x + */ + void setX(float x); + + /** + * Set the Y value + * + * @param y + */ + void setY(float y); + + /** + * Set the X,Y values + * + * @param x + * @param y + */ + void set(float x, float y); + +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/WritableVector3f.java b/src/main/java/org/lwjgl/utils/vector/WritableVector3f.java new file mode 100644 index 00000000..13fc430d --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/WritableVector3f.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * Writable interface to Vector3fs + * + * @author $author$ + * @version $revision$ $Id$ + */ +public interface WritableVector3f extends WritableVector2f { + + /** + * Set the Z value + * + * @param z + */ + void setZ(float z); + + /** + * Set the X,Y,Z values + * + * @param x + * @param y + * @param z + */ + void set(float x, float y, float z); + +} \ No newline at end of file diff --git a/src/main/java/org/lwjgl/utils/vector/WritableVector4f.java b/src/main/java/org/lwjgl/utils/vector/WritableVector4f.java new file mode 100644 index 00000000..f20ba3ef --- /dev/null +++ b/src/main/java/org/lwjgl/utils/vector/WritableVector4f.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2002-2008 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.utils.vector; + +/** + * Writable interface to Vector4fs + * + * @author $author$ + * @version $revision$ $Id$ + */ +public interface WritableVector4f extends WritableVector3f { + + /** + * Set the W value + * + * @param w + */ + void setW(float w); + + /** + * Set the X,Y,Z,W values + * + * @param x + * @param y + * @param z + * @param w + */ + void set(float x, float y, float z, float w); + +} \ No newline at end of file diff --git a/src/main/java/riven/PerlinNoise.java b/src/main/java/riven/PerlinNoise.java new file mode 100644 index 00000000..4b26106c --- /dev/null +++ b/src/main/java/riven/PerlinNoise.java @@ -0,0 +1,352 @@ +package riven; + +import java.util.Random; + +import xueli.mcremake.registry.PocketNativeType; + +/** + *

+ * Adapted from Riven's Implementation of Perlin noise. Modified it to be more + * OOP rather than C like. + *

+ * + * @author Matthew A. Johnston (WarmWaffles) + * + */ +@PocketNativeType(version = "0.1.3", value = "ImprovedNoise") +public class PerlinNoise { + + private static final double[] pow = new double[32]; + static { + for (int i = 0; i < pow.length; i++) + pow[i] = Math.pow(2, i); + + } + + @PocketNativeType("*((_DWORD*)this + 2)") + private double xo; + @PocketNativeType("*((_DWORD*)this + 3)") + private double yo; + @PocketNativeType("*((_DWORD*)this + 4)") + private double zo; + + @PocketNativeType("*((_DWORD*)v4_this + 5)") + private int[] perm; + + /** + * Builds the Perlin Noise generator. + * + * @param seed The seed for the random number generator + */ + public PerlinNoise(int seed) { + this(new Random(seed)); + } + + public PerlinNoise(Random r) { + this.xo = r.nextDouble(256.0); // Origin: (double)random.genrand_int32() * 2.32830644e-10 + this.yo = r.nextDouble(256.0); + this.zo = r.nextDouble(256.0); + + perm = new int[512]; + for (int i = 0; i < 256; i++) { + perm[i] = i; + } + + for (int i = 0; i < 256; i++) { + int v3 = r.nextInt(256 - i); + + int v4 = perm[i]; + perm[i] = perm[i + v3]; + perm[i + v3] = v4; + + perm[i + 256] = perm[i]; + + } + +// if (permutation.length != 256) +// throw new IllegalStateException(); + +// for (int i = 0; i < 256; i++) +// perm[256 + i] = perm[i] = permutation[i]; + + } + + /** + * + * @param x + * @param y + * @param z + */ + public void offset(double x, double y, double z) { + this.xo = x; + this.yo = y; + this.zo = z; + } + + /** + * + * @param x + * @param y + * @param z + * @param octaves + * @return + */ + public double smoothNoise(double x, double y, double z, int octaves) { + double height = 0.0; + for (int octave = 1; octave <= octaves; octave++) + height += noise(x, y, z, octave); + return height; + } + + /** + * + * @param x + * @param y + * @param z + * @param octaves + * @return + */ + public double turbulentNoise(double x, double y, double z, int octaves) { + double height = 0.0; + for (int octave = 1; octave <= octaves; octave++) { + double h = noise(x, y, z, octave); + if (h < 0.0f) + h *= -1.0; + height += h; + } + return height; + } + + /** + * + * @param x + * @param y + * @param z + * @return + */ + public double noise(double x, double y, double z) { + double fx = floor(x); + double fy = floor(y); + double fz = floor(z); + + int gx = (int) fx & 0xFF; + int gy = (int) fy & 0xFF; + int gz = (int) fz & 0xFF; + + double u = fade(x -= fx); + double v = fade(y -= fy); + double w = fade(z -= fz); + + int a0 = perm[gx + 0] + gy; + int b0 = perm[gx + 1] + gy; + int aa = perm[a0 + 0] + gz; + int ab = perm[a0 + 1] + gz; + int ba = perm[b0 + 0] + gz; + int bb = perm[b0 + 1] + gz; + + double a1 = grad(perm[bb + 1], x - 1, y - 1, z - 1); + double a2 = grad(perm[ab + 1], x - 0, y - 1, z - 1); + double a3 = grad(perm[ba + 1], x - 1, y - 0, z - 1); + double a4 = grad(perm[aa + 1], x - 0, y - 0, z - 1); + double a5 = grad(perm[bb + 0], x - 1, y - 1, z - 0); + double a6 = grad(perm[ab + 0], x - 0, y - 1, z - 0); + double a7 = grad(perm[ba + 0], x - 1, y - 0, z - 0); + double a8 = grad(perm[aa + 0], x - 0, y - 0, z - 0); + + double a2_1 = lerp(u, a2, a1); + double a4_3 = lerp(u, a4, a3); + double a6_5 = lerp(u, a6, a5); + double a8_7 = lerp(u, a8, a7); + double a8_5 = lerp(v, a8_7, a6_5); + double a4_1 = lerp(v, a4_3, a2_1); + double a8_1 = lerp(w, a8_5, a4_1); + + return a8_1; + } + + public double[] add(double[] v39, double v38, double v37, double a5, int a6, int a7, double a8, double a9, + double a10, double a11, double a12) { + if (a7 == 1) { + if (a6 > 0) { + int v74 = 0; + for (int i = 0; i < a6; i++) { + double v49 = (i + v38) * a9 + this.xo; + int v49_floor = (int) Math.floor(v49); // Maybe casting it directly is faster + double v49_remain = v49 - v49_floor; + v49_floor = Math.floorMod(v49_floor, 256); // Cast to unsigned __int8 + + if (a8 > 0) { + double v49_fade = this.fade(v49_remain); + for (int j = 0; j < a8; j++) { + double v58 = (j + a5) * a11 + this.zo; + int v58_floor = (int) Math.floor(v58); + double v58_remain = v58 - v58_floor; + v58_floor = Math.floorMod(v58_floor, 256); // Cast to unsigned __int8 + + int v62 = v58_floor + perm[perm[v49_floor]]; + int v64 = v58_floor + perm[perm[v49_floor + 1]]; + + double v65 = grad2(perm[v62], v49_remain, v58_remain); // Index 574 out of bounds for length + // 512 + double v66 = grad(perm[v64], v49_remain - 1.0, 0.0, v58_remain); + double v67 = lerp(v49_fade, v65, v66); + + double v68 = grad(perm[v62 + 1], v49_remain, 0.0, v58_remain - 1.0); + double v69 = grad(perm[v64 + 1], v49_remain - 1.0, 0.0, v58_remain - 1.0); + double v70 = lerp(v49_fade, v68, v69); + + double result = lerp(fade(v58_remain), v67, v70); + v39[v74] += result / a12; + v74++; + + } + } + } + } + } else if (a6 > 0) { + int v60 = 0; + int v59 = -1; + double v58 = 0.0, v57 = 0.0, v56 = 0.0, v55 = 0.0; + for (int k = 0; k < a6; k++) { + double v52 = (k + v38) * a9 + this.xo; + int v52_floor = (int) Math.floor(v52); + double v52_remain = v52 - v52_floor; + double v52_fade = fade(v52_remain); + v52_floor = Math.floorMod(v52_floor, 256); // Cast to unsigned __int8 + + for (int l = 0; l < a8; l++) { + double v47 = (l + a5) * a11 + this.zo; + int v47_floor = (int) Math.floor(v47); + double v47_remain = v47 - v47_floor; + double v47_fade = fade(v47_remain); + v47_floor = Math.floorMod(v47_floor, 256); // Cast to unsigned __int8 + + for (int m = 0; m < a7; m++) { + double v43 = (m + v37) * a10 + this.yo; + int v43_floor = (int) Math.floor(v43); + double v43_remain = v43 - v43_floor; + double v43_fade = fade(v43_remain); + v43_floor = Math.floorMod(v43_floor, 256); // Cast to unsigned __int8 + + if (m == 0 || v43_floor != v59) { + v59 = v43_floor; + + int v21 = v43_floor + perm[v52_floor]; + int v22 = v47_floor + perm[v21]; + int v23 = v47_floor + perm[v21 + 1]; + + int v24 = v43_floor + perm[v52_floor + 1]; + int v25 = v47_floor + perm[v24]; + int v26 = v47_floor + perm[v24 + 1]; + + double v27 = grad(perm[v22], v52_remain, v43_remain, v47_remain); + double v28 = grad(perm[v25], v52_remain - 1.0, v43_remain, v47_remain); + v58 = lerp(v52_fade, v27, v28); + double v29 = grad(perm[v23], v52_remain, v43_remain - 1.0, v47_remain); + double v30 = grad(perm[v26], v52_remain - 1.0, v43_remain - 1.0, v47_remain); + v57 = lerp(v52_fade, v29, v30); + double v31 = grad(perm[v22 + 1], v52_remain, v43_remain, v47_remain - 1.0); + double v32 = grad(perm[v25 + 1], v52_remain - 1.0, v43_remain, v47_remain - 1.0); + v56 = lerp(v52_fade, v31, v32); + double v33 = grad(perm[v23 + 1], v52_remain, v43_remain - 1.0, v47_remain - 1.0); + double v34 = grad(perm[v26 + 1], v52_remain - 1.0, v43_remain - 1.0, v47_remain - 1.0); + v55 = lerp(v52_fade, v33, v34); + + } + + double v35 = lerp(v43_fade, v58, v57); + double v36 = lerp(v43_fade, v56, v55); + double result = lerp(v47_fade, v35, v36); + v39[v60] += result / a12; + v60++; + } + } + } + + } + return v39; + } + + // ======================================================================== + // PRIVATE + // ======================================================================== + + /** + * + * @param x + * @param y + * @param z + * @param octave + * @return + */ + private double noise(double x, double y, double z, int octave) { + double p = pow[octave]; + return this.noise(x * p + this.xo, y * p + this.yo, z * p + this.zo) / p; + } + + /** + * + * @param v + * @return + */ + private final double floor(double v) { + return (int) v; + } + + /** + * + * @param t + * @return + */ + private final double fade(double t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); + } + + /** + * + * @param t + * @param a + * @param b + * @return + */ + private final double lerp(double t, double a, double b) { + return a + t * (b - a); + } + + /** + * + * @param hash + * @param x + * @param y + * @param z + * @return + */ + private static double grad(int hash, double x, double y, double z) { + int h = hash & 15; + double u = (h < 8) ? x : y; + double v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + private static double grad2(int hash, double a3, double a4) { + int h = hash & 0xF; + double v4, v5; + if (h <= 3) { + v4 = 0.0; + } else if (h != 12 && h != 14) { + v4 = a4; + } else { + v4 = a3; + } + + v5 = ((1 - ((hash & 8) >> 3)) * a3); + + if ((h & 1) != 0) + v5 = -v5; + if ((h & 2) != 0) + v4 = -v4; + + return v4 + v5; + } + +} \ No newline at end of file diff --git a/src/main/java/xueLi/craftGame/Main.java b/src/main/java/xueLi/craftGame/Main.java deleted file mode 100644 index a23bbc9b..00000000 --- a/src/main/java/xueLi/craftGame/Main.java +++ /dev/null @@ -1,54 +0,0 @@ -package xueLi.craftGame; - -import java.io.IOException; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.GL11; - -import xueLi.craftGame.block.Block; -import xueLi.craftGame.utils.DisplayManager; -import xueLi.craftGame.utils.FPSTimer; -import xueLi.craftGame.world.WorldRenderer; - -public class Main { - - private static int width = 1200, height = 680; - - public static void main(String[] args) throws IOException { - //创åģē一ä¸Ē昞į¤ēåŒē域 - DisplayManager.create(width, height); - - //čŋ™ä¸Ēå‡Ŋ数里éĸåŽžįŽ°å¯šæ–šå—įš„æŗ¨å†Œ 一厚čĻæ”žåœ¨å­˜æĄŖį”Ÿæˆäš‹å‰ - Block.init(); - - //čŋ™ä¸Ēå‡Ŋæ•°åˆå§‹åŒ–ä¸–į•Œæ¸˛æŸ“å™¨ - //æč´¨æ”žåœ¨"res/textures.png"里éĸ,åĨŊ几ä¸Ēæč´¨įģ„合存储 - //å…ŗäēŽæ–°åĸžæ–šå—įš„æ•™į¨‹ 在Blockįš„initæ–šæŗ•é‡Œéĸ - WorldRenderer.init(); - - Mouse.setGrabbed(true); - while (DisplayManager.isRunning()) { - if (DisplayManager.isKeyDown(Keyboard.KEY_ESCAPE)) { - DisplayManager.postDestroyMessage(); - } - - //čŋ”回FPSįš„å€ŧ åĻ‚æžœå¤„åœ¨debugæ¨Ąåŧåˆ™äŧšåœ¨æŽ§åˆļ台里éĸ输å‡ē - FPSTimer.getFPS(); - - //ä¸Žä¸–į•Œæ¸˛æŸ“æœ‰å…ŗ - GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); - WorldRenderer.render(); - - //TODO:čŋ™é‡Œå¯äģĨ写游戏tickæ›´æ–°äš‹įąģįš„ åæ­Ŗå°ąæ˜¯æ¸¸æˆåžĒįŽ¯ - - - //čŋ™æ˜¯æ˜žį¤ēįĒ—åŖįš„åˆˇæ–° - DisplayManager.update(); - } - - //čĩ„æēé‡Šæ”ž - WorldRenderer.release(); - DisplayManager.destroy(); - - } -} diff --git a/src/main/java/xueLi/craftGame/block/Block.java b/src/main/java/xueLi/craftGame/block/Block.java deleted file mode 100644 index 4caf8726..00000000 --- a/src/main/java/xueLi/craftGame/block/Block.java +++ /dev/null @@ -1,60 +0,0 @@ -package xueLi.craftGame.block; - -import java.nio.FloatBuffer; - -import xueLi.craftGame.block.data.*; -import xueLi.craftGame.entity.HitBox; - -import static xueLi.craftGame.block.BlockData.datas; - -public class Block { - - public static void init() { - datas.put(1, new BlockStone()); - datas.put(2, new BlockGrass()); - - } - - private BlockData data; - - public int dataValue = 0; - - public Block(int id) { - this.data = BlockData.getData(id); - } - - public Block(BlockData data) { - this.data = data; - } - - public Block(int id,int dataValue) { - this.data = BlockData.getData(id); - this.dataValue = dataValue; - } - - public Block(BlockData data,int dataValue) { - this.data = data; - this.dataValue = dataValue; - } - - public int getDrawData(FloatBuffer buffer,int x,int y,int z,int face) { - return this.data.render(buffer, x, y, z, dataValue, face); - } - - public String getName() { - return data.getName(); - } - - public int getID() { - return data.getId(); - } - - public HitBox getHitbox(int x, int y, int z) { - return data.getHitBoxWithPos(x,y,z); - } - - - - - -} diff --git a/src/main/java/xueLi/craftGame/block/BlockData.java b/src/main/java/xueLi/craftGame/block/BlockData.java deleted file mode 100644 index debe91eb..00000000 --- a/src/main/java/xueLi/craftGame/block/BlockData.java +++ /dev/null @@ -1,64 +0,0 @@ -package xueLi.craftGame.block; - -import java.nio.FloatBuffer; -import java.util.HashMap; - -import xueLi.craftGame.entity.HitBox; -import xueLi.craftGame.utils.BlockPos; - -public abstract class BlockData { - - public static HashMap datas = new HashMap(); - - public static BlockData getData(int id) { - return datas.get(id); - } - - private int id; - private String name; - - public BlockData(int id,String name) { - this.id = id; - this.name = name; - } - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - //čŋ™ä¸Ē是įĸ°æ’žįŽąį”¨įš„ - public static final HitBox defaultHitbox = new HitBox(0f, 0f, 0f, 1f, 1f, 1f); - - public abstract HitBox getHitbox(); - - public HitBox getHitBoxWithPos(BlockPos pos) { - HitBox box = getHitbox(); - box.x1 += pos.getX(); - box.x2 += pos.getX(); - box.y1 += pos.getY(); - box.y2 += pos.getY(); - box.z1 += pos.getZ(); - box.z2 += pos.getZ(); - return box; - } - - public HitBox getHitBoxWithPos(int x,int y,int z) { - HitBox box = getHitbox(); - box.x1 += x; - box.x2 += x; - box.y1 += y; - box.y2 += y; - box.z1 += z; - box.z2 += z; - return box; - } - - //æ¸˛æŸ“æ–šåŧ čŋ”回éĄļį‚šæ•°é‡ - public abstract int render(FloatBuffer buffer,int x,int y,int z,int dataValue,int face); - - -} diff --git a/src/main/java/xueLi/craftGame/block/BlockRenderMethod.java b/src/main/java/xueLi/craftGame/block/BlockRenderMethod.java deleted file mode 100644 index 656954af..00000000 --- a/src/main/java/xueLi/craftGame/block/BlockRenderMethod.java +++ /dev/null @@ -1,204 +0,0 @@ -package xueLi.craftGame.block; - -import java.nio.FloatBuffer; - -public class BlockRenderMethod { - - //ä¸ēæč´¨åŒ…åˇĨäŊœ - public static float TEXTURES_SIZE = 16; - - /** - * äģ…įģ˜åˆļ斚块åŊĸįŠļ ä¸ä¸Šæč´¨ - */ - public static void drawDefaultBlockToBuffer(FloatBuffer buffer, int x, int y, int z, int face) { - switch (face) { - case 0: - buffer.put(x).put(y).put(z); - buffer.put(x).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z); - buffer.put(x).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z); - buffer.put(x + 1).put(y + 1).put(z); - break; - case 1: - buffer.put(x + 1).put(y).put(z); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - break; - case 2: - buffer.put(x).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - break; - case 3: - buffer.put(x).put(y).put(z); - buffer.put(x).put(y + 1).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z + 1); - break; - case 4: - buffer.put(x).put(y + 1).put(z); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - break; - case 5: - buffer.put(x).put(y).put(z); - buffer.put(x + 1).put(y).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x + 1).put(y).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x + 1).put(y).put(z + 1); - break; - } - - } - - //įģ˜åˆļæ–šå—čžšæĄ† - public static void drawDefaultBlockFrame(FloatBuffer buffer, int x, int y, int z) { - buffer.put(x).put(y).put(z); - buffer.put(x).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z); - buffer.put(x + 1).put(y + 1).put(z); - - buffer.put(x + 1).put(y).put(z); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - - buffer.put(x).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - - buffer.put(x).put(y).put(z); - buffer.put(x).put(y + 1).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x).put(y + 1).put(z + 1); - - buffer.put(x).put(y + 1).put(z); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(x + 1).put(y + 1).put(z + 1); - - buffer.put(x).put(y).put(z); - buffer.put(x + 1).put(y).put(z); - buffer.put(x).put(y).put(z + 1); - buffer.put(x + 1).put(y).put(z + 1); - } - - // æ­¤å¤„įš„xīŧŒy是äģŽ0åŧ€å§‹æ•°įš„ 不äŧšæ”šīŧˆįŦ‘å“­īŧ‰ - public static int bindDefaultToBuffer(FloatBuffer buffer, int xInTexture, int yInTexture, int x, int y, int z, int face) { - float u1 = (float) xInTexture / TEXTURES_SIZE; - float v1 = (float) yInTexture / TEXTURES_SIZE; - float u2 = (float) (xInTexture + 1) / TEXTURES_SIZE; - float v2 = (float) (yInTexture + 1) / TEXTURES_SIZE; - - switch (face) { - case 0: - buffer.put(u1).put(v2); - // buffer.put(-1).put(-1).put(-1); - buffer.put(x).put(y).put(z); - buffer.put(u1).put(v1); - // buffer.put(-1).put(1).put(-1); - buffer.put(x).put(y + 1).put(z); - buffer.put(u2).put(v2); - // buffer.put(1).put(-1).put(-1); - buffer.put(x + 1).put(y).put(z); - buffer.put(u1).put(v1); - // buffer.put(-1).put(1).put(-1); - buffer.put(x).put(y + 1).put(z); - buffer.put(u2).put(v1); - // buffer.put(1).put(1).put(-1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(u2).put(v2); - // buffer.put(1).put(-1).put(-1); - buffer.put(x + 1).put(y).put(z); - break; - case 1: - buffer.put(u1).put(v2); - // buffer.put(1).put(-1).put(-1); - buffer.put(x + 1).put(y).put(z); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(u2).put(v2); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(u2).put(v1); - buffer.put(x + 1).put(y + 1).put(z + 1); - buffer.put(u2).put(v2); - buffer.put(x + 1).put(y).put(z + 1); - break; - case 2: - buffer.put(u1).put(v2); - buffer.put(x).put(y).put(z + 1); - buffer.put(u2).put(v2); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(u2).put(v2); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(u2).put(v1); - buffer.put(x + 1).put(y + 1).put(z + 1); - break; - case 3: - buffer.put(u1).put(v2); - buffer.put(x).put(y).put(z); - buffer.put(u2).put(v2); - buffer.put(x).put(y).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x).put(y + 1).put(z); - buffer.put(u1).put(v1); - buffer.put(x).put(y + 1).put(z); - buffer.put(u2).put(v2); - buffer.put(x).put(y).put(z + 1); - buffer.put(u2).put(v1); - buffer.put(x).put(y + 1).put(z + 1); - break; - case 4: - buffer.put(u1).put(v2); - buffer.put(x).put(y + 1).put(z); - buffer.put(u2).put(v2); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y + 1).put(z); - buffer.put(u2).put(v2); - buffer.put(x).put(y + 1).put(z + 1); - buffer.put(u2).put(v1); - buffer.put(x + 1).put(y + 1).put(z + 1); - break; - case 5: - buffer.put(u1).put(v2); - buffer.put(x).put(y).put(z); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y).put(z); - buffer.put(u2).put(v2); - buffer.put(x).put(y).put(z + 1); - buffer.put(u1).put(v1); - buffer.put(x + 1).put(y).put(z); - buffer.put(u2).put(v1); - buffer.put(x + 1).put(y).put(z + 1); - buffer.put(u2).put(v2); - buffer.put(x).put(y).put(z + 1); - break; - } - - return 6; - } - -} diff --git a/src/main/java/xueLi/craftGame/block/data/BlockGrass.java b/src/main/java/xueLi/craftGame/block/data/BlockGrass.java deleted file mode 100644 index 22d12081..00000000 --- a/src/main/java/xueLi/craftGame/block/data/BlockGrass.java +++ /dev/null @@ -1,32 +0,0 @@ -package xueLi.craftGame.block.data; - -import java.nio.FloatBuffer; - -import xueLi.craftGame.block.BlockData; -import xueLi.craftGame.block.BlockRenderMethod; -import xueLi.craftGame.entity.HitBox; - -public class BlockGrass extends BlockData { - - public BlockGrass() { - super(2,"Grass Block"); - - } - - @Override - public HitBox getHitbox() { - return defaultHitbox; - } - - @Override - public int render(FloatBuffer buffer, int x, int y, int z, int dataValue, int face) { - if (face < 4) - return BlockRenderMethod.bindDefaultToBuffer(buffer, 1, 0, x, y, z, face); - else if (face == 4) - return BlockRenderMethod.bindDefaultToBuffer(buffer, 0, 0, x, y, z, face); - else if (face == 5) - return BlockRenderMethod.bindDefaultToBuffer(buffer, 2, 0, x, y, z, face); - return 0; - } - -} diff --git a/src/main/java/xueLi/craftGame/block/data/BlockStone.java b/src/main/java/xueLi/craftGame/block/data/BlockStone.java deleted file mode 100644 index 09f8349c..00000000 --- a/src/main/java/xueLi/craftGame/block/data/BlockStone.java +++ /dev/null @@ -1,28 +0,0 @@ -package xueLi.craftGame.block.data; - -import java.nio.FloatBuffer; - -import xueLi.craftGame.block.BlockData; -import xueLi.craftGame.block.BlockRenderMethod; -import xueLi.craftGame.entity.HitBox; - -public class BlockStone extends BlockData { - - public BlockStone() { - super(1,"Stone"); - - } - - @Override - public HitBox getHitbox() { - return defaultHitbox; - } - - @Override - public int render(FloatBuffer buffer, int x, int y, int z, int dataValue, int face) { - return BlockRenderMethod.bindDefaultToBuffer(buffer, 3, 0, x, y, z, face); - } - - - -} diff --git a/src/main/java/xueLi/craftGame/database/Entities.java b/src/main/java/xueLi/craftGame/database/Entities.java deleted file mode 100644 index 4259a115..00000000 --- a/src/main/java/xueLi/craftGame/database/Entities.java +++ /dev/null @@ -1,29 +0,0 @@ -package xueLi.craftGame.database; - -import java.io.FileNotFoundException; - -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; - -import xueLi.craftGame.template.entity.AttributeEntity; -import xueLi.craftGame.utils.JsonReader; - -public class Entities { - - public static AttributeEntity mWarma; - - static { - try { - mWarma = JsonReader.readToEntityData("res/entities/Warma.json"); - - } catch (JsonSyntaxException e) { - e.printStackTrace(); - } catch (JsonIOException e) { - e.printStackTrace(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/Bone.java b/src/main/java/xueLi/craftGame/entity/Bone.java deleted file mode 100644 index b2b1e293..00000000 --- a/src/main/java/xueLi/craftGame/entity/Bone.java +++ /dev/null @@ -1,74 +0,0 @@ -package xueLi.craftGame.entity; - -import java.util.ArrayList; -import java.util.List; - -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; - -import xueLi.craftGame.entity.renderer.RenderArgs; -import xueLi.craftGame.utils.Vector; - -public class Bone { - - public int id; - public float[] vertices = new float[24]; - public float[] rotPoint; - public float[] rawOffset; - - public Matrix4f localMatrix = new Matrix4f(); - public Matrix4f matrix = new Matrix4f(); - - //public float[] - public List children = new ArrayList(); - - public float rotX = 0,rotY = 0,rotZ = 0; - - public List getDrawArgs(Vector pos){ - List args = new ArrayList(); - - RenderArgs a = new RenderArgs(); - a.matrix = matrix; - - float[] rawVertices = vertices; - a.vertices = new float[rawVertices.length]; - for(int m = 0;m < rawVertices.length;m++) { - a.vertices[m] = rawVertices[m]; - } - a.color = new Vector3f(1f / id,1f / id / 2,1f / id / 3); - args.add(a); - - for(Bone c:children) { - args.addAll(c.getDrawArgs(pos)); - } - return args; - } - - public void calculateMatrix(Matrix4f parentMatrix) { - localMatrix.setIdentity(); - - //Matrix4f transMatrix = new Matrix4f(); - //transMatrix.translate(new Vector3f(rawOffset[0],rawOffset[1],rawOffset[2])); - - Matrix4f rotMatrix = new Matrix4f(); - rotMatrix.setIdentity(); - rotX +=1; - rotMatrix.rotate((float)Math.toRadians(rotX), new Vector3f(1,0,0)); - rotMatrix.rotate((float)Math.toRadians(rotY), new Vector3f(0,1,0)); - rotMatrix.rotate((float)Math.toRadians(rotZ), new Vector3f(0,0,1)); - - Matrix4f transMatrix = new Matrix4f(); - transMatrix.setIdentity(); - transMatrix.translate(new Vector3f(rawOffset[0],rawOffset[1],rawOffset[2])); - - localMatrix = Matrix4f.mul(transMatrix, rotMatrix , null); - - matrix = Matrix4f.mul(parentMatrix, localMatrix, null); - - for(Bone c:children) - c.calculateMatrix(matrix); - } - - - -} diff --git a/src/main/java/xueLi/craftGame/entity/Entity.java b/src/main/java/xueLi/craftGame/entity/Entity.java deleted file mode 100644 index e4bc9d94..00000000 --- a/src/main/java/xueLi/craftGame/entity/Entity.java +++ /dev/null @@ -1,103 +0,0 @@ -package xueLi.craftGame.entity; - -import java.util.ArrayList; -import java.util.List; - -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; - -import xueLi.craftGame.entity.renderer.EntityRenderer; -import xueLi.craftGame.entity.renderer.RenderArgs; -import xueLi.craftGame.template.entity.AttributeEntity; -import xueLi.craftGame.utils.DisplayManager; -import xueLi.craftGame.utils.Vector; -import xueLi.craftGame.world.World; - -public abstract class Entity { - - public Vector pos; - public boolean isInLiquid = false; - - // For physical engine - // public boolean[] collide = new boolean[6]; - - // For entity bones - public AttributeEntity attrib; - - public Entity(float x, float y, float z) { - pos = new Vector(x, y, z); - - } - - public Entity(float x, float y, float z, float rotX, float rotY, float rotZ) { - pos = new Vector(x, y, z, rotX, rotY, rotZ); - - } - - public Vector3f force = new Vector3f(); - public Vector3f speed = new Vector3f(); - - // this is for real physical engine and I haven't done yet :) - // I think maybe I can figure out how to correct the position of an entity by - // the direction of the speed. - // But now one of the most important things is adding entities,so :} I think it - // will be done much later. - public void updatePos(World w) { - Vector3f deltaPos = new Vector3f(speed.x * DisplayManager.deltaTime, speed.y * DisplayManager.deltaTime, - speed.z * DisplayManager.deltaTime); - - /** - * The player will be stuck when collided with this code - */ - // HitBox box = getHitBox(); - // if(w.getHitBoxes(box.move(deltaPos.x + deltaPos.x > 0 ? 0.005f : -0.005f, - // deltaPos.y + deltaPos.y > 0 ? 0.005f : -0.005f, deltaPos.z + deltaPos.z > 0 ? - // 0.005f : -0.005f), w.wlimit_long).size() == 0) { - pos.x += deltaPos.x; - pos.y += deltaPos.y; - pos.z += deltaPos.z; - // pos.y = new - // BigDecimal(pos.y).setScale(2,BigDecimal.ROUND_HALF_UP).floatValue(); - // } - - /** - * So I can only write a false engine code like this - */ - //if (pos.y + this.getOriginHitBox().y1 < 5) - // pos.y = 5 - this.getOriginHitBox().y1; - - // Physical? No no no it will be much later :} - force.set(0, 0, 0); - // At least moving had become smoother :) - - } - - protected List defaultRender() { - List args = new ArrayList(); - - Matrix4f posMatrix = EntityRenderer.identity; - posMatrix.translate(new Vector3f(pos.x,pos.y,pos.z)); - - for(Bone b:attrib.bones) { - b.calculateMatrix(posMatrix); - args.addAll(b.getDrawArgs(pos)); - } - - posMatrix.setIdentity(); - - return args; - } - - public abstract List render(); - - public abstract void tick(World world); - - public abstract float getSpeed(); - - public abstract HitBox getOriginHitBox(); - - protected HitBox getHitBox() { - return getOriginHitBox().move(pos.x, pos.y, pos.z); - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/EntityType.java b/src/main/java/xueLi/craftGame/entity/EntityType.java deleted file mode 100644 index 30113ce0..00000000 --- a/src/main/java/xueLi/craftGame/entity/EntityType.java +++ /dev/null @@ -1,13 +0,0 @@ -package xueLi.craftGame.entity; - -public enum EntityType { - - PASSIVE(0), ENEMY(1), EASTEREGG(2333); - - public int id; - - EntityType(int id) { - this.id = id; - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/EntityWarma.java b/src/main/java/xueLi/craftGame/entity/EntityWarma.java deleted file mode 100644 index 0fdb9e3f..00000000 --- a/src/main/java/xueLi/craftGame/entity/EntityWarma.java +++ /dev/null @@ -1,37 +0,0 @@ -package xueLi.craftGame.entity; - -import java.util.List; - -import xueLi.craftGame.database.Entities; -import xueLi.craftGame.entity.renderer.RenderArgs; -import xueLi.craftGame.world.World; - -public class EntityWarma extends Entity { - - public EntityWarma(float x, float y, float z) { - super(x, y, z); - this.attrib = Entities.mWarma; - } - - @Override - public void tick(World world) { - - super.updatePos(world); - } - - @Override - public float getSpeed() { - return 0.001f; - } - - @Override - public HitBox getOriginHitBox() { - return null; - } - - @Override - public List render() { - return super.defaultRender(); - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/HitBox.java b/src/main/java/xueLi/craftGame/entity/HitBox.java deleted file mode 100644 index 362a34bd..00000000 --- a/src/main/java/xueLi/craftGame/entity/HitBox.java +++ /dev/null @@ -1,131 +0,0 @@ -package xueLi.craftGame.entity; - -import org.lwjgl.util.vector.Vector3f; - -public class HitBox { - - public float x1, y1, z1, x2, y2, z2; - - public HitBox(float x1, float y1, float z1, float x2, float y2, float z2) { - this.x1 = x1; - this.y1 = y1; - this.z1 = z1; - this.x2 = x2; - this.y2 = y2; - this.z2 = z2; - } - - public boolean isCollide(HitBox h2) { - if (isPointIn(x1, y1, z1)) - return true; - if (isPointIn(x1, y1, z2)) - return true; - if (isPointIn(x1, y2, z1)) - return true; - if (isPointIn(x1, y2, z2)) - return true; - if (isPointIn(x2, y1, z1)) - return true; - if (isPointIn(x2, y1, z2)) - return true; - if (isPointIn(x2, y2, z1)) - return true; - if (isPointIn(x2, y2, z2)) - return true; - return false; - } - - public boolean isPointIn(float x, float y, float z) { - return isPointIn(new Vector3f(x, y, z), this); - } - - public static boolean isPointIn(Vector3f point, HitBox box) { - if (point.x <= box.x1 || point.x >= box.x2) - return false; - if (point.y >= box.y2 || point.y <= box.y1) - return false; - if (point.z <= box.z1 || point.z >= box.z2) - return false; - return true; - } - - public HitBox move(float x, float y, float z) { - return new HitBox(x1 + x, y1 + y, z1 + z, x2 + x, y2 + y, z2 + z); - } - - public HitBox expand(float x, float y, float z) { - if (x < 0) - this.x1 += x; - else if (x > 0) - this.x2 += x; - if (y < 0) - this.y1 += y; - else if (y > 0) - this.y2 += y; - if (z < 0) - this.z1 += z; - else if (z > 0) - this.z2 += z; - return this; - } - - public float xCollide(HitBox a, float x) { - if (a.y2 <= this.y1 || a.y1 >= this.y2) - return x; - if (a.z2 <= this.z1 || a.z1 >= this.z2) - return x; - float max; - if (x > 0.0f && a.x2 <= this.x1) { - max = this.x1 - a.x2; - if (max < x) - x = max; - } else if (x < 0.0f && a.x1 >= this.x2) { - max = this.x2 - a.x1; - if (max > x) - x = max; - } - return x; - } - - public float yCollide(HitBox a, float y) { - if (a.x2 <= this.x1 || a.x1 >= this.x2) - return y; - if (a.z2 <= this.z1 || a.z1 >= this.z2) - return y; - float max; - if (y > 0.0f && a.y2 <= this.y1) { - max = this.y1 - a.y2; - if (max < y) - y = max; - } else if (y < 0.0f && a.y1 >= this.y2) { - max = this.y2 - a.y1; - if (max > y) - y = max; - } - return y; - } - - public float zCollide(HitBox a, float z) { - if (a.x2 <= this.x1 || a.x1 >= this.x2) - return z; - if (a.y2 <= this.y1 || a.y1 >= this.y2) - return z; - float max; - if (z > 0.0f && a.z2 <= this.z1) { - max = this.z1 - a.z2; - if (max < z) - z = max; - } else if (z < 0.0f && a.z1 >= this.z2) { - max = this.z2 - a.z1; - if (max > z) - z = max; - } - return z; - } - - @Override - public String toString() { - return x1 + "," + y1 + "," + z1 + " " + x2 + "," + y2 + "," + z2; - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/Player.java b/src/main/java/xueLi/craftGame/entity/Player.java deleted file mode 100644 index d9e06ecb..00000000 --- a/src/main/java/xueLi/craftGame/entity/Player.java +++ /dev/null @@ -1,145 +0,0 @@ -package xueLi.craftGame.entity; - -import java.util.List; - -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; - -import xueLi.craftGame.entity.renderer.RenderArgs; -import xueLi.craftGame.utils.BlockPos; -import xueLi.craftGame.utils.DisplayManager; -import xueLi.craftGame.utils.MousePicker; -import xueLi.craftGame.world.World; - -public class Player extends Entity { - - public int gamemode = 1; - public int health = 20; - - public BlockPos blockPointed; - - public float resistant = 0.009f; - public float sensivity = 0.1f; - - private final HitBox hitbox = new HitBox(-0.2f, -1.8f, -0.2f, 0.2f, 0.2f, 0.2f); - - public Player(float x, float y, float z) { - super(x, y, z); - this.attrib.box = hitbox; - } - - public Player(float x, float y, float z, float rotX, float rotY, float rotZ) { - super(x, y, z, rotX, rotY, rotZ); - } - - private static BlockPos block_select, last_block_select; - private static long placeTimeCount; - - @Override - public void tick(World world) { - if (speed.x > 0) { - speed.x -= resistant * speed.x * DisplayManager.deltaTime; - if (speed.x < 0) - speed.x = 0; - } else if (speed.x < 0) { - speed.x -= resistant * speed.x * DisplayManager.deltaTime; - if (speed.x > 0) - speed.x = 0; - } - - if (speed.y > 0) { - speed.y -= resistant * speed.y * 1.2f * DisplayManager.deltaTime; - if (speed.y < 0) - speed.y = 0; - } else if (speed.y < 0) { - speed.y -= resistant * speed.y * 1.2f * DisplayManager.deltaTime; - if (speed.y > 0) - speed.y = 0; - } - - if (speed.z > 0) { - speed.z -= resistant * speed.z * DisplayManager.deltaTime; - if (speed.z < 0) - speed.z = 0; - } else if (speed.z < 0) { - speed.z -= resistant * speed.z * DisplayManager.deltaTime; - if (speed.z > 0) - speed.z = 0; - } - - if (DisplayManager.isKeyDown(Keyboard.KEY_W)) { - if (DisplayManager.isKeyDown(Keyboard.KEY_R)) { - speed.x -= this.getSpeed() * 3f * (float) Math.sin(Math.toRadians(-pos.rotY)); - speed.z -= this.getSpeed() * 3f * (float) Math.cos(Math.toRadians(-pos.rotY)); - } else { - speed.x -= this.getSpeed() * (float) Math.sin(Math.toRadians(-pos.rotY)); - speed.z -= this.getSpeed() * (float) Math.cos(Math.toRadians(-pos.rotY)); - } - } - if (DisplayManager.isKeyDown(Keyboard.KEY_S)) { - speed.x += this.getSpeed() * (float) Math.sin(Math.toRadians(-pos.rotY)); - speed.z += this.getSpeed() * (float) Math.cos(Math.toRadians(-pos.rotY)); - } - if (DisplayManager.isKeyDown(Keyboard.KEY_A)) { - speed.x -= this.getSpeed() * (float) Math.sin(Math.toRadians(-pos.rotY + 90)); - speed.z -= this.getSpeed() * (float) Math.cos(Math.toRadians(-pos.rotY + 90)); - } - if (DisplayManager.isKeyDown(Keyboard.KEY_D)) { - speed.x -= this.getSpeed() * (float) Math.sin(Math.toRadians(-pos.rotY - 90)); - speed.z -= this.getSpeed() * (float) Math.cos(Math.toRadians(-pos.rotY - 90)); - } - - if (DisplayManager.isKeyDown(Keyboard.KEY_SPACE)) { - speed.y += this.getSpeed() * 0.9f; - } - - if (DisplayManager.isKeyDown(Keyboard.KEY_LSHIFT)) { - speed.y -= this.getSpeed() * 0.9f; - } - - pos.rotX -= Mouse.getDY() * sensivity; - pos.rotY += Mouse.getDX() * sensivity; - - super.updatePos(world); - - if (DisplayManager.isMouseDown(0) & block_select != null & DisplayManager.currentTime - placeTimeCount > 100) { - world.setBlock(block_select, null); - placeTimeCount = DisplayManager.currentTime; - } - - if (DisplayManager.isMouseDown(1) & block_select != null & DisplayManager.currentTime - placeTimeCount > 100) { - world.setBlock(last_block_select, 1); - placeTimeCount = DisplayManager.currentTime; - } - - } - - public void pickTick(World world) { - block_select = null; - MousePicker.ray(pos); - for (float distance = 0; distance < 8; distance += 0.05f) { - BlockPos searching_block_pos = MousePicker.getPointOnRay(distance); - if (world.hasBlock(searching_block_pos)) { - block_select = searching_block_pos; - break; - } - last_block_select = searching_block_pos; - } - } - - @Override - public List render() { - return null; - } - - @Override - public float getSpeed() { - return 0.002f; - } - - @Override - public HitBox getOriginHitBox() { - return hitbox; - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/renderer/EntityRenderer.java b/src/main/java/xueLi/craftGame/entity/renderer/EntityRenderer.java deleted file mode 100644 index 9635dfe2..00000000 --- a/src/main/java/xueLi/craftGame/entity/renderer/EntityRenderer.java +++ /dev/null @@ -1,96 +0,0 @@ -package xueLi.craftGame.entity.renderer; - -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; -import java.util.ArrayList; -import java.util.List; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL15; -import org.lwjgl.util.vector.Matrix4f; - -import xueLi.craftGame.entity.Entity; -import xueLi.craftGame.world.Chunk; - -public class EntityRenderer { - - public static Matrix4f identity = new Matrix4f(); - - private static final short[] ELEMENTS = { - 0,1,2, - 0,2,3, - - 3,2,6, - 3,6,7, - - 0,5,1, - 0,4,5, - - 4,6,5, - 4,7,6, - - 0,7,4, - 0,3,7, - - 1,5,6, - 1,6,2 - }; - public static final int ELEMENTS_COUNT = ELEMENTS.length; - - private static FloatBuffer vertBuffer = BufferUtils.createFloatBuffer(24); - private static ShortBuffer element = BufferUtils.createShortBuffer(ELEMENTS_COUNT); - private static List renderList = new ArrayList(); - - private static int vbo; - private static int ebo; - - public static void init() { - identity.setIdentity(); - - ebo = GL15.glGenBuffers(); - GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo); - element.put(ELEMENTS); - element.flip(); - GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, element, GL15.GL_STATIC_DRAW); - GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); - - vbo = GL15.glGenBuffers(); - } - - public static void buildMesh(Chunk chunk) { - for(Entity e : chunk.entities) { - renderList.addAll(e.render()); - } - } - - private static FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); - - public static void render() { - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); - for(RenderArgs arg:renderList) { - vertBuffer.put(arg.vertices); - vertBuffer.flip(); - - GL11.glMatrixMode(GL11.GL_MODELVIEW); - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuffer, GL15.GL_STATIC_DRAW); - GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0); - GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo); - GL11.glColor3f(arg.color.x,arg.color.y,arg.color.z); - - GL11.glPushMatrix(); - arg.matrix.store(matrixBuffer); - matrixBuffer.flip(); - GL11.glMultMatrix(matrixBuffer); - matrixBuffer.clear(); - - GL11.glDrawElements(GL11.GL_TRIANGLES,ELEMENTS_COUNT, GL11.GL_UNSIGNED_SHORT, 0); - GL11.glPopMatrix(); - - vertBuffer.clear(); - } - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); - renderList.clear(); - } - -} diff --git a/src/main/java/xueLi/craftGame/entity/renderer/RenderArgs.java b/src/main/java/xueLi/craftGame/entity/renderer/RenderArgs.java deleted file mode 100644 index e95a2d48..00000000 --- a/src/main/java/xueLi/craftGame/entity/renderer/RenderArgs.java +++ /dev/null @@ -1,12 +0,0 @@ -package xueLi.craftGame.entity.renderer; - -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; - -public class RenderArgs { - - public float[] vertices; - public Matrix4f matrix; - public Vector3f color; - -} diff --git a/src/main/java/xueLi/craftGame/gui/EnumGUIAlign.java b/src/main/java/xueLi/craftGame/gui/EnumGUIAlign.java deleted file mode 100644 index cb569890..00000000 --- a/src/main/java/xueLi/craftGame/gui/EnumGUIAlign.java +++ /dev/null @@ -1,12 +0,0 @@ -package xueLi.craftGame.gui; - -public enum EnumGUIAlign { - - //åŽŒå…¨åą…ä¸­ - center, - //äģ…xåą…ä¸­ - xcenter, - //äģ…yåą…ä¸­ - ycenter - -} diff --git a/src/main/java/xueLi/craftGame/gui/GUIRenderer.java b/src/main/java/xueLi/craftGame/gui/GUIRenderer.java deleted file mode 100644 index 2df1e83a..00000000 --- a/src/main/java/xueLi/craftGame/gui/GUIRenderer.java +++ /dev/null @@ -1,13 +0,0 @@ -package xueLi.craftGame.gui; - -public class GUIRenderer { - - public static void render(GUIScreen gui) { - - } - - - - - -} diff --git a/src/main/java/xueLi/craftGame/gui/GUIScreen.java b/src/main/java/xueLi/craftGame/gui/GUIScreen.java deleted file mode 100644 index f10861d7..00000000 --- a/src/main/java/xueLi/craftGame/gui/GUIScreen.java +++ /dev/null @@ -1,14 +0,0 @@ -package xueLi.craftGame.gui; - -import java.util.ArrayList; -import java.util.List; - -public class GUIScreen { - - private static List widgets = new ArrayList(); - - public GUIScreen(String path) { - - } - -} diff --git a/src/main/java/xueLi/craftGame/gui/GUIWidget.java b/src/main/java/xueLi/craftGame/gui/GUIWidget.java deleted file mode 100644 index b00db96b..00000000 --- a/src/main/java/xueLi/craftGame/gui/GUIWidget.java +++ /dev/null @@ -1,16 +0,0 @@ -package xueLi.craftGame.gui; - -import org.lwjgl.util.vector.Vector2f; -import org.lwjgl.util.vector.Vector4f; -import org.newdawn.slick.opengl.Texture; - -public class GUIWidget { - - public Vector2f pos; - public Vector2f size; - public Vector4f backgroundColor; - public Texture texture; - public String panel; - - -} diff --git a/src/main/java/xueLi/craftGame/nightmare/Subject.java b/src/main/java/xueLi/craftGame/nightmare/Subject.java deleted file mode 100644 index cfaa0608..00000000 --- a/src/main/java/xueLi/craftGame/nightmare/Subject.java +++ /dev/null @@ -1,12 +0,0 @@ -package xueLi.craftGame.nightmare; - -public enum Subject { - - CHINESE(100),MATH(1000),ENGLISH(50),PHYSICAL(500),CHEMISTRY(800),BIOLOGY(233); - - public int howHardItIs; - Subject(int howHardItIs){ - this.howHardItIs = howHardItIs; - } - -} diff --git a/src/main/java/xueLi/craftGame/physics/Force.java b/src/main/java/xueLi/craftGame/physics/Force.java deleted file mode 100644 index 375fd0b9..00000000 --- a/src/main/java/xueLi/craftGame/physics/Force.java +++ /dev/null @@ -1,15 +0,0 @@ -package xueLi.craftGame.physics; - -import xueLi.craftGame.utils.Vector; - -public class Force { - - public Vector pos; - public ForceType type; - - public Force(Vector pos, ForceType type) { - this.pos = pos; - this.type = type; - } - -} diff --git a/src/main/java/xueLi/craftGame/physics/ForceType.java b/src/main/java/xueLi/craftGame/physics/ForceType.java deleted file mode 100644 index e4a2c397..00000000 --- a/src/main/java/xueLi/craftGame/physics/ForceType.java +++ /dev/null @@ -1,8 +0,0 @@ -package xueLi.craftGame.physics; - -public enum ForceType { - - // ÖØÁĻŖŦÄϞÁÁĻŖŦŋÕÆø×čÁĻŖŦĩ¯ÁĻ - Gravity, Friction, Resistance, Elasticity - -} diff --git a/src/main/java/xueLi/craftGame/physics/Gravity.java b/src/main/java/xueLi/craftGame/physics/Gravity.java deleted file mode 100644 index 60ab1842..00000000 --- a/src/main/java/xueLi/craftGame/physics/Gravity.java +++ /dev/null @@ -1,7 +0,0 @@ -package xueLi.craftGame.physics; - -public class Gravity { - - public static final float g = 9.8f; - -} diff --git a/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelation.java b/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelation.java deleted file mode 100644 index 7ee51187..00000000 --- a/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelation.java +++ /dev/null @@ -1,16 +0,0 @@ -package xueLi.craftGame.template.bilibili; - -public class TUpperRelation { - - @SuppressWarnings("unused") - private int code; - - @SuppressWarnings("unused") - private int message; - - @SuppressWarnings("unused") - private int ttl; - - public TUpperRelationData data; - -} diff --git a/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelationData.java b/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelationData.java deleted file mode 100644 index f3d0e498..00000000 --- a/src/main/java/xueLi/craftGame/template/bilibili/TUpperRelationData.java +++ /dev/null @@ -1,19 +0,0 @@ -package xueLi.craftGame.template.bilibili; - -public class TUpperRelationData { - - @SuppressWarnings("unused") - private long mid; - - @SuppressWarnings("unused") - private long following; - - @SuppressWarnings("unused") - private long whisper; - - @SuppressWarnings("unused") - private long black; - - public long follower; - -} diff --git a/src/main/java/xueLi/craftGame/template/entity/AttributeEntity.java b/src/main/java/xueLi/craftGame/template/entity/AttributeEntity.java deleted file mode 100644 index a8426ce4..00000000 --- a/src/main/java/xueLi/craftGame/template/entity/AttributeEntity.java +++ /dev/null @@ -1,15 +0,0 @@ -package xueLi.craftGame.template.entity; - -import java.util.ArrayList; -import java.util.List; - -import xueLi.craftGame.entity.Bone; -import xueLi.craftGame.entity.HitBox; - -public class AttributeEntity { - - public String name; - public List bones = new ArrayList(); - public HitBox box; - -} diff --git a/src/main/java/xueLi/craftGame/template/entity/TBone.java b/src/main/java/xueLi/craftGame/template/entity/TBone.java deleted file mode 100644 index c10aa762..00000000 --- a/src/main/java/xueLi/craftGame/template/entity/TBone.java +++ /dev/null @@ -1,12 +0,0 @@ -package xueLi.craftGame.template.entity; - -public class TBone { - - public int id; - public int parent; - public float[] offset; - public float[] size; - public float[] uv; - public float[] rotPoint; - -} diff --git a/src/main/java/xueLi/craftGame/template/entity/TEntity.java b/src/main/java/xueLi/craftGame/template/entity/TEntity.java deleted file mode 100644 index 85be533c..00000000 --- a/src/main/java/xueLi/craftGame/template/entity/TEntity.java +++ /dev/null @@ -1,9 +0,0 @@ -package xueLi.craftGame.template.entity; - -public class TEntity { - - public String name; - public String type; - public TModel model; - -} diff --git a/src/main/java/xueLi/craftGame/template/entity/TModel.java b/src/main/java/xueLi/craftGame/template/entity/TModel.java deleted file mode 100644 index a8164883..00000000 --- a/src/main/java/xueLi/craftGame/template/entity/TModel.java +++ /dev/null @@ -1,8 +0,0 @@ -package xueLi.craftGame.template.entity; - -public class TModel { - - public float[] box; - public TBone[] bones; - -} diff --git a/src/main/java/xueLi/craftGame/template/gui/TGUI.java b/src/main/java/xueLi/craftGame/template/gui/TGUI.java deleted file mode 100644 index 7346575c..00000000 --- a/src/main/java/xueLi/craftGame/template/gui/TGUI.java +++ /dev/null @@ -1,8 +0,0 @@ -package xueLi.craftGame.template.gui; - -public class TGUI { - - public String type; - - -} diff --git a/src/main/java/xueLi/craftGame/template/gui/TGUIData.java b/src/main/java/xueLi/craftGame/template/gui/TGUIData.java deleted file mode 100644 index 2e71d2d4..00000000 --- a/src/main/java/xueLi/craftGame/template/gui/TGUIData.java +++ /dev/null @@ -1,9 +0,0 @@ -package xueLi.craftGame.template.gui; - -public class TGUIData { - - public String type; - public float[] size; - public String align; - -} diff --git a/src/main/java/xueLi/craftGame/utils/BilibiliAPI.java b/src/main/java/xueLi/craftGame/utils/BilibiliAPI.java deleted file mode 100644 index 0a48c9de..00000000 --- a/src/main/java/xueLi/craftGame/utils/BilibiliAPI.java +++ /dev/null @@ -1,32 +0,0 @@ -package xueLi.craftGame.utils; - -import java.io.IOException; - -public class BilibiliAPI { - - public static boolean realtimeGetFansRunning = true; - public static long follower; - - private static final Thread realtimeGetFans = new Thread(()-> { - while(realtimeGetFansRunning) { - try { - follower = JsonReader.getBilibiliUpperFollower(157262276); - System.out.println("åŽįš„į˛‰ä¸æ•°: " + follower); - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - - public static void startThreadOfRealtimeGetFans() { - realtimeGetFans.start(); - } - - public static void stopThreadOfRealtimeGetFans() { - realtimeGetFansRunning = false; - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/BlockPos.java b/src/main/java/xueLi/craftGame/utils/BlockPos.java deleted file mode 100644 index 2d9d8f48..00000000 --- a/src/main/java/xueLi/craftGame/utils/BlockPos.java +++ /dev/null @@ -1,34 +0,0 @@ -package xueLi.craftGame.utils; - -public class BlockPos { - - private int x, y, z; - - public BlockPos(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public int getZ() { - return z; - } - - @Override - public String toString() { - return x + "," + y + "," + z; - } - - public static Vector getBlockPosInChunk(Vector pos,ChunkPos cpos) { - return new Vector(pos.x - cpos.getX() * 16,pos.y,pos.z - cpos.getZ() * 16,pos.rotX,pos.rotY,pos.rotZ); - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/ChunkPos.java b/src/main/java/xueLi/craftGame/utils/ChunkPos.java deleted file mode 100644 index 8e83e3ce..00000000 --- a/src/main/java/xueLi/craftGame/utils/ChunkPos.java +++ /dev/null @@ -1,25 +0,0 @@ -package xueLi.craftGame.utils; - -public class ChunkPos { - - private int x, z; - - public ChunkPos(int x, int z) { - this.x = x; - this.z = z; - } - - public int getX() { - return x; - } - - public int getZ() { - return z; - } - - @Override - public String toString() { - return x + "," + z; - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/DisplayManager.java b/src/main/java/xueLi/craftGame/utils/DisplayManager.java deleted file mode 100644 index 9150d06e..00000000 --- a/src/main/java/xueLi/craftGame/utils/DisplayManager.java +++ /dev/null @@ -1,131 +0,0 @@ -package xueLi.craftGame.utils; - -import java.nio.FloatBuffer; - -import org.lwjgl.BufferUtils; -import org.lwjgl.LWJGLException; -import org.lwjgl.Sys; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.DisplayMode; -import org.lwjgl.opengl.GL11; - -import xueLi.craftGame.world.Chunk; -import xueLi.craftGame.world.World; - -public class DisplayManager { - - public static boolean isDebug = true; - - private static boolean isRunning = false; - private static int fps_cap = 60; - - public static int d_width, d_height; - - public static long currentTime; - public static long deltaTime; - - public static void create(int width, int height) { - try { - Display.setDisplayMode(new DisplayMode(width, height)); - Display.setResizable(true); - Display.setSwapInterval(1); - Display.setVSyncEnabled(true); - Display.setTitle("CraftGame"); - - Display.create(); - Display.makeCurrent(); - - Keyboard.create(); - Mouse.create(); - } catch (LWJGLException e) { - e.printStackTrace(); - } - isRunning = true; - - GL11.glViewport(0, 0, Display.getWidth(), Display.getHeight()); - GL11.glEnable(GL11.GL_TEXTURE_2D); - - GL11.glEnable(GL11.GL_DEPTH_TEST); - GL11.glDepthFunc(GL11.GL_ONE); - - GL11.glEnable(GL11.GL_CULL_FACE); - GL11.glCullFace(GL11.GL_BACK); - - FloatBuffer fogColor = BufferUtils.createFloatBuffer(4); - float[] fogColour = { 0.8f, 0.8f, 1.0f, 1.0f }; - fogColor.put(fogColour); - fogColor.flip(); - - GL11.glEnable(GL11.GL_FOG); - GL11.glFog(GL11.GL_FOG_COLOR, fogColor); - GL11.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR); - GL11.glFogf(GL11.GL_FOG_DENSITY, 0.003f); - GL11.glFogf(GL11.GL_FOG_START, World.chunkRenderDistance * Chunk.size * 0.56f); - GL11.glFogf(GL11.GL_FOG_END, World.chunkRenderDistance * Chunk.size * 1.90f); - GL11.glHint(GL11.GL_FOG_HINT, GL11.GL_DONT_CARE); - - //The website says these are the way to anti-aliasing - GL11.glEnable(GL11.GL_BLEND); - GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - GL11.glEnable(GL11.GL_POINT_SMOOTH); - GL11.glEnable(GL11.GL_LINE_SMOOTH); - GL11.glEnable(GL11.GL_POLYGON_SMOOTH); - - d_width = width; - d_height = height; - - } - - public static boolean tickResize() { - if (Display.wasResized()) { - d_width = Display.getWidth(); - d_height = Display.getHeight(); - GL11.glViewport(0, 0, d_width, d_height); - - return true; - } - return false; - } - - private static long getCurrentTime() { - return Sys.getTime() * 1000 / Sys.getTimerResolution(); - } - - public static void update() { - if (Display.isCloseRequested()) - isRunning = false; - Display.sync(fps_cap); - Display.update(); - - deltaTime = getCurrentTime() - currentTime; - } - - public static boolean isKeyDown(int i) { - return Keyboard.isKeyDown(i); - } - - public static boolean isMouseDown(int i) { - return Mouse.isButtonDown(i); - } - - public static boolean isKeyDown() { - return Keyboard.getEventKeyState(); - } - - public static boolean isRunning() { - currentTime = getCurrentTime(); - return isRunning; - } - - public static void postDestroyMessage() { - isRunning = false; - } - - public static void destroy() { - isRunning = false; - Display.destroy(); - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/FPSTimer.java b/src/main/java/xueLi/craftGame/utils/FPSTimer.java deleted file mode 100644 index 6c7448e6..00000000 --- a/src/main/java/xueLi/craftGame/utils/FPSTimer.java +++ /dev/null @@ -1,22 +0,0 @@ -package xueLi.craftGame.utils; - -public class FPSTimer { - - private static int counter = 0; - private static int fps = 0; - - private static long time1 = 0; - - public static int getFPS() { - counter++; - if (DisplayManager.currentTime - time1 >= 1000) { - fps = counter; - counter = 0; - time1 = DisplayManager.currentTime; - if (DisplayManager.isDebug) - System.out.println("FPS : " + fps); - } - return fps; - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/GLHelper.java b/src/main/java/xueLi/craftGame/utils/GLHelper.java deleted file mode 100644 index 31d0c396..00000000 --- a/src/main/java/xueLi/craftGame/utils/GLHelper.java +++ /dev/null @@ -1,256 +0,0 @@ -package xueLi.craftGame.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.FloatBuffer; - -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; -import org.lwjgl.opengl.GL13; -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; -import org.newdawn.slick.opengl.Texture; -import org.newdawn.slick.opengl.TextureLoader; - -import xueLi.craftGame.entity.Player; - -public class GLHelper { - - public static Matrix4f lastTimeProjMatrix, lastTimeViewMatrix; - private static FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); - public static float[][] frustumPlane = new float[6][4]; - - public static int registerTexture(String path) { - Texture t = null; - try { - t = TextureLoader.getTexture("png", new FileInputStream(new File(path))); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 0); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); - return t.getTextureID(); - } - - public static void deleteTexture(int id) { - GL11.glDeleteTextures(id); - } - - public static void perspecive(float width, float height, float fov, float near, float far) { - Matrix4f projectionMatrix = new Matrix4f(); - - float ratio = width / height; - float y_scale = (float) ((1f / Math.tan(Math.toRadians(fov / 2F))) * ratio); - float x_scale = y_scale / ratio; - float frustum_length = far - near; - - projectionMatrix.setIdentity(); - projectionMatrix.m00 = x_scale; - projectionMatrix.m11 = y_scale; - projectionMatrix.m22 = -(far + near) / frustum_length; - projectionMatrix.m23 = -1; - projectionMatrix.m32 = -((2 * far * near) / frustum_length); - projectionMatrix.m33 = 0; - - GL11.glMatrixMode(GL11.GL_PROJECTION); - matrixBuffer.clear(); - projectionMatrix.store(matrixBuffer); - matrixBuffer.flip(); - GL11.glLoadMatrix(matrixBuffer); - - lastTimeProjMatrix = projectionMatrix; - } - - public static void player(Player player) { - Vector camera = player.pos; - Matrix4f viewMatrix = new Matrix4f(); - viewMatrix.setIdentity(); - Matrix4f.rotate((float) Math.toRadians(camera.rotX), new Vector3f(1, 0, 0), viewMatrix, viewMatrix); - Matrix4f.rotate((float) Math.toRadians(camera.rotY), new Vector3f(0, 1, 0), viewMatrix, viewMatrix); - Matrix4f.rotate((float) Math.toRadians(camera.rotZ), new Vector3f(0, 0, 1), viewMatrix, viewMatrix); - Vector3f nagativeCamPos = new Vector3f(-camera.x, -camera.y, -camera.z); - Matrix4f.translate(nagativeCamPos, viewMatrix, viewMatrix); - - lastTimeViewMatrix = viewMatrix; - matrixBuffer.clear(); - viewMatrix.store(matrixBuffer); - matrixBuffer.flip(); - GL11.glMatrixMode(GL11.GL_MODELVIEW); - GL11.glLoadMatrix(matrixBuffer); - } - - public static Matrix4f createTransformationMatrix(Vector3f translation, float rx, float ry, float rz, float scale) { - Matrix4f matrix = new Matrix4f(); - matrix.setIdentity(); - Matrix4f.translate(translation, matrix, matrix); - Matrix4f.rotate((float) Math.toRadians(rx), new Vector3f(1, 0, 0), matrix, matrix); - Matrix4f.rotate((float) Math.toRadians(ry), new Vector3f(0, 1, 0), matrix, matrix); - Matrix4f.rotate((float) Math.toRadians(rz), new Vector3f(0, 0, 1), matrix, matrix); - Matrix4f.scale(new Vector3f(scale, scale, scale), matrix, matrix); - return matrix; - } - - public static void calculateFrustumPlane() { - Matrix4f matrix = Matrix4f.mul(lastTimeProjMatrix, lastTimeViewMatrix, null); - - double temp; - frustumPlane[0][0] = matrix.m03 - matrix.m00; - frustumPlane[0][1] = matrix.m13 - matrix.m10; - frustumPlane[0][2] = matrix.m23 - matrix.m20; - frustumPlane[0][3] = matrix.m33 - matrix.m30; - temp = Math.sqrt(frustumPlane[0][0] * frustumPlane[0][0] + frustumPlane[0][1] * frustumPlane[0][1] - + frustumPlane[0][2] * frustumPlane[0][2]); - frustumPlane[0][0] /= temp; - frustumPlane[0][1] /= temp; - frustumPlane[0][2] /= temp; - frustumPlane[0][3] /= temp; - - frustumPlane[1][0] = matrix.m03 + matrix.m00; - frustumPlane[1][1] = matrix.m13 + matrix.m10; - frustumPlane[1][2] = matrix.m23 + matrix.m20; - frustumPlane[1][3] = matrix.m33 + matrix.m30; - temp = Math.sqrt(frustumPlane[1][0] * frustumPlane[1][0] + frustumPlane[1][1] * frustumPlane[1][1] - + frustumPlane[1][2] * frustumPlane[1][2]); - frustumPlane[1][0] /= temp; - frustumPlane[1][1] /= temp; - frustumPlane[1][2] /= temp; - frustumPlane[1][3] /= temp; - - frustumPlane[2][0] = matrix.m03 + matrix.m01; - frustumPlane[2][1] = matrix.m13 + matrix.m11; - frustumPlane[2][2] = matrix.m23 + matrix.m21; - frustumPlane[2][3] = matrix.m33 + matrix.m31; - temp = Math.sqrt(frustumPlane[2][0] * frustumPlane[2][0] + frustumPlane[2][1] * frustumPlane[2][1] - + frustumPlane[2][2] * frustumPlane[2][2]); - frustumPlane[2][0] /= temp; - frustumPlane[2][1] /= temp; - frustumPlane[2][2] /= temp; - frustumPlane[2][3] /= temp; - - frustumPlane[3][0] = matrix.m03 - matrix.m01; - frustumPlane[3][1] = matrix.m13 - matrix.m11; - frustumPlane[3][2] = matrix.m23 - matrix.m21; - frustumPlane[3][3] = matrix.m33 - matrix.m31; - temp = Math.sqrt(frustumPlane[3][0] * frustumPlane[3][0] + frustumPlane[3][1] * frustumPlane[3][1] - + frustumPlane[3][2] * frustumPlane[3][2]); - frustumPlane[3][0] /= temp; - frustumPlane[3][1] /= temp; - frustumPlane[3][2] /= temp; - frustumPlane[3][3] /= temp; - - frustumPlane[4][0] = matrix.m03 - matrix.m02; - frustumPlane[4][1] = matrix.m13 - matrix.m12; - frustumPlane[4][2] = matrix.m23 - matrix.m22; - frustumPlane[4][3] = matrix.m33 - matrix.m32; - temp = Math.sqrt(frustumPlane[4][0] * frustumPlane[4][0] + frustumPlane[4][1] * frustumPlane[4][1] - + frustumPlane[4][2] * frustumPlane[4][2]); - frustumPlane[4][0] /= temp; - frustumPlane[4][1] /= temp; - frustumPlane[4][2] /= temp; - frustumPlane[4][3] /= temp; - - frustumPlane[5][0] = matrix.m03 + matrix.m02; - frustumPlane[5][1] = matrix.m13 + matrix.m12; - frustumPlane[5][2] = matrix.m23 + matrix.m22; - frustumPlane[5][3] = matrix.m33 + matrix.m32; - temp = Math.sqrt(frustumPlane[5][0] * frustumPlane[5][0] + frustumPlane[5][1] * frustumPlane[5][1] - + frustumPlane[5][2] * frustumPlane[5][2]); - frustumPlane[5][0] /= temp; - frustumPlane[5][1] /= temp; - frustumPlane[5][2] /= temp; - frustumPlane[5][3] /= temp; - } - - public static boolean isPointInFrustum(float x, float y, float z) { - for (int p = 0; p < 6; p++) { - if (frustumPlane[p][0] * x + frustumPlane[p][1] * y + frustumPlane[p][2] * z + frustumPlane[p][3] <= 0) - return false; - } - return true; - } - - public static boolean isBlockInFrustum(int x, int y, int z) { - for (int p = 0; p < 6; p++) { - if (frustumPlane[p][0] * x + frustumPlane[p][1] * y + frustumPlane[p][2] * z + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) + frustumPlane[p][1] * y + frustumPlane[p][2] * z + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x + frustumPlane[p][1] * (y + 1) + frustumPlane[p][2] * z + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) + frustumPlane[p][1] * (y + 1) + frustumPlane[p][2] * z - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x + frustumPlane[p][1] * y + frustumPlane[p][2] * (z + 1) + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) + frustumPlane[p][1] * y + frustumPlane[p][2] * (z + 1) - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x + frustumPlane[p][1] * (y + 1) + frustumPlane[p][2] * (z + 1) - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) + frustumPlane[p][1] * (y + 1) + frustumPlane[p][2] * (z + 1) - + frustumPlane[p][3] > 0) - continue; - return false; - } - return true; - } - - public static boolean isChunkInFrustum(int x, int heightMap, int z) { - for (int p = 0; p < 6; p++) { - if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * 0 + frustumPlane[p][2] * z * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * 0 + frustumPlane[p][2] * z * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * heightMap + frustumPlane[p][2] * z * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * heightMap + frustumPlane[p][2] * z * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * 0 + frustumPlane[p][2] * (z + 1) * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * 0 + frustumPlane[p][2] * (z + 1) * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * heightMap + frustumPlane[p][2] * (z + 1) * 16 - + frustumPlane[p][3] > 0) - continue; - if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * heightMap + frustumPlane[p][2] * (z + 1) * 16 - + frustumPlane[p][3] > 0) - continue; - return false; - } - return true; - } - - public static float doubleToFloat(double value) { - return new BigDecimal(String.valueOf(value)).floatValue(); - } - - public static double floatToDouble(float value) { - return new BigDecimal(String.valueOf(value)).doubleValue(); - } - - public static int floatToInt(float value) { - return new BigDecimal(String.valueOf(value)).setScale(2, BigDecimal.ROUND_DOWN).intValue(); - } - - public static long vert2ToLong(int x, int z) { - return (long) x & 4294967295L | ((long) z & 4294967295L) << 32; - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/JsonReader.java b/src/main/java/xueLi/craftGame/utils/JsonReader.java deleted file mode 100644 index 016d9db3..00000000 --- a/src/main/java/xueLi/craftGame/utils/JsonReader.java +++ /dev/null @@ -1,107 +0,0 @@ -package xueLi.craftGame.utils; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import com.google.gson.Gson; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; - -import xueLi.craftGame.entity.Bone; -import xueLi.craftGame.entity.HitBox; -import xueLi.craftGame.template.bilibili.TUpperRelation; -import xueLi.craftGame.template.entity.*; - -public class JsonReader { - - private static Gson gson = new Gson(); - - /** - * @throws JsonSyntaxException jsonæ ŧåŧé”™č¯¯ - * @throws JsonIOException json文äģļč¯ģå–é”™č¯¯ - * @throws FileNotFoundException æ˛Ąæ‰žåˆ°æ–‡äģļ - * @throws IllegalArgumentException jsonå‚æ•°é”™č¯¯ - */ - public static AttributeEntity readToEntityData(String path) - throws JsonSyntaxException, JsonIOException, FileNotFoundException { - TEntity raw = gson.fromJson(new FileReader(path), TEntity.class); - if (raw == null) { - System.err.println("Can't read entity: " + path); - return null; - } - - AttributeEntity attrib = new AttributeEntity(); - attrib.name = raw.name; - - TModel rawModel = raw.model; - attrib.box = new HitBox(rawModel.box[0], rawModel.box[1], rawModel.box[2], rawModel.box[3], rawModel.box[4], - rawModel.box[5]); - - TBone[] bones = rawModel.bones; - - for (int s = 0; s < bones.length; s++) { - TBone rawData = bones[s]; - Bone bone = new Bone(); - - bone.id = rawData.id; - - bone.vertices[0] = rawData.rotPoint[0]-rawData.size[0] / 2; - bone.vertices[1] = rawData.rotPoint[1]-rawData.size[1] / 2; - bone.vertices[2] = rawData.rotPoint[2]-rawData.size[2] / 2; - - bone.vertices[3] = rawData.rotPoint[0]-rawData.size[0] / 2; - bone.vertices[4] = rawData.rotPoint[1]+rawData.size[1] / 2; - bone.vertices[5] = rawData.rotPoint[2]-rawData.size[2] / 2; - - bone.vertices[6] = rawData.rotPoint[0]+rawData.size[0] / 2; - bone.vertices[7] = rawData.rotPoint[1]+rawData.size[1] / 2; - bone.vertices[8] = rawData.rotPoint[2]-rawData.size[2] / 2; - - bone.vertices[9] = rawData.rotPoint[0]+rawData.size[0] / 2; - bone.vertices[10] = rawData.rotPoint[1]-rawData.size[1] / 2; - bone.vertices[11] = rawData.rotPoint[2]-rawData.size[2] / 2; - - bone.vertices[12] = rawData.rotPoint[0]-rawData.size[0] / 2; - bone.vertices[13] = rawData.rotPoint[1]-rawData.size[1] / 2; - bone.vertices[14] = rawData.rotPoint[2]+rawData.size[2] / 2; - - bone.vertices[15] = rawData.rotPoint[0]-rawData.size[0] / 2; - bone.vertices[16] = rawData.rotPoint[1]+rawData.size[1] / 2; - bone.vertices[17] = rawData.rotPoint[2]+rawData.size[2] / 2; - - bone.vertices[18] = rawData.rotPoint[0]+rawData.size[0] / 2; - bone.vertices[19] = rawData.rotPoint[1]+rawData.size[1] / 2; - bone.vertices[20] = rawData.rotPoint[2]+rawData.size[2] / 2; - - bone.vertices[21] = rawData.rotPoint[0]+rawData.size[0] / 2; - bone.vertices[22] = rawData.rotPoint[1]-rawData.size[1] / 2; - bone.vertices[23] = rawData.rotPoint[2]+rawData.size[2] / 2; - - bone.rotPoint = rawData.rotPoint; - bone.rawOffset = rawData.offset; - - if (rawData.parent != -1) { - attrib.bones.get(rawData.parent).children.add(bone); - } else { - attrib.bones.add(bone); - } - - } - - return attrib; - } - - public static long getBilibiliUpperFollower(long uuid) throws IOException { - URL url = new URL("http://api.bilibili.com/x/relation/stat?vmid=" + uuid); - InputStream in = url.openStream(); - TUpperRelation r = gson.fromJson(new InputStreamReader(in), TUpperRelation.class); - in.close(); - return r.data.follower; - } - - - -} diff --git a/src/main/java/xueLi/craftGame/utils/MousePicker.java b/src/main/java/xueLi/craftGame/utils/MousePicker.java deleted file mode 100644 index 9213acde..00000000 --- a/src/main/java/xueLi/craftGame/utils/MousePicker.java +++ /dev/null @@ -1,36 +0,0 @@ -package xueLi.craftGame.utils; - -import org.lwjgl.util.vector.Matrix4f; -import org.lwjgl.util.vector.Vector3f; -import org.lwjgl.util.vector.Vector4f; - -public class MousePicker { - - private static Vector3f ray, camPos; - - public static void ray(Vector pos) { - camPos = new Vector3f(pos.x, pos.y, pos.z); - - // float MouseX = Mouse.getX(); - // float MouseY = Mouse.getY(); - // Vector2f normalizedMouseCoords = new Vector2f((2f * (MouseX)) / - // Display.getWidth() - 1f,(2f * (MouseY)) / Display.getHeight() - 1f); - Vector4f clipCoords = new Vector4f(0, 0, -1f, 1f); - - Matrix4f invertedProj = Matrix4f.invert(GLHelper.lastTimeProjMatrix, null); - Vector4f eyeCoords_origin = Matrix4f.transform(invertedProj, clipCoords, null); - Vector4f eyeCoords = new Vector4f(eyeCoords_origin.x, eyeCoords_origin.y, -1f, 0f); - Matrix4f invertedView = Matrix4f.invert(GLHelper.lastTimeViewMatrix, null); - Vector4f rayWorld = Matrix4f.transform(invertedView, eyeCoords, null); - ray = new Vector3f(rayWorld.x, rayWorld.y, rayWorld.z); - ray.normalise(); - } - - public static BlockPos getPointOnRay(float distance) { - Vector3f scaledRay = new Vector3f(ray.x * distance, ray.y * distance, ray.z * distance); - Vector3f rayEnd = Vector3f.add(camPos, scaledRay, null); - return new BlockPos(GLHelper.floatToInt(rayEnd.x), GLHelper.floatToInt(rayEnd.y), - GLHelper.floatToInt(rayEnd.z)); - } - -} diff --git a/src/main/java/xueLi/craftGame/utils/NoiseGenerator.java b/src/main/java/xueLi/craftGame/utils/NoiseGenerator.java deleted file mode 100644 index d841298c..00000000 --- a/src/main/java/xueLi/craftGame/utils/NoiseGenerator.java +++ /dev/null @@ -1,27 +0,0 @@ -package xueLi.craftGame.utils; - -import java.util.Random; - -import xueLi.craftGame.world.Chunk; - -public class NoiseGenerator { - - private Random r = new Random(); - - public NoiseGenerator(long seed) { - r.setSeed(seed); - } - - private float[][] genWhiteNoise(){ - float[][] noise = new float[Chunk.size][Chunk.size]; - for(int i = 0;i < Chunk.size;i++) - for(int j = 0;j < Chunk.size;j++) - noise[i][j] = r.nextFloat() % 1; - return noise; - } - - - - - -} diff --git a/src/main/java/xueLi/craftGame/utils/Vector.java b/src/main/java/xueLi/craftGame/utils/Vector.java deleted file mode 100644 index 704df4b9..00000000 --- a/src/main/java/xueLi/craftGame/utils/Vector.java +++ /dev/null @@ -1,28 +0,0 @@ -package xueLi.craftGame.utils; - -public class Vector { - - public float x, y, z; - public float rotX, rotY, rotZ; - - public Vector() { - x = y = z = rotX = rotY = rotZ = 0; - } - - public Vector(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - rotX = rotY = rotZ = 0; - } - - public Vector(float x, float y, float z, float rotX, float rotY, float rotZ) { - this.x = x; - this.y = y; - this.z = z; - this.rotX = rotX; - this.rotY = rotY; - this.rotZ = rotZ; - } - -} diff --git a/src/main/java/xueLi/craftGame/world/Chunk.java b/src/main/java/xueLi/craftGame/world/Chunk.java deleted file mode 100644 index b276a705..00000000 --- a/src/main/java/xueLi/craftGame/world/Chunk.java +++ /dev/null @@ -1,67 +0,0 @@ -package xueLi.craftGame.world; - -import java.util.HashSet; -import java.util.Set; - -import xueLi.craftGame.block.Block; -import xueLi.craftGame.entity.Entity; -import xueLi.craftGame.utils.BlockPos; - -public class Chunk { - - public static final int size = 16, height = 128; - Block[][][] blockState = new Block[size][height][size]; - public int[][] heightMap = new int[size][size]; - - public int chunkX, chunkZ; - - public Set entities = new HashSet(); - - public Chunk(int chunkX, int chunkZ) { - this.chunkX = chunkX; - this.chunkZ = chunkZ; - } - - public void update() { - - } - - public void setBlock(int x, int y, int z, Block block) { - if (x < 0 || x >= size || y < 0 || y >= height || z < 0 || z >= size) - return; - blockState[x][y][z] = block; - - if (y > heightMap[x][z]) { - if (block == null) { - for (int yy = y;; y--) { - if (this.getBlock(x, yy, z) != null) { - heightMap[x][z] = yy; - break; - } - } - } else - heightMap[x][z] = y; - } - } - - public Block getBlock(BlockPos pos) { - if (pos.getX() < 0 || pos.getX() >= size || pos.getY() < 0 || pos.getY() >= height || pos.getZ() < 0 - || pos.getZ() >= size) - return null; - return blockState[pos.getX()][pos.getY()][pos.getZ()]; - } - - public Block getBlock(int x, int y, int z) { - if (x < 0 || x >= size || y < 0 || y >= height || z < 0 || z >= size) - return null; - return blockState[x][y][z]; - } - - public boolean hasBlock(BlockPos pos) { - if (pos.getX() < 0 || pos.getX() >= size || pos.getY() < 0 || pos.getY() >= height || pos.getZ() < 0 - || pos.getZ() >= size) - return false; - return blockState[pos.getX()][pos.getY()][pos.getZ()] != null; - } - -} diff --git a/src/main/java/xueLi/craftGame/world/ChunkGenerator.java b/src/main/java/xueLi/craftGame/world/ChunkGenerator.java deleted file mode 100644 index 616c376c..00000000 --- a/src/main/java/xueLi/craftGame/world/ChunkGenerator.java +++ /dev/null @@ -1,44 +0,0 @@ -package xueLi.craftGame.world; - -import xueLi.craftGame.block.Block; - -public class ChunkGenerator { - - //į”Ÿæˆä¸€ä¸Ēčļ…åšŗåĻ 可äģĨ在Worldįąģįš„æž„é€ å™¨é‡Œéĸ扞到 - public static Chunk superflat(int chunkX, int chunkZ) { - Chunk chunk = new Chunk(chunkX,chunkZ); - for (int x = 0; x < Chunk.size; x++) { - for (int z = 0; z < Chunk.size; z++) { - for (int y = 0; y < 4; y++) { - chunk.blockState[x][y][z] = new Block(1); - - } - chunk.blockState[x][4][z] = new Block(2); - - chunk.heightMap[x][z] = 4; - } - } - return chunk; - } - - //čŋ™é‡Œæ˜¯ä¸–į•Œį”Ÿæˆæœ‰å…ŗįš„ äŊ†æ˛Ąæœ‰äŊŋᔍ - public static Chunk gen(int chunkX,int chunkZ) { - Chunk chunk = new Chunk(chunkX,chunkZ); - for (int x = 0; x < Chunk.size; x++) { - for (int z = 0; z < Chunk.size; z++) { - - for(int y = 0;y < Chunk.height;y++) - { - - } - - } - } - return chunk; - } - - - - - -} diff --git a/src/main/java/xueLi/craftGame/world/World.java b/src/main/java/xueLi/craftGame/world/World.java deleted file mode 100644 index e40a4d6e..00000000 --- a/src/main/java/xueLi/craftGame/world/World.java +++ /dev/null @@ -1,216 +0,0 @@ -package xueLi.craftGame.world; - -import java.math.BigDecimal; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import xueLi.craftGame.block.Block; -import xueLi.craftGame.entity.Entity; -import xueLi.craftGame.entity.HitBox; -import xueLi.craftGame.entity.Player; -import xueLi.craftGame.entity.renderer.EntityRenderer; -import xueLi.craftGame.utils.BlockPos; -import xueLi.craftGame.utils.ChunkPos; -import xueLi.craftGame.utils.GLHelper; - -public class World { - - private Map chunks = new HashMap(); - - private boolean isWorldLimited = false; - public int wlimit_long, wlimit_width; - private int limit_long, limit_width; - - //ä¸–į•Œå¤§å° 按åŒē块æĨįŽ— - public World(int limit_long, int limit_width) { - isWorldLimited = true; - this.wlimit_long = limit_long * Chunk.size; - this.wlimit_width = limit_width * Chunk.size; - this.limit_long = limit_long; - this.limit_width = limit_width; - - } - - //ä¸–į•Œį”Ÿæˆįš„æ–šæŗ• - public void generate() { - for (int x = 0; x < limit_long; x++) { - for (int z = 0; z < limit_width; z++) { - chunks.put(GLHelper.vert2ToLong(x, z), ChunkGenerator.superflat(x, z)); - } - } - } - - private static Chunk tempChunk; - public Block getBlock(int x, int y, int z) { - ChunkPos cp = getChunkPosFromBlock(x, z); - if (isWorldLimited) { - if (cp.getX() > this.limit_long - 1 || cp.getZ() > this.limit_width - 1 || cp.getX() < 0 || cp.getZ() < 0) - return null; - - int xInChunk = x - cp.getX() * Chunk.size; - int zInChunk = z - cp.getZ() * Chunk.size; - - if (tempChunk == null || cp.getX() != tempChunk.chunkX || cp.getZ() != tempChunk.chunkZ) - tempChunk = chunks.get(GLHelper.vert2ToLong(cp.getX(), cp.getZ())); - if (tempChunk == null) - return null; - return tempChunk.getBlock(xInChunk, y, zInChunk); - } - return null; - } - - - public void setBlock(int x, int y, int z, Block block) { - ChunkPos cp = getChunkPosFromBlock(x, z); - if (isWorldLimited) { - if (cp.getX() > this.limit_long - 1 || cp.getZ() > this.limit_width - 1 || cp.getX() < 0 || cp.getZ() < 0 - || y > Chunk.height - 1 || y < 0) - return; - - int xInChunk = x - cp.getX() * Chunk.size; - int zInChunk = z - cp.getZ() * Chunk.size; - chunks.get(GLHelper.vert2ToLong(cp.getX(), cp.getZ())).setBlock(xInChunk, y, zInChunk, block); - } - } - - //I accidently found that sometimes the game will throw NullPointerException on World.java:77 and I dont know why - public void setBlock(BlockPos p, Block b) { - if(p == null) - return; - setBlock(p.getX(), p.getY(), p.getZ(), b); - } - - public void setBlock(BlockPos p, int blockID) { - if(p == null) - return; - setBlock(p.getX(), p.getY(), p.getZ(), new Block(blockID)); - } - - public boolean hasBlock(BlockPos p) { - ChunkPos cp = getChunkPosFromBlock(p.getX(), p.getZ()); - if (isWorldLimited) { - if (cp.getX() > this.limit_long - 1 || cp.getZ() > this.limit_width - 1 || cp.getX() < 0 || cp.getZ() < 0) - return false; - - int xInChunk = p.getX() - cp.getX() * Chunk.size; - int zInChunk = p.getZ() - cp.getZ() * Chunk.size; - return chunks.get(GLHelper.vert2ToLong(cp.getX(), cp.getZ())) - .hasBlock(new BlockPos(xInChunk, p.getY(), zInChunk)); - } - return false; - } - - public static int chunkRenderDistance = 5; - - public int draw(Player cam, FloatBuffer buffer) { - int vertCount = 0; - int camX = (int) cam.pos.x; - int camZ = (int) cam.pos.z; - ChunkPos chunkPos = this.getChunkPosFromBlock(camX, camZ); - for (int chunkX = chunkPos.getX() - chunkRenderDistance; chunkX < chunkPos.getX() - + chunkRenderDistance; chunkX++) { - for (int chunkZ = chunkPos.getZ() - chunkRenderDistance; chunkZ < chunkPos.getZ() - + chunkRenderDistance; chunkZ++) { - Chunk c = this.chunks.get(GLHelper.vert2ToLong(chunkX, chunkZ)); - if (c == null) - continue; - c.update(); - if (!GLHelper.isChunkInFrustum(chunkX, Chunk.height, chunkZ)) - continue; - EntityRenderer.buildMesh(c); - for (int xInChunk = 0; xInChunk < Chunk.size; xInChunk++) { - for (int zInChunk = 0; zInChunk < Chunk.size; zInChunk++) { - int yMax = c.heightMap[xInChunk][zInChunk]; - for (int y = 0; y <= yMax; y++) { - int x = chunkX * Chunk.size + xInChunk; - int z = chunkZ * Chunk.size + zInChunk; - Block block = c.getBlock(xInChunk, y, zInChunk); - if (block == null) - continue; - if (c.getBlock(xInChunk, y - 1, zInChunk) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 5); - } - if (c.getBlock(xInChunk, y + 1, zInChunk) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 4); - } - if (xInChunk - 1 < 0 ? this.getBlock(x - 1, y, z) == null - : c.getBlock(xInChunk - 1, y, zInChunk) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 3); - } - if (xInChunk + 1 >= Chunk.size ? this.getBlock(x + 1, y, z) == null - : c.getBlock(xInChunk + 1, y, zInChunk) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 1); - } - if (zInChunk - 1 < 0 ? this.getBlock(x, y, z - 1) == null - : c.getBlock(xInChunk, y, zInChunk - 1) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 0); - } - if (zInChunk + 1 >= Chunk.size ? this.getBlock(x, y, z + 1) == null - : c.getBlock(xInChunk, y, zInChunk + 1) == null) { - vertCount += block.getDrawData(buffer, x, y, z, 2); - } - - - } - } - } - } - } - - return vertCount; - } - - public void addEntity(Entity entity) { - ChunkPos chunkPos = getChunkPosFromBlock(entity.pos.x,entity.pos.z); - chunks.get(GLHelper.vert2ToLong(chunkPos.getX(), chunkPos.getZ())).entities.add(entity); - } - - public ArrayList getHitBoxes(HitBox box, int worldMaxSize) { - int x1 = new BigDecimal(String.valueOf(box.x1)).setScale(0, BigDecimal.ROUND_DOWN).intValue(); - int x2 = new BigDecimal(String.valueOf(box.x2 + 1.0f)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); - int y1 = new BigDecimal(String.valueOf(box.y1)).setScale(0, BigDecimal.ROUND_DOWN).intValue(); - int y2 = new BigDecimal(String.valueOf(box.y2 + 1.0f)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); - int z1 = new BigDecimal(String.valueOf(box.z1)).setScale(0, BigDecimal.ROUND_DOWN).intValue(); - int z2 = new BigDecimal(String.valueOf(box.z2 + 1.0f)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); - - if (x1 < 0) - x1 = 0; - if (y1 < 0) - y1 = 0; - if (z1 < 0) - z1 = 0; - if (x2 > worldMaxSize) - x2 = worldMaxSize - 1; - if (y2 > Chunk.height) - y2 = Chunk.height - 1; - if (z2 > worldMaxSize) - z1 = worldMaxSize - 1; - - ArrayList boxes = new ArrayList(); - for (int x = x1; x < x2; x++) { - for (int y = y1; y < y2; y++) { - for (int z = z1; z < z2; z++) { - Block block = this.getBlock(x, y, z); - if (block == null) - continue; - boxes.add(block.getHitbox(x, y, z)); - } - } - } - return boxes; - } - - private ChunkPos getChunkPosFromBlock(int x, int z) { - int chunkX = x / 16; - int chunkZ = z / 16; - return new ChunkPos(chunkX - (x < 0 ? 1 : 0), chunkZ - (z < 0 ? 1 : 0)); - } - - private ChunkPos getChunkPosFromBlock(float x, float z) { - int chunkX = (int) (x / 16); - int chunkZ = (int) (z / 16); - return new ChunkPos(chunkX - (x < 0 ? 1 : 0), chunkZ - (z < 0 ? 1 : 0)); - } - -} diff --git a/src/main/java/xueLi/craftGame/world/WorldRenderer.java b/src/main/java/xueLi/craftGame/world/WorldRenderer.java deleted file mode 100644 index f5600a44..00000000 --- a/src/main/java/xueLi/craftGame/world/WorldRenderer.java +++ /dev/null @@ -1,70 +0,0 @@ -package xueLi.craftGame.world; - -import java.nio.FloatBuffer; - -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL13; -import org.lwjgl.util.glu.GLU; - -import xueLi.craftGame.entity.Player; -import xueLi.craftGame.utils.DisplayManager; -import xueLi.craftGame.utils.GLHelper; - -public class WorldRenderer { - - private static int texture; - private static FloatBuffer buffer; - - private static World w = new World(8,8); - - private static Player player = new Player(8, 8, 8, 0, 0, 0); - - public static void init() { - texture = GLHelper.registerTexture("res/textures.png"); - WorldVertexBinder.init(); - - //上éĸ邪䏤ä¸Ēå‡Ŋæ•°æ˜¯čˇŸOpenGLæœ‰å…ŗįš„ - w.generate(); - - } - - public static void render() { - GL11.glClearColor(0.5f, 0.8f, 1.0f, 1.0f); - - player.tick(w); - buffer = WorldVertexBinder.map(); - int v = w.draw(player, buffer); - - buffer.flip(); - - GL11.glMatrixMode(GL11.GL_MODELVIEW); - GL13.glActiveTexture(GL13.GL_TEXTURE0); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); - //GL11.glColor3f(1, 1, 1); - WorldVertexBinder.draw(GL11.GL_TRIANGLES, v); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); - - buffer.clear(); - - //EntityRenderer.render(); - - int error = GL11.glGetError(); - if (error != 0) { - System.out.println(GLU.gluErrorString(error)); - } - - if (DisplayManager.tickResize()) { - GLHelper.perspecive(DisplayManager.d_width, DisplayManager.d_height, 90.0f, 0.1f, 1000.0f); - } - GLHelper.player(player); - GLHelper.calculateFrustumPlane(); - - player.pickTick(w); - } - - public static void release() { - GLHelper.deleteTexture(texture); - - } - -} diff --git a/src/main/java/xueLi/craftGame/world/WorldVertexBinder.java b/src/main/java/xueLi/craftGame/world/WorldVertexBinder.java deleted file mode 100644 index 902a8452..00000000 --- a/src/main/java/xueLi/craftGame/world/WorldVertexBinder.java +++ /dev/null @@ -1,31 +0,0 @@ -package xueLi.craftGame.world; - -import java.nio.FloatBuffer; - -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL15; - -public class WorldVertexBinder { - - private static int vbo; - - public static void init() { - vbo = GL15.glGenBuffers(); - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, 1073741824, GL15.GL_DYNAMIC_DRAW); - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - } - - public static FloatBuffer map() { - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); - return GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.GL_WRITE_ONLY, null).asFloatBuffer(); - } - - public static void draw(int type, int vertex_count) { - GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); - GL11.glInterleavedArrays(GL11.GL_T2F_V3F, 0, 0); - GL11.glDrawArrays(type, 0, vertex_count); - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); - } - -} diff --git a/src/main/java/xueli/animation/AnimationBinding.java b/src/main/java/xueli/animation/AnimationBinding.java new file mode 100644 index 00000000..7c511f2c --- /dev/null +++ b/src/main/java/xueli/animation/AnimationBinding.java @@ -0,0 +1,29 @@ +package xueli.animation; + +public interface AnimationBinding { + + public void animStart(); + + default public void animStartFromEnd() { + this.animStart(); + } + + public void animProgress(double timeProgress); + + public void animEnd(); + + public static AnimationBinding EMPTY = new AnimationBinding() { + @Override + public void animStart() { + } + + @Override + public void animProgress(double timeProgress) { + } + + @Override + public void animEnd() { + } + }; + +} diff --git a/src/main/java/xueli/animation/AnimationBindingBuilder.java b/src/main/java/xueli/animation/AnimationBindingBuilder.java new file mode 100644 index 00000000..d211fbda --- /dev/null +++ b/src/main/java/xueli/animation/AnimationBindingBuilder.java @@ -0,0 +1,106 @@ +package xueli.animation; + +import java.util.ArrayList; + +public class AnimationBindingBuilder { + + private ArrayList bindings = new ArrayList<>(); + + private double lastPointTime = 0; + private ArrayList separatePoints = new ArrayList<>(); + + { + separatePoints.add(0.0); + } + + private AnimationBindingBuilder() { + } + + public AnimationBindingBuilder add(AnimationBinding binding, double part) { + binding = binding == null ? AnimationBinding.EMPTY : binding; + bindings.add(binding); + this.lastPointTime += part; + separatePoints.add(this.lastPointTime); + return this; + } + + public AnimationBinding build() { + if (bindings.isEmpty()) + return AnimationBinding.EMPTY; + + double allCount = lastPointTime; + + return new AnimationBinding() { + AnimationBinding current; + int currentAnimIndex; + double currentBindingStart, currentBindingEnd, currentBindingDuration; + double lastTimeProgress; + + @Override + public void animStart() { + currentBindingStart = 0; + currentBindingEnd = 0; + currentBindingDuration = 0; + + currentAnimIndex = 0; + refreshAnim(); + lastTimeProgress = 0; + + current.animStart(); + + } + + @Override + public void animProgress(double timeProgress) { + double relativeTimeProgress = timeProgress * allCount; + + if (relativeTimeProgress > lastTimeProgress) { + while (relativeTimeProgress > currentBindingEnd) { + current.animEnd(); + + currentAnimIndex++; + refreshAnim(); + + current.animStart(); + + } + + } else if (relativeTimeProgress < lastTimeProgress) { + while (relativeTimeProgress < currentBindingStart) { + current.animStart(); + + currentAnimIndex--; + refreshAnim(); + + current.animStartFromEnd(); + + } + + } + + current.animProgress((relativeTimeProgress - currentBindingStart) / currentBindingDuration); + lastTimeProgress = relativeTimeProgress; + + } + + private void refreshAnim() { + current = bindings.get(currentAnimIndex = 0); + currentBindingStart = separatePoints.get(currentAnimIndex); + currentBindingEnd = separatePoints.get(currentAnimIndex + 1); + currentBindingDuration = currentBindingEnd - currentBindingStart; + + } + + @Override + public void animEnd() { + current.animEnd(); + + } + }; + } + + public static AnimationBindingBuilder newBuilder() { + return new AnimationBindingBuilder(); + } + +} diff --git a/src/main/java/xueli/animation/AnimationEndListener.java b/src/main/java/xueli/animation/AnimationEndListener.java new file mode 100644 index 00000000..b1b426c1 --- /dev/null +++ b/src/main/java/xueli/animation/AnimationEndListener.java @@ -0,0 +1,7 @@ +package xueli.animation; + +public interface AnimationEndListener { + + public void onAnimationEnd(); + +} diff --git a/src/main/java/xueli/animation/AnimationInstance.java b/src/main/java/xueli/animation/AnimationInstance.java new file mode 100644 index 00000000..5823a600 --- /dev/null +++ b/src/main/java/xueli/animation/AnimationInstance.java @@ -0,0 +1,15 @@ +package xueli.animation; + +public interface AnimationInstance { + + public void pause(); + + public void stop(double progress); + + public double getProgress(); + + public long getDuration(); + + public void addAnimationEndListener(AnimationEndListener listener); + +} diff --git a/src/main/java/xueli/animation/AnimationInstanceImpl.java b/src/main/java/xueli/animation/AnimationInstanceImpl.java new file mode 100644 index 00000000..8f1762de --- /dev/null +++ b/src/main/java/xueli/animation/AnimationInstanceImpl.java @@ -0,0 +1,93 @@ +package xueli.animation; + +import java.util.ArrayList; + +class AnimationInstanceImpl implements AnimationInstance { + + private final AnimationBinding binding; + private final Curve curve; + private final long startTime, duration, endTime; + private final boolean reverse; + + // Progress from the time, so its change is linear + private double progress = 0.0; + private AnimationStage stage = AnimationStage.PLAYING; + + private final ArrayList endListeners = new ArrayList<>(); + + AnimationInstanceImpl(AnimationBinding binding, long startTime, long duration, Curve curve, boolean reverse) { + this.binding = binding; + this.startTime = startTime; + this.duration = duration; + this.curve = curve; + this.endTime = this.startTime + this.duration; + + this.reverse = reverse; + + this.binding.animStart(); + this.progress = 0.0; + + } + + /** + * @return Whether the animation needs ticking + */ + boolean tick(long currentTime) { + if (currentTime >= endTime) { + this.stage = AnimationStage.STOP; + this.progress = 1.0; + this.binding.animEnd(); + + endListeners.forEach(AnimationEndListener::onAnimationEnd); + + return false; + } + + switch (this.stage) { + case PLAYING -> { + this.progress = (double) (currentTime - startTime) / duration; + double curveValue = curve.getValue(progress); + if (reverse) + this.binding.animProgress(1 - curveValue); + else + this.binding.animProgress(curveValue); + } + case PAUSE -> { + } + case STOP -> { + return false; + } + } + + return true; + } + + @Override + public void pause() { + this.stage = AnimationStage.PAUSE; + + } + + @Override + public void stop(double progress) { + this.stage = AnimationStage.STOP; + this.binding.animProgress(curve.getValue(progress)); + + } + + @Override + public double getProgress() { + return this.progress; + } + + @Override + public long getDuration() { + return this.duration; + } + + @Override + public void addAnimationEndListener(AnimationEndListener listener) { + endListeners.add(listener); + } + +} diff --git a/src/main/java/xueli/animation/AnimationManager.java b/src/main/java/xueli/animation/AnimationManager.java new file mode 100644 index 00000000..21121a5a --- /dev/null +++ b/src/main/java/xueli/animation/AnimationManager.java @@ -0,0 +1,55 @@ +package xueli.animation; + +import java.util.ArrayList; +import java.util.function.Supplier; + +public class AnimationManager { + + private final Supplier timeProvider; + private final ArrayList animationInstances = new ArrayList<>(); + + public AnimationManager(Supplier timeProvider) { + this.timeProvider = timeProvider; + } + + public AnimationInstance start(AnimationBinding binding, Curve curve, long duration) { + AnimationInstanceImpl inst = new AnimationInstanceImpl(binding, timeProvider.get(), duration, curve, false); + animationInstances.add(inst); + return inst; + } + + public AnimationInstance startReverse(AnimationBinding binding, Curve curve, long duration) { + AnimationInstanceImpl inst = new AnimationInstanceImpl(binding, timeProvider.get(), duration, curve, true); + animationInstances.add(inst); + return inst; + } + + public AnimationInstance startReverse(AnimationBinding binding, Curve curve, AnimationInstance previous) { + return this.startReverse(binding, curve, previous.getDuration(), previous); + } + + public AnimationInstance startReverse(AnimationBinding binding, Curve curve, long duration, + AnimationInstance previous) { + long simulateStartTime = timeProvider.get(); + if (previous != null) { + double lastProgress = previous.getProgress(); + int shouldHaveGoneTime = (int) ((1 - lastProgress) * duration); + simulateStartTime -= shouldHaveGoneTime; + } + AnimationInstanceImpl inst = new AnimationInstanceImpl(binding, simulateStartTime, duration, curve, true); + animationInstances.add(inst); + return inst; + } + + public void tick() { + var iter = animationInstances.iterator(); + while (iter.hasNext()) { + AnimationInstanceImpl inst = iter.next(); + if (!inst.tick(timeProvider.get())) { + iter.remove(); + } + } + + } + +} diff --git a/src/main/java/xueli/animation/AnimationStage.java b/src/main/java/xueli/animation/AnimationStage.java new file mode 100644 index 00000000..28a465be --- /dev/null +++ b/src/main/java/xueli/animation/AnimationStage.java @@ -0,0 +1,7 @@ +package xueli.animation; + +public enum AnimationStage { + + PLAYING, PAUSE, STOP; + +} diff --git a/src/main/java/xueli/animation/Curve.java b/src/main/java/xueli/animation/Curve.java new file mode 100644 index 00000000..887272c1 --- /dev/null +++ b/src/main/java/xueli/animation/Curve.java @@ -0,0 +1,7 @@ +package xueli.animation; + +public interface Curve { + + public double getValue(double progress); + +} diff --git a/src/main/java/xueli/animation/CurveAnimationBinding.java b/src/main/java/xueli/animation/CurveAnimationBinding.java new file mode 100644 index 00000000..03f93695 --- /dev/null +++ b/src/main/java/xueli/animation/CurveAnimationBinding.java @@ -0,0 +1,18 @@ +package xueli.animation; + +public abstract class CurveAnimationBinding implements AnimationBinding { + + private final Curve curve; + + public CurveAnimationBinding(Curve curve) { + this.curve = curve; + } + + @Override + public void animProgress(double timeProgress) { + this.animRealProgress(this.curve.getValue(timeProgress)); + } + + protected abstract void animRealProgress(double progress); + +} diff --git a/src/main/java/xueli/animation/Curves.java b/src/main/java/xueli/animation/Curves.java new file mode 100644 index 00000000..f6e0f5a9 --- /dev/null +++ b/src/main/java/xueli/animation/Curves.java @@ -0,0 +1,116 @@ +package xueli.animation; + +/** + * All the internal curves. Actually this is a clone of "https://easings.net/", + * so we have the same names + */ +public class Curves { + + public final static Curve linear = x -> x; + + public final static Curve easeInSine = x -> 1 - Math.cos((x * Math.PI) / 2); + + public final static Curve easeOutSine = x -> Math.sin((x * Math.PI) / 2); + + public final static Curve easeInOutSine = x -> -(Math.cos(Math.PI * x) - 1) / 2; + + public final static Curve easeInQuad = x -> x * x; + + public final static Curve easeOutQuad = x -> 1 - (1 - x) * (1 - x); + + public final static Curve easeInOutQuad = x -> x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + + public final static Curve easeInCubic = x -> x * x * x; + + public final static Curve easeOutCubic = x -> 1 - Math.pow(1 - x, 3); + + public final static Curve easeInOutCubic = x -> x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + + public final static Curve easeInQuart = x -> x * x * x * x; + + public final static Curve easeOutQuart = x -> 1 - Math.pow(1 - x, 4); + + public final static Curve easeInOutQuart = x -> x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; + + public final static Curve easeInQuint = x -> x * x * x * x * x; + + public final static Curve easeOutQuint = x -> 1 - Math.pow(1 - x, 5); + + public final static Curve easeInOutQuint = x -> x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; + + public final static Curve easeInExpo = x -> x == 0 ? 0 : Math.pow(2, 10 * x - 10); + + public final static Curve easeOutExpo = x -> x == 1 ? 1 : 1 - Math.pow(2, -10 * x); + + public final static Curve easeInOutExpo = x -> x == 0 ? 0 + : x == 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2; + + public final static Curve easeInCirc = x -> 1 - Math.sqrt(1 - Math.pow(x, 2)); + + public final static Curve easeOutCirc = x -> Math.sqrt(1 - Math.pow(x - 1, 2)); + + public final static Curve easeInOutCirc = x -> x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; + + public final static Curve easeInBack = x -> { + double c1 = 1.70158; + double c3 = c1 + 1; + return c3 * x * x * x - c1 * x * x; + }; + + public final static Curve easeOutBack = x -> { + double c1 = 1.70158; + double c3 = c1 + 1; + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }; + + public final static Curve easeInOutBack = x -> { + double c1 = 1.70158; + double c2 = c1 * 1.525; + return x < 0.5 ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 + : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + }; + + public final static Curve easeInElastic = x -> { + double c4 = (2 * Math.PI) / 3; + return x == 0 ? 0 : x == 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); + }; + + public final static Curve easeOutElastic = x -> { + double c4 = (2 * Math.PI) / 3; + return x == 0 ? 0 : x == 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; + }; + + public final static Curve easeInOutElastic = x -> { + double c5 = (2 * Math.PI) / 4.5; + return x == 0 ? 0 + : x == 1 ? 1 + : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; + }; + + public final static Curve easeOutBounce = x -> { + double n1 = 7.5625; + double d1 = 2.75; + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + }; + + public final static Curve easeInBounce = x -> 1 - easeOutBounce.getValue(1 - x); + + public final static Curve easeInOutBounce = x -> x < 0.5 ? (1 - easeOutBounce.getValue(1 - 2 * x)) / 2 + : (1 + easeOutBounce.getValue(2 * x - 1)) / 2; + + public final static Curve easeOutExtreme = x -> { + double exp = Math.exp(6 * x + 1); + return (exp - 1) / (exp + 1); + }; + +} diff --git a/src/main/java/xueli/animation/DoubleValueAnimationBinding.java b/src/main/java/xueli/animation/DoubleValueAnimationBinding.java new file mode 100644 index 00000000..2246fd8c --- /dev/null +++ b/src/main/java/xueli/animation/DoubleValueAnimationBinding.java @@ -0,0 +1,45 @@ +package xueli.animation; + +import java.util.function.Supplier; + +public abstract class DoubleValueAnimationBinding implements AnimationBinding { + + private final Supplier startValueSupplier, endValueSupplier; + private final boolean stay; + + public DoubleValueAnimationBinding(Supplier startValue, Supplier endValue, boolean stay) { + this.startValueSupplier = startValue; + this.endValueSupplier = endValue; + this.stay = stay; + + } + + private double realStartValue; + private double realEndValue, realDurationValue; + + @Override + public void animStart() { + this.realStartValue = startValueSupplier.get(); + this.realEndValue = endValueSupplier.get(); + this.realDurationValue = this.realEndValue - this.realStartValue; + + } + + @Override + public void animProgress(double progress) { + this.progress(realStartValue + this.realDurationValue * progress); + + } + + @Override + public void animEnd() { + if (!stay) { + this.progress(realStartValue); + } else { + this.progress(realEndValue); + } + } + + protected abstract void progress(double val); + +} diff --git a/src/main/java/xueli/animation/FloatValueAnimationBinding.java b/src/main/java/xueli/animation/FloatValueAnimationBinding.java new file mode 100644 index 00000000..eea1f22a --- /dev/null +++ b/src/main/java/xueli/animation/FloatValueAnimationBinding.java @@ -0,0 +1,46 @@ +package xueli.animation; + +import java.util.function.Supplier; + +public abstract class FloatValueAnimationBinding implements AnimationBinding { + + private final Supplier startValueSupplier, endValueSupplier; + private final boolean stay; + + public FloatValueAnimationBinding(Supplier startValue, Supplier endValue, boolean stay) { + this.startValueSupplier = startValue; + this.endValueSupplier = endValue; + this.stay = stay; + + } + + private float realStartValue; + private double realEndValue, realDurationValue; + + @Override + public void animStart() { + this.realStartValue = startValueSupplier.get(); + this.realEndValue = endValueSupplier.get(); + this.realDurationValue = this.realEndValue - this.realStartValue; + + } + + @Override + public void animProgress(double progress) { + this.progress((float) (realStartValue + this.realDurationValue * progress)); +// System.out.println(progress); + + } + + @Override + public void animEnd() { + if (!stay) { + this.progress(realStartValue); + } else { + this.progress((float) realEndValue); + } + } + + protected abstract void progress(float val); + +} diff --git a/src/main/java/xueli/animation/IntValueAnimationBinding.java b/src/main/java/xueli/animation/IntValueAnimationBinding.java new file mode 100644 index 00000000..96d02d6c --- /dev/null +++ b/src/main/java/xueli/animation/IntValueAnimationBinding.java @@ -0,0 +1,46 @@ +package xueli.animation; + +import java.util.function.Supplier; + +public abstract class IntValueAnimationBinding implements AnimationBinding { + + private final Supplier startValueSupplier, endValueSupplier; + private final boolean stay; + + public IntValueAnimationBinding(Supplier startValue, Supplier endValue, boolean stay) { + this.startValueSupplier = startValue; + this.endValueSupplier = endValue; + this.stay = stay; + + } + + private int realStartValue; + private double realEndValue, realDurationValue; + + @Override + public void animStart() { + this.realStartValue = startValueSupplier.get(); + this.realEndValue = endValueSupplier.get(); + this.realDurationValue = this.realEndValue - this.realStartValue; +// System.out.println(realStartValue + ", " + realEndValue + ", " + realDurationValue); + + } + + @Override + public void animProgress(double progress) { + this.progress((int) (realStartValue + this.realDurationValue * progress)); +// System.out.println(realStartValue + ", " + realDurationValue + ", " + progress); + } + + @Override + public void animEnd() { + if (!stay) { + this.progress(realStartValue); + } else { + this.progress((int) realEndValue); + } + } + + protected abstract void progress(int val); + +} diff --git a/src/main/java/xueli/animation/StateChangingTransitionCaller.java b/src/main/java/xueli/animation/StateChangingTransitionCaller.java new file mode 100644 index 00000000..f920b254 --- /dev/null +++ b/src/main/java/xueli/animation/StateChangingTransitionCaller.java @@ -0,0 +1,7 @@ +package xueli.animation; + +public interface StateChangingTransitionCaller { + + public void announceTransition(boolean state); + +} diff --git a/src/main/java/xueli/animation/TransitionBinding.java b/src/main/java/xueli/animation/TransitionBinding.java new file mode 100644 index 00000000..ed1f9d72 --- /dev/null +++ b/src/main/java/xueli/animation/TransitionBinding.java @@ -0,0 +1,19 @@ +package xueli.animation; + +public abstract class TransitionBinding implements AnimationBinding { + + public TransitionBinding() { + } + + @Override + public void animStart() { + this.animProgress(0.0); + + } + + @Override + public void animEnd() { + this.animProgress(1.0); + } + +} diff --git a/src/main/java/xueli/animation/TransitionCaller.java b/src/main/java/xueli/animation/TransitionCaller.java new file mode 100644 index 00000000..38c41efd --- /dev/null +++ b/src/main/java/xueli/animation/TransitionCaller.java @@ -0,0 +1,7 @@ +package xueli.animation; + +public interface TransitionCaller { + + public void announceTransition(); + +} diff --git a/src/main/java/xueli/animation/TransitionManager.java b/src/main/java/xueli/animation/TransitionManager.java new file mode 100644 index 00000000..2e42a3d9 --- /dev/null +++ b/src/main/java/xueli/animation/TransitionManager.java @@ -0,0 +1,61 @@ +package xueli.animation; + +public class TransitionManager { + + private final AnimationManager animator; + + public TransitionManager(AnimationManager animator) { + this.animator = animator; + } + + public TransitionCaller registerNewTransition(TransitionBinding binding) { + return this.registerNewTransition(binding, Curves.linear, 0); + } + + public TransitionCaller registerNewTransition(TransitionBinding binding, Curve curve, long duration) { + TransitionCaller caller = new TransitionCaller() { + private AnimationInstance previous; + + @Override + public void announceTransition() { + if (previous != null) { + previous.stop(previous.getProgress()); + } + + AnimationInstance newInstance = animator.start(binding, curve, duration); + newInstance.addAnimationEndListener(() -> { + previous = null; + }); + this.previous = newInstance; + + } + }; + caller.announceTransition(); + return caller; + } + + public StateChangingTransitionCaller registerNewStateChangingTransition(TransitionBinding binding, Curve curve, + long duration) { + StateChangingTransitionCaller caller = new StateChangingTransitionCaller() { + private AnimationInstance previous; + + @Override + public void announceTransition(boolean state) { + if (previous != null) { + previous.stop(previous.getProgress()); + } + + AnimationInstance newInstance = state ? animator.start(binding, curve, duration) + : animator.startReverse(binding, curve, duration); + newInstance.addAnimationEndListener(() -> { + previous = null; + }); + this.previous = newInstance; + + } + }; + caller.announceTransition(false); + return caller; + } + +} diff --git a/src/main/java/xueli/daw/Channel.java b/src/main/java/xueli/daw/Channel.java new file mode 100644 index 00000000..fc883389 --- /dev/null +++ b/src/main/java/xueli/daw/Channel.java @@ -0,0 +1,59 @@ +package xueli.daw; + +import java.util.ArrayList; + +public class Channel { + + private final DawContext context; + + private Generator generator; + private final ArrayList pluginChain = new ArrayList<>(); + + private final int[] buffer1 = new int[DawContext.BUFFER_SIZE]; + private final int[] buffer2 = new int[DawContext.BUFFER_SIZE]; + + public Channel(DawContext context) { + this.context = context; + } + + public void setGenerator(Generator generator) { + this.generator = generator; + } + + public Generator getGenerator() { + return generator; + } + + // Can plugins be singleton? No + public void addPlugin(Plugin plugin) { + this.pluginChain.add(plugin); + } + + public void removePlugin(Plugin plugin) { + this.pluginChain.remove(plugin); + } + + // Notice that the array yielded can be changed outside + + public int[] tick(double time) { + if(generator == null) return null; + + // Double buffer + int[] src = buffer1; + int[] dest = buffer2; + + generator.progress(dest, time, context); + + for(Plugin p : pluginChain) { + int[] temp = src; + src = dest; + dest = temp; + + p.progress(src, dest, time, context); + + } + + return dest; + } + +} diff --git a/src/main/java/xueli/daw/ChannelManager.java b/src/main/java/xueli/daw/ChannelManager.java new file mode 100644 index 00000000..7f22f87e --- /dev/null +++ b/src/main/java/xueli/daw/ChannelManager.java @@ -0,0 +1,92 @@ +package xueli.daw; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +// The manager just store the channel, so the life-cycle should be managed by the user! +public class ChannelManager { + + private final DawContext context; + private final ArrayList channels = new ArrayList<>(); + + private double time = 0.0; + + public ChannelManager(DawContext context) { + this.context = context; + + } + + private int[] buffer; + private byte[] destBuffer = new byte[DawContext.BUFFER_SIZE * DawContext.GLOBAL_FORMAT.getSampleSizeInBits()]; + + public void tick() { + time += DawContext.BUFFER_AHEAD_TIME; + + AtomicBoolean firstApply = new AtomicBoolean(true); + + channels.parallelStream() + .map(c -> c.tick(time)) + // I want to make a collector myself but seems can't + // And I don't want to collect them either + .forEachOrdered(i -> { + if(i == null) return; + if(firstApply.get()) { + buffer = i; + firstApply.set(false); + } else { + for(int j = 0; j < DawContext.BUFFER_SIZE; j++) { + // Blend + buffer[j] = (buffer[j] + i[j]) / 2; + } + } + }); + + if(buffer == null) return; + + // Convert to sample bit + for(int i = 0; i < DawContext.BUFFER_SIZE; i++) { + double percentage = (double) buffer[i] / Integer.MAX_VALUE; + percentage = Math.min(1.0, percentage); + percentage = Math.max(-1.0, percentage); + + int sampleValue = (int) (percentage * DawContext.MAX_SAMPLE_VALUE); + System.out.println(sampleValue); + for(int j = 0; j < DawContext.SAMPLE_BYTES_COUNT; j++) { + // little-endian + destBuffer[i * DawContext.SAMPLE_BYTES_COUNT + j] = (byte) ((sampleValue >> (j * 8)) & (1 << 8 - 1)); + } + + // Set sign bit + if(sampleValue < 0) { + destBuffer[i * DawContext.SAMPLE_BYTES_COUNT] |= (1 << 8); + } + + } + + context.streamer.write(destBuffer); + + } + + public int addChannel(Channel channel) { + int index = this.channels.size(); + this.channels.add(channel); + return index; + } + + public boolean remove(Channel channel) { + return this.channels.remove(channel); + } + + public Channel removeChannel(int index) { + return this.channels.remove(index); + } + + public int getChannelsCount() { + return this.channels.size(); + } + + public Channel getChannelFromIndex(int i) { + return this.channels.get(i); + } + +} diff --git a/src/main/java/xueli/daw/DawContext.java b/src/main/java/xueli/daw/DawContext.java new file mode 100644 index 00000000..2ddfebed --- /dev/null +++ b/src/main/java/xueli/daw/DawContext.java @@ -0,0 +1,50 @@ +package xueli.daw; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.LineUnavailableException; + +import xueli.daw.driver.JavaSoundDriver; +import xueli.daw.driver.JavaSoundStream; + +public class DawContext { + + public static final AudioFormat GLOBAL_FORMAT = new AudioFormat(44100, 16, 1, true, false); + public static final int BUFFER_SIZE = 2048; + public static final double BUFFER_AHEAD_TIME = (double) BUFFER_SIZE / GLOBAL_FORMAT.getSampleRate(); + + public static final int SAMPLE_BYTES_COUNT = GLOBAL_FORMAT.getSampleSizeInBits() >> 3; + public static final int MAX_SAMPLE_VALUE = (1 << (GLOBAL_FORMAT.getSampleSizeInBits() - 1)) - 1; + + private final JavaSoundDriver driver; + final JavaSoundStream streamer; + + private final ChannelManager channels; + + public DawContext() { + try { + this.driver = new JavaSoundDriver(GLOBAL_FORMAT); + } catch (LineUnavailableException e) { + throw new RuntimeException(e); + } + this.streamer = driver.start(); + + this.channels = new ChannelManager(this); + + } + + public ChannelManager getChannelManager() { + return channels; + } + + public void tick() { + this.channels.tick(); + + } + + public void release() { + this.streamer.close(); + this.driver.release(); + + } + +} diff --git a/src/main/java/xueli/daw/Generator.java b/src/main/java/xueli/daw/Generator.java new file mode 100644 index 00000000..85d1c442 --- /dev/null +++ b/src/main/java/xueli/daw/Generator.java @@ -0,0 +1,7 @@ +package xueli.daw; + +public interface Generator { + + public void progress(int[] dest, double time, DawContext context); + +} diff --git a/src/main/java/xueli/daw/Player.java b/src/main/java/xueli/daw/Player.java new file mode 100644 index 00000000..acb814ca --- /dev/null +++ b/src/main/java/xueli/daw/Player.java @@ -0,0 +1,9 @@ +package xueli.daw; + +interface Player { + + public void progressForward(double time); + + public void release(); + +} diff --git a/src/main/java/xueli/daw/Plugin.java b/src/main/java/xueli/daw/Plugin.java new file mode 100644 index 00000000..c7072ef7 --- /dev/null +++ b/src/main/java/xueli/daw/Plugin.java @@ -0,0 +1,7 @@ +package xueli.daw; + +public interface Plugin { + + public void progress(int[] src, int[] dest, double time, DawContext context); + +} diff --git a/src/main/java/xueli/daw/driver/ALBuffer.java b/src/main/java/xueli/daw/driver/ALBuffer.java new file mode 100644 index 00000000..c1f2f1b3 --- /dev/null +++ b/src/main/java/xueli/daw/driver/ALBuffer.java @@ -0,0 +1,62 @@ +package xueli.daw.driver; + +import java.util.Objects; + +import org.lwjgl.openal.AL11; + +import xueli.utils.buffer.BufferSyncor; +import xueli.utils.buffer.LotsOfByteBuffer; + +public class ALBuffer { + + final int id; + + BufferFormat format; + int frequency; + + final BufferSyncor bufferManager = new BufferSyncor(); + + public ALBuffer(int id) { + this.id = id; + } + + public void setBuffer(LotsOfByteBuffer buffer, BufferFormat format, int frequency) { + this.format = format; + this.frequency = frequency; + this.bufferManager.updateBuffer(buffer); + + } + + public BufferSyncor.BackBuffer createBackBuffer(BufferFormat format, int frequency) { + this.format = format; + this.frequency = frequency; + return this.bufferManager.createBackBuffer(); + } + + // Maybe here we have to do this ourselves + public void doingSyncIfNecessary() { + this.bufferManager.doingSyncIfNecessary(b -> AL11.alBufferData(this.id, BufferFormat.getALFormat(this.format), b.getBuffer(), this.frequency)); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ALBuffer other = (ALBuffer) obj; + return id == other.id; + } + +// public BufferState getState() { +// return BufferState.getFromAL(AL11.algetbuffer); +// } + +} diff --git a/src/main/java/xueli/daw/driver/ALDriver.java b/src/main/java/xueli/daw/driver/ALDriver.java new file mode 100644 index 00000000..b93eac85 --- /dev/null +++ b/src/main/java/xueli/daw/driver/ALDriver.java @@ -0,0 +1,85 @@ +package xueli.daw.driver; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.HashMap; + +import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL11; +import org.lwjgl.openal.ALC; +import org.lwjgl.openal.ALC11; +import org.lwjgl.openal.ALCCapabilities; +import xueli.utils.logger.Logger; + +// The driver is designed for DAW so it doesn't provide functions like "effects" or "positions". +// But this group of classes can provide. +// Not used! +public class ALDriver { + + private static final Logger LOGGER = new Logger(); + + private final long context; + private HashMap bufferPool = new HashMap<>(); + + public ALDriver(ByteBuffer deviceSpecifier) { + long device = ALC11.alcOpenDevice(deviceSpecifier); + if(device == 0) { + throw new RuntimeException("Can't open OpenAL device!"); + } + + ALCCapabilities deviceCaps = ALC.createCapabilities(device); + this.context = ALC11.alcCreateContext(device, (IntBuffer) null); + if (context == 0) { + throw new IllegalStateException("Failed to create OpenAL context."); + } + ALC11.alcMakeContextCurrent(context); + AL.createCapabilities(deviceCaps); + + this.printDeviceInfo(device); + + } + + private void printDeviceInfo(long device) { + // Keep the same code structure as OpenGL :} + String nameString = AL11.alGetString(AL11.AL_VENDOR); + String platform = AL11.alGetString(AL11.AL_RENDERER); + String version = AL11.alGetString(AL11.AL_VERSION); + LOGGER.warning("[DeviceInfo] OpenAL: " + nameString + ", " + platform + ", " + version); + + + } + + public ALSpeaker createSpeaker() { + int speaker = AL11.alGenSources(); + return new ALSpeaker(speaker, this); + } + + public void releaseSpeaker(ALSpeaker speaker) { + AL11.alDeleteSources(speaker.id); + + } + + public ALBuffer createBuffer() { + int name = AL11.alGenBuffers(); + var bufferObj = new ALBuffer(name); + this.bufferPool.put(name, bufferObj); + return bufferObj; + } + + public ALBuffer getBufferFromId(int id) { + return bufferPool.get(id); + } + + public void releaseBuffer(ALBuffer buffer) { + AL11.alDeleteBuffers(buffer.id); + this.bufferPool.remove(buffer.id); + + } + + public void release() { + ALC11.alcMakeContextCurrent(0); + ALC11.alcDestroyContext(this.context); + + } + +} diff --git a/src/main/java/xueli/daw/driver/ALSpeaker.java b/src/main/java/xueli/daw/driver/ALSpeaker.java new file mode 100644 index 00000000..491fd9ea --- /dev/null +++ b/src/main/java/xueli/daw/driver/ALSpeaker.java @@ -0,0 +1,105 @@ +package xueli.daw.driver; + +import java.nio.IntBuffer; +import org.lwjgl.openal.AL11; +import org.lwjgl.system.MemoryUtil; + +public class ALSpeaker { + + final int id; + final ALDriver context; + + public ALSpeaker(int id, ALDriver context) { + this.id = id; + this.context = context; + } + + public void setBuffer(ALBuffer buffer) { + if(buffer == null) + AL11.alSourcei(this.id, AL11.AL_BUFFER, 0); + else { + AL11.alSourcei(this.id, AL11.AL_BUFFER, buffer.id); + } + + } + + public void setPitch(float pitch) { + AL11.alSourcef(this.id, AL11.AL_PITCH, pitch); + } + + public void setGain(float gain) { + AL11.alSourcef(this.id, AL11.AL_GAIN, gain); + } + + public void setLooping(boolean loop) { + AL11.alSourcei(this.id, AL11.AL_LOOPING, loop ? 1 : 0); + } + + public void play() { + AL11.alSourcePlay(this.id); + } + + public void pause() { + AL11.alSourcePause(this.id); + } + + public void stop() { + AL11.alSourceStop(this.id); + } + + public void rewind() { + AL11.alSourceRewind(this.id); + } + + public SourceState getState() { + int state = AL11.alGetSourcei(this.id, AL11.AL_SOURCE_STATE); + return SourceState.getFromAL(state); + } + + public SourceType getType() { + int type = AL11.alGetSourcei(this.id, AL11.AL_SOURCE_TYPE); + return SourceType.getFromAL(type); + } + + public void queueBuffer(ALBuffer... queue) { + if(queue.length == 1) { + AL11.alSourceQueueBuffers(this.id, queue[0].id); + return; + } + + // this time we can have full control of this memory + IntBuffer arrQueueNames = MemoryUtil.memAllocInt(queue.length); + + for(ALBuffer buf : queue) { // Is this slower than directly access to array? + arrQueueNames.put(buf.id); + } + arrQueueNames.flip(); + AL11.alSourceQueueBuffers(this.id, arrQueueNames); + + MemoryUtil.memFree(arrQueueNames); + + } + + // Unqueue the buffer and put it in the queue + public void unqueueBuffer(ALBuffer[] dest) { + int[] temp = new int[dest.length]; + AL11.alSourceUnqueueBuffers(this.id, temp); + for(int i = 0; i < dest.length; i++) { + dest[i] = context.getBufferFromId(temp[i]); + } + } + + public int queryProcessedBufferCount() { + return AL11.alGetSourcei(this.id, AL11.AL_BUFFERS_PROCESSED); + } + + public void queryProcessedBuffer(ALBuffer[] dest) { + int[] temp = new int[dest.length]; + AL11.alGetSourceiv(this.id, AL11.AL_BUFFERS_PROCESSED, temp); + for(int i = 0; i < dest.length; i++) { + dest[i] = context.getBufferFromId(temp[i]); + } + + } + +} diff --git a/src/main/java/xueli/daw/driver/AudioDriver.java b/src/main/java/xueli/daw/driver/AudioDriver.java new file mode 100644 index 00000000..4b8763a3 --- /dev/null +++ b/src/main/java/xueli/daw/driver/AudioDriver.java @@ -0,0 +1,6 @@ +package xueli.daw.driver; + +// TODO: Wrap OpenAL like GraphicDriver +public interface AudioDriver { + +} diff --git a/src/main/java/xueli/daw/driver/BufferFormat.java b/src/main/java/xueli/daw/driver/BufferFormat.java new file mode 100644 index 00000000..794acfa4 --- /dev/null +++ b/src/main/java/xueli/daw/driver/BufferFormat.java @@ -0,0 +1,31 @@ +package xueli.daw.driver; + +import org.lwjgl.openal.AL11; + +public enum BufferFormat { + + // For now it is the same as OpenAL because LovelyZeeiam doesn't know much about other format! + MONO8, MONO16, STEREO8, STEREO16; + + // Will be moved when make ALDriver to interface! + public static int getALFormat(BufferFormat format) { + return switch (format){ + case MONO8 -> AL11.AL_FORMAT_MONO8; + case MONO16 -> AL11.AL_FORMAT_MONO16; + case STEREO8 -> AL11.AL_FORMAT_STEREO8; + case STEREO16 -> AL11.AL_FORMAT_STEREO16; + default -> throw new IllegalArgumentException("Unexpected value: " + format); + }; + } + + public static BufferFormat getFromAL(int format) { + return switch (format) { + case AL11.AL_FORMAT_MONO8 -> MONO8; + case AL11.AL_FORMAT_MONO16 -> MONO16; + case AL11.AL_FORMAT_STEREO8 -> STEREO8; + case AL11.AL_FORMAT_STEREO16 -> STEREO16; + default -> throw new IllegalArgumentException("Unexpected value: " + format); + }; + } + +} diff --git a/src/main/java/xueli/daw/driver/BufferState.java b/src/main/java/xueli/daw/driver/BufferState.java new file mode 100644 index 00000000..23a4751e --- /dev/null +++ b/src/main/java/xueli/daw/driver/BufferState.java @@ -0,0 +1,29 @@ +package xueli.daw.driver; + +import org.lwjgl.openal.AL11; + +public enum BufferState { + + // For now it is the same as OpenAL because LovelyZeeiam doesn't know much about other format! + UNUSED, PENDING, PROCESSED; + + // Will be moved when make ALDriver to interface! + public static int getALFormat(BufferState format) { + return switch (format){ + case UNUSED -> AL11.AL_UNUSED; + case PENDING -> AL11.AL_PENDING; + case PROCESSED -> AL11.AL_PROCESSED; + default -> throw new IllegalArgumentException("Unexpected value: " + format); + }; + } + + public static BufferState getFromAL(int format) { + return switch (format) { + case AL11.AL_UNUSED -> UNUSED; + case AL11.AL_PENDING -> PENDING; + case AL11.AL_PROCESSED -> PROCESSED; + default -> throw new IllegalArgumentException("Unexpected value: " + format); + }; + } + +} diff --git a/src/main/java/xueli/daw/driver/JavaSoundDriver.java b/src/main/java/xueli/daw/driver/JavaSoundDriver.java new file mode 100644 index 00000000..c9d2ccb9 --- /dev/null +++ b/src/main/java/xueli/daw/driver/JavaSoundDriver.java @@ -0,0 +1,48 @@ +package xueli.daw.driver; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; + +public class JavaSoundDriver { + +// private static final Logger LOGGER = new Logger(); + + private final SourceDataLine sourceDataLine; + private JavaSoundStream stream; + + public JavaSoundDriver(AudioFormat format) throws LineUnavailableException { + this.sourceDataLine = AudioSystem.getSourceDataLine(format); + this.sourceDataLine.open(); + + } + + public JavaSoundStream start() { + if(this.stream != null) return stream; + + this.sourceDataLine.start(); + return new JavaSoundStream() { + + @Override + public void write(byte[] b, int offset, int length) { + sourceDataLine.write(b, offset, length); + } + + @Override + public void close() { + sourceDataLine.drain(); + sourceDataLine.stop(); + + stream = null; + + } + }; + } + + public void release() { + this.sourceDataLine.close(); + + } + +} diff --git a/src/main/java/xueli/daw/driver/JavaSoundOutputStream.java b/src/main/java/xueli/daw/driver/JavaSoundOutputStream.java new file mode 100644 index 00000000..5057d89c --- /dev/null +++ b/src/main/java/xueli/daw/driver/JavaSoundOutputStream.java @@ -0,0 +1,29 @@ +package xueli.daw.driver; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.sound.sampled.SourceDataLine; + +public class JavaSoundOutputStream extends OutputStream { + + private final SourceDataLine dataLine; + + public JavaSoundOutputStream(SourceDataLine dataLine) { + this.dataLine = dataLine; + } + + private final byte[] buffer = new byte[1]; + + @Override + public void write(int b) throws IOException { + buffer[0] = (byte) b; + dataLine.write(buffer, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + dataLine.write(b, off, len); + } + +} diff --git a/src/main/java/xueli/daw/driver/JavaSoundStream.java b/src/main/java/xueli/daw/driver/JavaSoundStream.java new file mode 100644 index 00000000..c563f000 --- /dev/null +++ b/src/main/java/xueli/daw/driver/JavaSoundStream.java @@ -0,0 +1,13 @@ +package xueli.daw.driver; + +public interface JavaSoundStream { + + default public void write(byte[] b) { + this.write(b, 0, b.length); + } + + public void write(byte[] b, int offset, int length); + + public void close(); + +} diff --git a/src/main/java/xueli/daw/driver/SourceState.java b/src/main/java/xueli/daw/driver/SourceState.java new file mode 100644 index 00000000..651fbf26 --- /dev/null +++ b/src/main/java/xueli/daw/driver/SourceState.java @@ -0,0 +1,32 @@ +package xueli.daw.driver; + +import org.lwjgl.openal.AL11; + +public enum SourceState { + + // For now it is the same as OpenAL because LovelyZeeiam doesn't know much about other format! + INITIAL, PLAYING, PAUSED, STOPPED; + + // Will be moved when make ALDriver to interface! + // Hard coded! wouldn't consider use a data structure to store this? —— LovelyZeeiam + public static int getALState(SourceState state) { + return switch (state){ + case INITIAL -> AL11.AL_INITIAL; + case PLAYING -> AL11.AL_PLAYING; + case PAUSED -> AL11.AL_PAUSED; + case STOPPED -> AL11.AL_STOPPED; + default -> throw new IllegalArgumentException("Unexpected value: " + state); + }; + } + + public static SourceState getFromAL(int state) { + return switch (state) { + case AL11.AL_INITIAL -> INITIAL; + case AL11.AL_PLAYING -> PLAYING; + case AL11.AL_PAUSED -> PAUSED; + case AL11.AL_STOPPED -> STOPPED; + default -> throw new IllegalArgumentException("Unexpected value: " + state); + }; + } + +} diff --git a/src/main/java/xueli/daw/driver/SourceType.java b/src/main/java/xueli/daw/driver/SourceType.java new file mode 100644 index 00000000..88f78d9d --- /dev/null +++ b/src/main/java/xueli/daw/driver/SourceType.java @@ -0,0 +1,21 @@ +package xueli.daw.driver; + +import org.lwjgl.openal.AL11; + +public enum SourceType { + + // For now it is the same as OpenAL because LovelyZeeiam doesn't know much about other format! + UNDETERMINED, STATIC, STREAMING; + + // Will be moved when make ALDriver to interface! + // Hard coded! wouldn't consider use a data structure to store this? —— LovelyZeeiam + public static SourceType getFromAL(int state) { + return switch (state) { + case AL11.AL_UNDETERMINED -> UNDETERMINED; + case AL11.AL_STATIC -> STATIC; + case AL11.AL_STREAMING -> STREAMING; + default -> throw new IllegalArgumentException("Unexpected value: " + state); + }; + } + +} diff --git a/src/main/java/xueli/daw/package-info.java b/src/main/java/xueli/daw/package-info.java new file mode 100644 index 00000000..a32d682c --- /dev/null +++ b/src/main/java/xueli/daw/package-info.java @@ -0,0 +1,10 @@ +/** + *

This is a little DAW where several generator channels can be added.

+ * + *

Each channel has a chain. The beginning item in the chain received MIDI signal, acting as the beginning generator, and the last one should yield sound signal, which is actually byte buffer managed in the context.

+ * + * This is a failed test. + * + */ +@java.lang.Deprecated +package xueli.daw; \ No newline at end of file diff --git a/src/main/java/xueli/daw/synthesizer/SineWaveSynthesizer.java b/src/main/java/xueli/daw/synthesizer/SineWaveSynthesizer.java new file mode 100644 index 00000000..89fa5036 --- /dev/null +++ b/src/main/java/xueli/daw/synthesizer/SineWaveSynthesizer.java @@ -0,0 +1,19 @@ +package xueli.daw.synthesizer; + +import xueli.daw.DawContext; +import xueli.daw.Generator; + +public class SineWaveSynthesizer implements Generator { + + public SineWaveSynthesizer() { + } + + @Override + public void progress(int[] dest, double time, DawContext context) { + for(int i = 0; i < dest.length; i++) { + dest[i] = (int) (Integer.MAX_VALUE * SynthesizerUtils.sine(440.0, time + i * DawContext.BUFFER_AHEAD_TIME / dest.length)); + } + + } + +} diff --git a/src/main/java/xueli/daw/synthesizer/SynthesizerUtils.java b/src/main/java/xueli/daw/synthesizer/SynthesizerUtils.java new file mode 100644 index 00000000..91d47127 --- /dev/null +++ b/src/main/java/xueli/daw/synthesizer/SynthesizerUtils.java @@ -0,0 +1,17 @@ +package xueli.daw.synthesizer; + +public class SynthesizerUtils { + + public static double sine(double freq, double time) { + return Math.sin(2.0*Math.PI*freq*time); + } + + public static double sawtooth(double freq, double time) { + return 2.0*(freq*time - Math.floor(freq*time + 0.5)); + } + + public static double triangle(double freq, double time) { + return 2.0*Math.abs(sawtooth(freq, time))-1.0; + } + +} diff --git a/src/main/java/xueli/game/Game.java b/src/main/java/xueli/game/Game.java new file mode 100644 index 00000000..8d19b5e8 --- /dev/null +++ b/src/main/java/xueli/game/Game.java @@ -0,0 +1,120 @@ +package xueli.game; + +import java.util.LinkedList; +import java.util.Queue; + +import org.lwjgl.opengl.GL11; + +import xueli.game.renderer.RendererManager; +import xueli.game2.display.Display; +import xueli.utils.exception.CrashReport; + +@Deprecated +public abstract class Game implements Runnable { + + public static Game INSTANCE_GAME; + public static final String DEFAULT_RES_DIRECTORY_STRING = "./res/"; + public static final String DEFAULT_WORKING_DIRECTORY_STRING = "./.cg/"; + + private final Display display; + + private final Queue queueInMainThread = new LinkedList<>(); + + protected RendererManager rendererManager; + + public Game(int width, int height, String windowTitle) { + INSTANCE_GAME = this; + + this.display = new Display(width, height, windowTitle); + + } + + @Override + public void run() { + display.create(); + rendererManager = new RendererManager(); + + onCreate(); + display.show(); + + onSize((int) getWidth(), (int) getHeight()); + + while (display.isRunning()) { + onTick(); + rendererManager.render(); +// GLHelper.checkGLError("[Renderer]"); + display.update(); + +// Logger.getInstance().pushState("MainThreadQueue"); + if (!queueInMainThread.isEmpty()) { + queueInMainThread.poll().run(); + } +// Logger.getInstance().popState(); + + } + + // display.hide(); + display.release(); + + rendererManager.release(); + onRelease(); + + } + + public abstract void onCreate(); + + public abstract void onTick(); + + protected void defaultViewport() { + GL11.glViewport(0, 0, display.getWidth(), display.getHeight()); + + } + + public void onSize(int width, int height) { + defaultViewport(); + rendererManager.size(width, height); + + } + + public abstract void onRelease(); + + public void announceCrash(String state, Throwable throwable) { + queueInMainThread.add(() -> { + display.release(); + new CrashReport(state, throwable).showCrashReport(); + }); + } + + public Display getDisplay() { + return display; + } + + public float getWidth() { + return display.getWidth(); + } + + public float getHeight() { + return display.getHeight(); + } + + public float getDisplayScale() { + return display.getDisplayScale(); + } + + public float getCursorX() { + return display.getCursorX(); + } + + public float getCursorY() { + return display.getCursorY(); + } + + public RendererManager getRendererManager() { + return rendererManager; + } + + public void addTaskForMainThread(Runnable run) { + this.queueInMainThread.add(run); + } + +} diff --git a/src/main/java/xueli/game/package-info.java b/src/main/java/xueli/game/package-info.java new file mode 100644 index 00000000..7bb0e72e --- /dev/null +++ b/src/main/java/xueli/game/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.game; \ No newline at end of file diff --git a/src/main/java/xueli/game/renderer/Renderer.java b/src/main/java/xueli/game/renderer/Renderer.java new file mode 100644 index 00000000..efc46549 --- /dev/null +++ b/src/main/java/xueli/game/renderer/Renderer.java @@ -0,0 +1,14 @@ +package xueli.game.renderer; + +@Deprecated +public interface Renderer { + + public void render(); + + public void update(); + + public void size(); + + public void release(); + +} diff --git a/src/main/java/xueli/game/renderer/RendererManager.java b/src/main/java/xueli/game/renderer/RendererManager.java new file mode 100644 index 00000000..8cc1221d --- /dev/null +++ b/src/main/java/xueli/game/renderer/RendererManager.java @@ -0,0 +1,51 @@ +package xueli.game.renderer; + +import xueli.game.Game; +import xueli.utils.logger.Logger; + +@Deprecated +public class RendererManager { + + private Renderer current; + + public RendererManager() { + + } + + public void setCurrentRenderer(Renderer renderer) { + Game.INSTANCE_GAME.addTaskForMainThread(() -> { +// Logger.getInstance().pushState("Renderer"); + if (this.current != null) { + this.current.release(); + } + this.current = renderer; + this.current.size(); + Logger.getInstance().info("Change renderer: " + renderer.getClass().getName()); +// Logger.getInstance().popState(); + }); + + } + + public void render() { + if (this.current != null) { + this.current.update(); + this.current.render(); + } + + } + + public void size(int w, int h) { + if (this.current != null) { + this.current.size(); + } + + } + + public void release() { + if (this.current != null) { + this.current.release(); + } + + } + +} diff --git a/src/main/java/xueli/game/renderer/ScreenQuadRenderer.java b/src/main/java/xueli/game/renderer/ScreenQuadRenderer.java new file mode 100644 index 00000000..51a08a82 --- /dev/null +++ b/src/main/java/xueli/game/renderer/ScreenQuadRenderer.java @@ -0,0 +1,107 @@ +package xueli.game.renderer; + +// TODO: seems that a new one is needed! +@Deprecated +public class ScreenQuadRenderer { + +// private static final float[] VERTICES = new float[] { -1, -1, 0, 0, -1, 1, 0, 1, 1, 1, 1, 1, 1, -1, 1, 0 }; +// +// private ScreenQuadPointer pointer; +// private Shader shader; +// +// public ScreenQuadRenderer() { +// pointer = new ScreenQuadPointer(); +// +// pointer.bind(); +// pointer.mapBuffer().asFloatBuffer().put(VERTICES); +// pointer.unmap(); +// pointer.unbind(); +// +// try { +// // TODO +// shader = Shader.compile( +// new String(Files.readResourcePackedInJar("/assets/craftgame/shaders/screen_quad/vert.txt"), +// StandardCharsets.UTF_8), +// new String(Files.readResourcePackedInJar("/assets/craftgame/shaders/screen_quad/frag.txt"), +// StandardCharsets.UTF_8)); +// } catch (IOException e) { +// e.printStackTrace(); +// System.exit(-1); +// } +// // shader = new Shader("res/shaders/screen_quad/vert.txt", +// // "res/shaders/screen_quad/frag.txt"); +// +// shader.bind(); +// shader.setInt(shader.getUnifromLocation("tex"), 0); +// shader.setInt(shader.getUnifromLocation("depth"), 1); +// shader.unbind(); +// +// } +// +// public ScreenQuadRenderer(Shader shader) { +// pointer = new ScreenQuadPointer(); +// +// pointer.bind(); +// pointer.mapBuffer().asFloatBuffer().put(VERTICES); +// pointer.unmap(); +// pointer.unbind(); +// +// this.shader = shader; +// +// } +// +// public void render(int textureId) { +// shader.bind(); +// pointer.bind(); +// GL30.glBindTexture(GL30.GL_TEXTURE_2D, textureId); +// pointer.draw(GL30.GL_TRIANGLE_FAN, 0, 4); +// pointer.unbind(); +// shader.unbind(); +// +// } +// +// public void render(int textureId, int depthTexID) { +// shader.bind(); +// pointer.bind(); +// +// GL30.glActiveTexture(GL30.GL_TEXTURE0); +// GL30.glBindTexture(GL30.GL_TEXTURE_2D, textureId); +// GL30.glActiveTexture(GL30.GL_TEXTURE1); +// GL30.glBindTexture(GL30.GL_TEXTURE_2D, depthTexID); +// +// pointer.draw(GL30.GL_TRIANGLE_FAN, 0, 4); +// pointer.unbind(); +// shader.unbind(); +// +// } +// +// public ScreenQuadPointer getPointer() { +// return pointer; +// } +// +// public Shader getShader() { +// return shader; +// } +// +// public static class ScreenQuadPointer extends DefaultRenderBuffer.DefaultVertexPointer { +// +// public ScreenQuadPointer() { +// super(1024, GL30.GL_STATIC_DRAW); +// +// } +// +// @Override +// protected void registerVertex() { +// // UV +// GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 4 * 4, 2 * 4); +// GL20.glEnableVertexAttribArray(1); +// +// // position +// GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 4 * 4, 0 * 4); +// GL20.glEnableVertexAttribArray(0); +// +// } +// +// } + +} diff --git a/src/main/java/xueli/game/renderer/package-info.java b/src/main/java/xueli/game/renderer/package-info.java new file mode 100644 index 00000000..ee162973 --- /dev/null +++ b/src/main/java/xueli/game/renderer/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.game.renderer; \ No newline at end of file diff --git a/src/main/java/xueli/game/resource/ImageResourceManager.java b/src/main/java/xueli/game/resource/ImageResourceManager.java new file mode 100644 index 00000000..67f22dd2 --- /dev/null +++ b/src/main/java/xueli/game/resource/ImageResourceManager.java @@ -0,0 +1,54 @@ +package xueli.game.resource; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVG.nvgCreateImageMem; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import xueli.utils.io.Files; + +public class ImageResourceManager extends ResourceManager + implements ResourceLoader, MissingProvider { + + private long currentNvg = 0; + private int imageLoadingFlag = NVG_IMAGE_NEAREST; + + ImageResourceManager(ResourceMaster master) { + super(master); + setLoader(this); + setMissingProvider(this); + + } + + public ImageResourceManager setCurrentNvg(long currentNvg) { + this.currentNvg = currentNvg; + return this; + } + + public ImageResourceManager setImageLoadingFlag(int imageLoadingFlag) { + this.imageLoadingFlag = imageLoadingFlag; + return this; + } + + @Override + public NVGImage load(ResourceHolder holder) throws Exception { + ByteBuffer buffer = null; + int imageId = -1; + String virtualPath = holder.virtualPath; + try { + buffer = Files.readResourcePackedInJarAndPackedToBuffer(virtualPath); + imageId = nvgCreateImageMem(currentNvg, imageLoadingFlag, buffer); + } catch (IOException e) { + throw new Exception("Couldn't load image: " + virtualPath, e); + } + // System.out.println(imageId); + return new NVGImage(currentNvg, imageId); + } + + @Override + public void onMissing(ResourceHolder holder) { + holder.result = TextureMissing.getMissingNvgImage(currentNvg); + } + +} diff --git a/src/main/java/xueli/game/resource/MissingProvider.java b/src/main/java/xueli/game/resource/MissingProvider.java new file mode 100644 index 00000000..bcdcc99d --- /dev/null +++ b/src/main/java/xueli/game/resource/MissingProvider.java @@ -0,0 +1,7 @@ +package xueli.game.resource; + +public interface MissingProvider { + + public void onMissing(ResourceHolder holder); + +} diff --git a/src/main/java/xueli/game/resource/NVGImage.java b/src/main/java/xueli/game/resource/NVGImage.java new file mode 100644 index 00000000..6e79fb26 --- /dev/null +++ b/src/main/java/xueli/game/resource/NVGImage.java @@ -0,0 +1,4 @@ +package xueli.game.resource; + +public record NVGImage(long nvg, int image) { +} diff --git a/src/main/java/xueli/game/resource/ResourceHolder.java b/src/main/java/xueli/game/resource/ResourceHolder.java new file mode 100644 index 00000000..00d85ae3 --- /dev/null +++ b/src/main/java/xueli/game/resource/ResourceHolder.java @@ -0,0 +1,43 @@ +package xueli.game.resource; + +public class ResourceHolder { + + private ResourceManager manager; + String namespace; + String virtualPath; + + private boolean preloaded = false; + T result; + + ResourceHolder(ResourceManager manager) { + this.manager = manager; + } + + ResourceHolder(ResourceManager manager, String namespace, String virtualPath) { + this.virtualPath = virtualPath; + this.namespace = namespace; + this.manager = manager; + + } + + public void preload() throws Exception { + preloaded = true; + result = manager.getLoader().load(this); + } + + public T getResult() { + if (!preloaded) { + try { + preload(); + } catch (Exception exception) { + exception.printStackTrace(); + return null; + } + } + return result; + } + + public void release() { + } + +} diff --git a/src/main/java/xueli/game/resource/ResourceLoader.java b/src/main/java/xueli/game/resource/ResourceLoader.java new file mode 100644 index 00000000..530d1438 --- /dev/null +++ b/src/main/java/xueli/game/resource/ResourceLoader.java @@ -0,0 +1,7 @@ +package xueli.game.resource; + +public interface ResourceLoader { + + public T load(ResourceHolder holder) throws Exception; + +} diff --git a/src/main/java/xueli/game/resource/ResourceManager.java b/src/main/java/xueli/game/resource/ResourceManager.java new file mode 100644 index 00000000..5df676c8 --- /dev/null +++ b/src/main/java/xueli/game/resource/ResourceManager.java @@ -0,0 +1,66 @@ +package xueli.game.resource; + +import java.util.HashMap; + +public class ResourceManager { + + protected ResourceMaster master; + protected HashMap> holderMap = new HashMap<>(); + protected ResourceLoader loader; + + private MissingProvider missingProvider = new MissingProvider() { + @Override + public void onMissing(ResourceHolder holder) { + holder.result = null; + } + }; + + public ResourceManager(ResourceLoader loader, ResourceMaster master) { + this.loader = loader; + this.master = master; + + } + + ResourceManager(ResourceMaster master) { + this.master = master; + + } + + public ResourceHolder addToken(String namespace, String virtualPath) { + ResourceHolder holder = holderMap.get(namespace); + if (holder == null) { + holder = new ResourceHolder(this, namespace, virtualPath); + holderMap.put(namespace, holder); + } + return holder; + } + + public void removeToken(String namespace) { + holderMap.remove(namespace); + } + + public void preload() throws Exception { + for (ResourceHolder value : holderMap.values()) { + value.preload(); + } + } + + public ResourceLoader getLoader() { + return loader; + } + + public ResourceManager setLoader(ResourceLoader loader) { + this.loader = loader; + return this; + } + + public MissingProvider getMissingProvider() { + return missingProvider; + } + + public ResourceManager setMissingProvider(MissingProvider missingProvider) { + this.missingProvider = missingProvider; + return this; + } + +} diff --git a/src/main/java/xueli/game/resource/ResourceMaster.java b/src/main/java/xueli/game/resource/ResourceMaster.java new file mode 100644 index 00000000..38b9c08b --- /dev/null +++ b/src/main/java/xueli/game/resource/ResourceMaster.java @@ -0,0 +1,51 @@ +package xueli.game.resource; + +import java.util.ArrayList; + +import xueli.game2.resource.provider.ClassLoaderResourceProvider; +import xueli.game2.resource.provider.ResourceProvider; + +public class ResourceMaster { + + private ResourceProvider provider = new ClassLoaderResourceProvider(); + + private ArrayList> allManager = new ArrayList<>(); + private ImageResourceManager imageResourceManager; + private TextureResourceManager textureResourceManager; + + public ResourceMaster() { + imageResourceManager = new ImageResourceManager(this); + textureResourceManager = new TextureResourceManager(this); + + this.allManager.add(imageResourceManager); + this.allManager.add(textureResourceManager); + + } + + public ResourceMaster(ResourceProvider provider) { + this.provider = provider; + } + + public void preload() throws Exception { + for (ResourceManager manager : allManager) { + manager.preload(); + } + } + + public ResourceProvider getProvider() { + return provider; + } + + public ImageResourceManager getImageResourceManager() { + return imageResourceManager; + } + + public TextureResourceManager getTextureResourceManager() { + return textureResourceManager; + } + + public void release() { + + } + +} diff --git a/src/main/java/xueli/game/resource/TextureAtlasManager.java b/src/main/java/xueli/game/resource/TextureAtlasManager.java new file mode 100644 index 00000000..e4650d69 --- /dev/null +++ b/src/main/java/xueli/game/resource/TextureAtlasManager.java @@ -0,0 +1,59 @@ +package xueli.game.resource; + +import java.util.HashMap; + +import xueli.game.resource.texture.TextureAtlas; +import xueli.game.resource.texture.TextureAtlasBuilder; +import xueli.game.resource.texture.TextureAtlasHolder; + +class TextureAtlasManager extends ResourceManager { + + private final HashMap builders = new HashMap<>(); + private final HashMap atlases = new HashMap<>(); + + private HashMap namespaceToHolder = new HashMap<>(); + + TextureAtlasManager(ResourceMaster master) { + super(master); + + } + + @Override + public ResourceHolder addToken(String namespace, String virtualPath) { + throw new UnsupportedOperationException(""); + } + + public void addBuiltList(String namespace, TextureAtlasBuilder builder) { + builders.put(namespace, builder); + // System.out.println(namespace); + + builder.getTextureMaps().keySet().forEach(n -> { + TextureAtlasHolder holder = new TextureAtlasHolder(n); + namespaceToHolder.put(n, holder); + }); + + } + + @Override + public void preload() throws Exception { + // System.out.println(builders.size()); + builders.forEach((n, builder) -> { + TextureAtlas atlas = TextureAtlas.generateAtlas(builder); + atlases.put(n, atlas); + // System.out.println(atlas + ", " + builder.getTextureMaps().keySet().size()); + builder.getTextureMaps().keySet().forEach(namespace -> { + TextureAtlasHolder holder = namespaceToHolder.get(namespace); + holder.loadResult(atlas); + }); + }); + } + + public TextureAtlasHolder getHolder(String namespace) { + return namespaceToHolder.get(namespace); + } + + public TextureAtlas getAtlas(String namespace) { + return atlases.get(namespace); + } + +} diff --git a/src/main/java/xueli/game/resource/TextureMissing.java b/src/main/java/xueli/game/resource/TextureMissing.java new file mode 100644 index 00000000..4634d4e4 --- /dev/null +++ b/src/main/java/xueli/game/resource/TextureMissing.java @@ -0,0 +1,71 @@ +package xueli.game.resource; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVG.nvgCreateImageMem; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; + +import javax.imageio.ImageIO; + +import org.lwjgl.BufferUtils; + +import xueli.game.resource.texture.TextureAtlas; +import xueli.game.resource.texture.TextureAtlasHolder; + +public class TextureMissing { + + private static BufferedImage IMAGE_MISSING = new BufferedImage(16, 16, BufferedImage.TYPE_4BYTE_ABGR); + private static ByteBuffer imageData; + static { + for (int k = 0; k < 16; ++k) { + for (int l = 0; l < 16; ++l) { + if (k < 8 ^ l < 8) { + IMAGE_MISSING.setRGB(l, k, -524040); + } else { + IMAGE_MISSING.setRGB(l, k, -16777216); + } + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ImageIO.write(IMAGE_MISSING, "png", out); + } catch (IOException e) { + e.printStackTrace(); + } + imageData = BufferUtils.createByteBuffer(out.size()); + imageData.put(out.toByteArray()); + imageData.flip(); + + } + + private static HashMap missingImageMap = new HashMap<>(); + + public static NVGImage getMissingNvgImage(long nvg) { + NVGImage image = missingImageMap.get(nvg); + if (image == null) { + int imageId = nvgCreateImageMem(nvg, NVG_IMAGE_NEAREST, imageData); + image = new NVGImage(nvg, imageId); + missingImageMap.put(nvg, image); + } + + return image; + } + + private static TextureAtlas missingTextureAtlas; + private static TextureAtlasHolder missingTextureHolder; + + public static TextureAtlasHolder getMissingTexture() { + if (missingTextureAtlas == null) { + missingTextureAtlas = TextureAtlas.single(IMAGE_MISSING); + missingTextureHolder = missingTextureAtlas.getHolder(TextureAtlas.SINGLE); + } + + return missingTextureHolder; + } + +} diff --git a/src/main/java/xueli/game/resource/TextureResourceManager.java b/src/main/java/xueli/game/resource/TextureResourceManager.java new file mode 100644 index 00000000..e1e2a34b --- /dev/null +++ b/src/main/java/xueli/game/resource/TextureResourceManager.java @@ -0,0 +1,68 @@ +package xueli.game.resource; + +import xueli.game.resource.texture.TextureAtlas; +import xueli.game.resource.texture.TextureAtlasBuilder; +import xueli.game.resource.texture.TextureAtlasHolder; + +public class TextureResourceManager extends ResourceManager + implements ResourceLoader, MissingProvider { + + private TextureAtlasManager atlasManager; + private boolean textureAtlasPreloaded = false; + + TextureResourceManager(ResourceMaster master) { + super(master); + setLoader(this); + setMissingProvider(this); + + atlasManager = new TextureAtlasManager(master); + + } + + @Override + public ResourceHolder addToken(String namespace, String virtualPath) { + throw new UnsupportedOperationException(""); + } + + public TextureAtlasHolder addToken(String namespace) { + TextureAtlasHolder holder = atlasManager.getHolder(namespace); + holderMap.put(namespace, new ResourceHolder<>(null, namespace, null)); + if (holder == null) { + System.err.println("Can't find holder: " + namespace); + } + return holder; + } + + public void build(String namespace, TextureAtlasBuilder builder) { + atlasManager.addBuiltList(namespace, builder); + } + + @Override + public TextureAtlasHolder load(ResourceHolder holder) throws Exception { + checkAtlasManagerPreloaded(); + return atlasManager.getHolder(holder.namespace); + } + + @Override + public void preload() throws Exception { + checkAtlasManagerPreloaded(); + } + + private void checkAtlasManagerPreloaded() throws Exception { + if (textureAtlasPreloaded) + return; + this.atlasManager.preload(); + // System.out.println("checked"); + textureAtlasPreloaded = true; + } + + @Override + public void onMissing(ResourceHolder holder) { + holder.result = TextureMissing.getMissingTexture(); + } + + public TextureAtlas getAtlas(String namespace) { + return atlasManager.getAtlas(namespace); + } + +} diff --git a/src/main/java/xueli/game/resource/package-info.java b/src/main/java/xueli/game/resource/package-info.java new file mode 100644 index 00000000..0edcef49 --- /dev/null +++ b/src/main/java/xueli/game/resource/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.game.resource; \ No newline at end of file diff --git a/src/main/java/xueli/game/resource/texture/Texture.java b/src/main/java/xueli/game/resource/texture/Texture.java new file mode 100644 index 00000000..896c285e --- /dev/null +++ b/src/main/java/xueli/game/resource/texture/Texture.java @@ -0,0 +1,96 @@ +package xueli.game.resource.texture; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL13; + +import xueli.utils.logger.Logger; + +public class Texture { + + public static final Texture NULL = new Texture(0, 0, 0); + + private int id, width, height; + + public Texture(int id, int imageWidth, int imageHeight) { + this.id = id; + this.width = imageWidth; + this.height = imageHeight; + + } + + public int getId() { + return id; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public void bind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + } + + public void unbind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + public static Texture loadTexture(InputStream in) throws IOException { + BufferedImage image = ImageIO.read(in); + if (image == null) { + Logger.getInstance().warning("Can't read image file: " + in.toString()); + return Texture.NULL; + } + + return loadTexture(image); + } + + public static Texture loadTexture(String path) throws IOException { + BufferedImage image = ImageIO.read(new File(path)); + if (image == null) { + Logger.getInstance().warning("Can't read image file: " + path); + return Texture.NULL; + } + + return loadTexture(image); + } + + private static Texture loadTexture(BufferedImage image) { + int[] pixels = new int[image.getWidth() * image.getHeight()]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); + + int[] data = new int[image.getWidth() * image.getHeight()]; + for (int i = 0; i < data.length; i++) { + int temp=pixels[i]; + temp&=0xFF00FF00; + temp|=(data[i]&0xFF)<<16; + temp|=(data[i]>>16)&0xFF; + data[i]=temp; + } + + int id = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 0); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, image.getWidth(), image.getHeight(), 0, GL11.GL_RGBA, + GL11.GL_UNSIGNED_BYTE, data); + + return new Texture(id, image.getWidth(), image.getHeight()); + } + +} diff --git a/src/main/java/xueli/game/resource/texture/TextureAtlas.java b/src/main/java/xueli/game/resource/texture/TextureAtlas.java new file mode 100644 index 00000000..5213a9d1 --- /dev/null +++ b/src/main/java/xueli/game/resource/texture/TextureAtlas.java @@ -0,0 +1,280 @@ +package xueli.game.resource.texture; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.imageio.ImageIO; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL13; +import org.lwjgl.utils.vector.Vector2i; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonReader; + +import xueli.utils.logger.Logger; + +public class TextureAtlas { + + public static final String SINGLE = "S"; + + private int width = 0, height = 0; + private int id; + private HashMap atlas; + + TextureAtlas(HashMap atlas, int width, int height, int id) { + this.id = id; + this.width = width; + this.height = height; + this.atlas = atlas; + + } + + public void bind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + } + + public void unbind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + public void release() { + GL11.glDeleteTextures(id); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public Vector2i getAtlas(String key) { + return atlas.get(key); + } + + public TextureAtlasHolder getHolder(String namespace) { + TextureAtlasHolder holder = new TextureAtlasHolder(namespace); + holder.loadResult(this); + return holder; + } + + public static TextureAtlas single(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + + int[] pixels = new int[width * height]; + image.getRGB(0, 0, width, height, pixels, 0, width); + + int[] data = new int[width * height]; + for (int i = 0; i < width * height; i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + data[i] = a << 24 | b << 16 | g << 8 | r; + } + + int id = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 0); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, + data); + + HashMap atlas = new HashMap<>(); + atlas.put(SINGLE, new Vector2i(0, 0)); + + return new TextureAtlas(atlas, width, height, id); + } + + public static TextureAtlas generateAtlas(TextureAtlasBuilder builder) { + HashMap textureMaps = builder.textureMaps; + + String[] names = new String[textureMaps.size()]; + BufferedImage[] images = new BufferedImage[textureMaps.size()]; + + int per_width = 0, per_height = 0; + + int count = 0; + try { + for (Entry e : textureMaps.entrySet()) { + String name = e.getKey(); + URL imgUrl = e.getValue(); + BufferedImage image = ImageIO.read(imgUrl); + if (image == null) { + Logger.getInstance().warning("Can't read image: " + imgUrl.toString()); + continue; + } + per_width = Math.max(per_width, image.getWidth()); + per_height = Math.max(per_height, image.getHeight()); + + names[count] = name; + images[count] = image; + count++; + + } + } catch (IOException e) { + e.printStackTrace(); + } + + int size = (int) Math.ceil(Math.sqrt(images.length)); + + HashMap atlas = new HashMap<>(); + + BufferedImage atlasImage = new BufferedImage(per_width * size, per_height * size, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D g2d = atlasImage.createGraphics(); + + for (int i = 0; i < images.length; i++) { + BufferedImage image = images[i]; + + int x = i % size; + int y = i / size; + atlas.put(names[i], new Vector2i(x, y)); + + g2d.drawImage(image, x * per_width, y * per_height, null); + + } + + g2d.dispose(); + /* + * try { ImageIO.write(atlasImage, "png", new File("temp/atlas.png")); } catch + * (IOException e) { e.printStackTrace(); } + */ + + int[] pixels = new int[atlasImage.getWidth() * atlasImage.getHeight()]; + atlasImage.getRGB(0, 0, atlasImage.getWidth(), atlasImage.getHeight(), pixels, 0, atlasImage.getWidth()); + + int[] data = new int[atlasImage.getWidth() * atlasImage.getHeight()]; + for (int i = 0; i < atlasImage.getWidth() * atlasImage.getHeight(); i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + data[i] = a << 24 | b << 16 | g << 8 | r; + } + + int id = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 0); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, atlasImage.getWidth(), atlasImage.getHeight(), 0, + GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data); + + return new TextureAtlas(atlas, size, size, id); + } + + public static TextureAtlas generateAtlas(String defineJsonPath, String textureFolderString) { + JsonObject obj = null; + try { + obj = new Gson().fromJson(new JsonReader(new BufferedReader(new FileReader(defineJsonPath))), + JsonObject.class); + } catch (JsonIOException | JsonSyntaxException | FileNotFoundException e1) { + e1.printStackTrace(); + } + JsonObject blocksObj = obj.get("blocks").getAsJsonObject(); + + String[] names = new String[blocksObj.entrySet().size()]; + BufferedImage[] images = new BufferedImage[blocksObj.entrySet().size()]; + + int per_width = 0, per_height = 0; + + int count = 0; + try { + for (Entry e : blocksObj.entrySet()) { + String name = e.getKey(); + String imgPath = e.getValue().getAsString(); + + File imgFile = new File(textureFolderString + File.separator + imgPath); + BufferedImage image = ImageIO.read(imgFile); + if (image == null) { + Logger.getInstance().warning("Can't read image: " + imgPath); + continue; + } + per_width = Math.max(per_width, image.getWidth()); + per_height = Math.max(per_height, image.getHeight()); + + names[count] = name; + images[count] = image; + count++; + + } + } catch (IOException e) { + e.printStackTrace(); + } + + int size = (int) Math.ceil(Math.sqrt(images.length)); + + HashMap atlas = new HashMap<>(); + + BufferedImage atlasImage = new BufferedImage(per_width * size, per_height * size, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D g2d = atlasImage.createGraphics(); + + for (int i = 0; i < images.length; i++) { + BufferedImage image = images[i]; + + int x = i % size; + int y = i / size; + atlas.put(names[i], new Vector2i(x, y)); + + g2d.drawImage(image, x * per_width, y * per_height, null); + + } + + g2d.dispose(); + /* + * try { ImageIO.write(atlasImage, "png", new File("temp/atlas.png")); } catch + * (IOException e) { e.printStackTrace(); } + */ + + int[] pixels = new int[atlasImage.getWidth() * atlasImage.getHeight()]; + atlasImage.getRGB(0, 0, atlasImage.getWidth(), atlasImage.getHeight(), pixels, 0, atlasImage.getWidth()); + + int[] data = new int[atlasImage.getWidth() * atlasImage.getHeight()]; + for (int i = 0; i < atlasImage.getWidth() * atlasImage.getHeight(); i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + data[i] = a << 24 | b << 16 | g << 8 | r; + } + + int id = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 0); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4); + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, atlasImage.getWidth(), atlasImage.getHeight(), 0, + GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data); + + return new TextureAtlas(atlas, size, size, id); + } + +} diff --git a/src/main/java/xueli/game/resource/texture/TextureAtlasBuilder.java b/src/main/java/xueli/game/resource/texture/TextureAtlasBuilder.java new file mode 100644 index 00000000..7a604402 --- /dev/null +++ b/src/main/java/xueli/game/resource/texture/TextureAtlasBuilder.java @@ -0,0 +1,83 @@ +package xueli.game.resource.texture; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; + +import xueli.game2.resource.provider.ResourceProvider; +import xueli.utils.io.Files; + +public class TextureAtlasBuilder { + +// private static final MyLogger LOGGER = new MyLogger() { +// { +// pushState("TextureAtlasBuilder"); +// } +// }; + + HashMap textureMaps = new HashMap<>(); + + public TextureAtlasBuilder() { + + } + + public TextureAtlasBuilder add(String namespace, String virtualPath, ResourceProvider provider) { +// URL url = provider.virtualPathToUrl(virtualPath); +// if(url == null) { +// LOGGER.warning("Can't find virtual path: " + virtualPath); +// } else { +// textureMaps.put(namespace, url); +// } + + return this; + } + + public TextureAtlasBuilder add(String namespace, String path) { + File file = new File(path); + URL url = null; + try { + url = file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + textureMaps.put(namespace, url); + return this; + } + + public TextureAtlasBuilder iterate(String virtualPath, ResourceProvider provider, boolean onlyCurrentModule) { +// String[] virtualPaths = provider.iterateFile(virtualPath, onlyCurrentModule); +// // System.out.println(Arrays.toString(virtualPaths)); +// for (String path : virtualPaths) { +// String name = Files.getNameExcludeSuffix(path); +// this.add(name, path, provider); +// // System.out.println(name); +// } + return this; + } + + public TextureAtlasBuilder iterate(File folder) { + File[] files = folder.listFiles(pathname -> pathname.getPath().endsWith(".png")); + assert files != null; + for (File file : files) { + String name = Files.getNameExcludeSuffix(file.getName()); + this.add(name, file.getPath()); + } + + return this; + } + + public static TextureAtlasBuilder iterateFiles(String virtualPath, ResourceProvider provider, + boolean onlyCurrentModule) { + return new TextureAtlasBuilder().iterate(virtualPath, provider, onlyCurrentModule); + } + + public static TextureAtlasBuilder single(String namespace, String virtualPath, ResourceProvider provider) { + return new TextureAtlasBuilder().add(namespace, virtualPath, provider); + } + + public HashMap getTextureMaps() { + return textureMaps; + } + +} diff --git a/src/main/java/xueli/game/resource/texture/TextureAtlasHolder.java b/src/main/java/xueli/game/resource/texture/TextureAtlasHolder.java new file mode 100644 index 00000000..2bb69f1d --- /dev/null +++ b/src/main/java/xueli/game/resource/texture/TextureAtlasHolder.java @@ -0,0 +1,76 @@ +package xueli.game.resource.texture; + +import java.util.Objects; + +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector2i; + +public class TextureAtlasHolder { + + private String name; + private Vector2f offsetFrom, offsetTo; + + private boolean hasResult = false; + private TextureAtlas atlas; + public Vector2f p_left_top, p_left_down, p_right_top, p_right_down; + private int atlasWidth, atlasHeight; + + public TextureAtlasHolder(String name) { + this(name, new Vector2f(0, 0), new Vector2f(1, 1)); + } + + public TextureAtlasHolder(String name, Vector2f offsetFrom, Vector2f offsetTo) { + this.name = name; + this.offsetFrom = offsetFrom; + this.offsetTo = offsetTo; + + } + + public void loadResult(TextureAtlas atlas) { + this.atlas = atlas; + + Vector2i index = atlas.getAtlas(name); + if (index == null) { + throw new RuntimeException("Found no atlas named: " + name); + } + + this.atlasWidth = atlas.getWidth(); + this.atlasHeight = atlas.getHeight(); + + p_left_top = new Vector2f((float) (index.x + offsetFrom.getX()) / atlasWidth, + (float) (index.y + offsetFrom.getY()) / atlasHeight); + p_left_down = new Vector2f((float) (index.x + offsetFrom.getX()) / atlasWidth, + (float) (index.y + offsetTo.getY()) / atlasHeight); + p_right_top = new Vector2f((float) (index.x + offsetTo.getX()) / atlasWidth, + (float) (index.y + offsetFrom.getY()) / atlasHeight); + p_right_down = new Vector2f((float) (index.x + offsetTo.getX()) / atlasWidth, + (float) (index.y + offsetTo.getY()) / atlasHeight); + + this.hasResult = true; + + } + + public TextureAtlas getAtlas() { + return atlas; + } + + public boolean hasResult() { + return hasResult; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + TextureAtlasHolder that = (TextureAtlasHolder) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + +} diff --git a/src/main/java/xueli/game/resource/texture/package-info.java b/src/main/java/xueli/game/resource/texture/package-info.java new file mode 100644 index 00000000..75861b81 --- /dev/null +++ b/src/main/java/xueli/game/resource/texture/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.game.resource.texture; \ No newline at end of file diff --git a/src/main/java/xueli/game/ui/FontManager.java b/src/main/java/xueli/game/ui/FontManager.java new file mode 100644 index 00000000..4ff9a998 --- /dev/null +++ b/src/main/java/xueli/game/ui/FontManager.java @@ -0,0 +1,9 @@ +package xueli.game.ui; + +public class FontManager { + + public FontManager() { + + } + +} diff --git a/src/main/java/xueli/game/ui/ImageManager.java b/src/main/java/xueli/game/ui/ImageManager.java new file mode 100644 index 00000000..2aa9e15e --- /dev/null +++ b/src/main/java/xueli/game/ui/ImageManager.java @@ -0,0 +1,49 @@ +package xueli.game.ui; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVG.nvgCreateImageMem; +import static org.lwjgl.nanovg.NanoVG.nvgDeleteImage; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; + +import xueli.utils.io.Files; + +public class ImageManager { + + private UIRenderer renderer; + private HashMap imagesHashMap = new HashMap<>(); + + public ImageManager(UIRenderer renderer) { + this.renderer = renderer; + + } + + public int getImage(String pathInJar) { + Integer imageInteger = imagesHashMap.get(pathInJar); + + if (imageInteger == null) { + ByteBuffer buffer = null; + try { + buffer = Files.readResourcePackedInJarAndPackedToBuffer(pathInJar); + } catch (IOException e) { + e.printStackTrace(); + return 0; + } + + imageInteger = nvgCreateImageMem(renderer.nvg, NVG_IMAGE_NEAREST, buffer); + imagesHashMap.put(pathInJar, imageInteger); + + } + + return imageInteger; + } + + public void release() { + imagesHashMap.values().forEach(i -> { + nvgDeleteImage(renderer.nvg, i); + }); + } + +} diff --git a/src/main/java/xueli/game/ui/UI.java b/src/main/java/xueli/game/ui/UI.java new file mode 100644 index 00000000..94708177 --- /dev/null +++ b/src/main/java/xueli/game/ui/UI.java @@ -0,0 +1,35 @@ +package xueli.game.ui; + +import static org.lwjgl.nanovg.NanoVG.nvgBeginPath; +import static org.lwjgl.nanovg.NanoVG.nvgFill; +import static org.lwjgl.nanovg.NanoVG.nvgFillColor; +import static org.lwjgl.nanovg.NanoVG.nvgFillPaint; +import static org.lwjgl.nanovg.NanoVG.nvgImagePattern; +import static org.lwjgl.nanovg.NanoVG.nvgRect; + +import org.lwjgl.nanovg.NVGColor; +import org.lwjgl.nanovg.NVGPaint; + +public class UI { + + private static NVGPaint paint = NVGPaint.create(); + + private UI() { + } + + public static void drawTexture(long nvg, float x, float y, float width, float height, int image) { + nvgImagePattern(nvg, x, y, width, height, 0, image, 1.0f, paint); + nvgBeginPath(nvg); + nvgRect(nvg, x, y, width, height); + nvgFillPaint(nvg, paint); + nvgFill(nvg); + } + + public static void drawRect(long nvg, float x, float y, float width, float height, NVGColor color) { + nvgBeginPath(nvg); + nvgRect(nvg, x, y, width, height); + nvgFillColor(nvg, color); + nvgFill(nvg); + } + +} diff --git a/src/main/java/xueli/game/ui/UIRenderer.java b/src/main/java/xueli/game/ui/UIRenderer.java new file mode 100644 index 00000000..83b6c6ad --- /dev/null +++ b/src/main/java/xueli/game/ui/UIRenderer.java @@ -0,0 +1,94 @@ +package xueli.game.ui; + +import static org.lwjgl.nanovg.NanoVG.nvgBeginFrame; +import static org.lwjgl.nanovg.NanoVG.nvgEndFrame; +import static org.lwjgl.nanovg.NanoVGGL3.NVG_ANTIALIAS; +import static org.lwjgl.nanovg.NanoVGGL3.NVG_DEBUG; +import static org.lwjgl.nanovg.NanoVGGL3.NVG_STENCIL_STROKES; +import static org.lwjgl.nanovg.NanoVGGL3.nvgCreate; + +import java.util.Stack; + +import xueli.game.Game; +import xueli.game.renderer.Renderer; + +public class UIRenderer implements Renderer { + + private Game game; + long nvg; + + private ImageManager imageManager; + + private Stack views = new Stack<>(); + + public UIRenderer(Game game) { + this.game = game; + + this.nvg = nvgCreate(NVG_STENCIL_STROKES | NVG_ANTIALIAS | NVG_DEBUG); + if (this.nvg == 0) { + game.announceCrash("UI Init", new RuntimeException("Couldn't init NanoVG!")); + } + + this.imageManager = new ImageManager(this); + + } + + public void pushUIView(UIView view) { + views.push(view); + } + + public void popUIView() { + UIView view = views.pop(); + if (view != null) { + view.release(); + } + } + + @Override + public void render() { + views.forEach(view -> { + view.update(); + }); + + nvgBeginFrame(nvg, game.getWidth(), game.getHeight(), game.getWidth() / game.getHeight()); + views.forEach(view -> { + view.render(); + }); + nvgEndFrame(nvg); + + } + + @Override + public void update() { + } + + @Override + public void size() { + views.forEach(view -> { + view.requestRepaint(); + }); + } + + @Override + public void release() { + views.forEach(view -> { + view.release(); + }); + + imageManager.release(); + + } + + public Game getGame() { + return game; + } + + public long getNvg() { + return nvg; + } + + public ImageManager getImageManager() { + return imageManager; + } + +} diff --git a/src/main/java/xueli/game/ui/UIView.java b/src/main/java/xueli/game/ui/UIView.java new file mode 100644 index 00000000..c6aeb174 --- /dev/null +++ b/src/main/java/xueli/game/ui/UIView.java @@ -0,0 +1,130 @@ +package xueli.game.ui; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVG.nvgBeginFrame; +import static org.lwjgl.nanovg.NanoVG.nvgEndFrame; +import static org.lwjgl.nanovg.NanoVGGL3.nvgluBindFramebuffer; +import static org.lwjgl.nanovg.NanoVGGL3.nvgluCreateFramebuffer; +import static org.lwjgl.nanovg.NanoVGGL3.nvgluDeleteFramebuffer; + +import java.util.ArrayList; + +import org.lwjgl.nanovg.NVGColor; +import org.lwjgl.nanovg.NVGLUFramebuffer; +import org.lwjgl.opengl.GL11; + +import xueli.game.renderer.Renderer; +import xueli.game.utils.NVGColors; + +public abstract class UIView implements Renderer { + + private UIRenderer ctxRenderer; + + private NVGLUFramebuffer framebuffer; + public static final NVGColor ColorFrameBufferNull = NVGColors.DARK_GRAY; + private boolean needRepaint = true; + + private float width = 0, height = 0; + + UIView fatherComponent; + private ArrayList subCompnents = new ArrayList<>(); + + public UIView(UIRenderer ctx) { + this.ctxRenderer = ctx; + + } + + @Override + public void render() { + if (framebuffer != null) { + UI.drawTexture(ctxRenderer.nvg, 0, 0, width, height, framebuffer.image()); + } else { + UI.drawRect(ctxRenderer.nvg, 0, 0, width, height, ColorFrameBufferNull); + } + + } + + @Override + public void update() { + subCompnents.forEach(UIView::update); + + if (needRepaint) { + size(); + stroke(); + needRepaint = false; + } + + } + + public abstract void stroke(); + + @Override + public void size() { + this.width = ctxRenderer.getGame().getWidth(); + this.height = ctxRenderer.getGame().getHeight(); + + if (framebuffer != null) { + nvgluDeleteFramebuffer(ctxRenderer.nvg, framebuffer); + } + framebuffer = nvgluCreateFramebuffer(ctxRenderer.nvg, (int) width, (int) height, NVG_IMAGE_NEAREST); + + subCompnents.forEach(UIView::requestRepaint); + + } + + @Override + public void release() { + if (framebuffer != null) { + nvgluDeleteFramebuffer(ctxRenderer.nvg, framebuffer); + } + + subCompnents.forEach(UIView::release); + + } + + protected void bindFrameBuffer() { + nvgluBindFramebuffer(ctxRenderer.nvg, framebuffer); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT); + nvgBeginFrame(ctxRenderer.nvg, getWidth(), getHeight(), getWidth() / getHeight()); + } + + protected void unbindFrameBuffer() { + nvgEndFrame(ctxRenderer.nvg); + nvgluBindFramebuffer(ctxRenderer.nvg, null); + } + + public T addView(T view) { + subCompnents.add(view); + view.fatherComponent = this; + return view; + } + + public T removeView(T view) { + subCompnents.remove(view); + return view; + } + + public float getWidth() { + return width; + } + + public float getHeight() { + return height; + } + + public UIRenderer getCtxRenderer() { + return ctxRenderer; + } + + public int getImage(String pathInJar) { + return ctxRenderer.getImageManager().getImage(pathInJar); + } + + public void requestRepaint() { + this.needRepaint = true; + if (this.fatherComponent != null) { + this.fatherComponent.requestRepaint(); + } + } + +} diff --git a/src/main/java/xueli/game/ui/package-info.java b/src/main/java/xueli/game/ui/package-info.java new file mode 100644 index 00000000..bf14c585 --- /dev/null +++ b/src/main/java/xueli/game/ui/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.game.ui; \ No newline at end of file diff --git a/src/main/java/xueli/game/utils/FloatList.java b/src/main/java/xueli/game/utils/FloatList.java new file mode 100644 index 00000000..7ff106a9 --- /dev/null +++ b/src/main/java/xueli/game/utils/FloatList.java @@ -0,0 +1,131 @@ +package xueli.game.utils; + +import java.nio.FloatBuffer; +import java.util.Arrays; + +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector3f; +import org.lwjgl.utils.vector.Vector4b; +import org.lwjgl.utils.vector.Vector4f; + +/** + * įģčŋ‡æĩ‹č¯•īŧŒæ€§čƒŊæ˛Ąæœ‰į›´æŽĨįš„floatBufferé̘īŧŒ äŊ†æ˜¯čŋ˜æ˜¯æŒēåĨŊį”¨įš„ 因ä¸ēfloatBufferå¤§å°åˇ˛įģé™åˆļæ­ģäē† + * Please use LotsOfByteBuffer instead! + */ +@Deprecated +public class FloatList { + + private float[] data; + private int capacity; + + private int pointer; + private int size; + + private boolean hasChanged = false; + private float[] realDataCache = null; + + public FloatList() { + this(32); + + } + + public FloatList(int capacity) { + if (capacity <= 0) + throw new IllegalArgumentException("Capacity not bigger than zero: " + capacity); + + this.data = new float[capacity]; + this.pointer = 0; + this.size = 0; + this.capacity = capacity; + + } + + public void clear() { + this.pointer = 0; + this.size = 0; + this.hasChanged = true; + + } + + public FloatList put(float v) { + data[pointer] = v; + pointer++; + size = Math.max(pointer, size); + + if (pointer == data.length) { + this.capacity += 4096; + float[] data2 = new float[this.capacity]; + System.arraycopy(data, 0, data2, 0, data.length); + this.data = data2; + + } + + hasChanged = true; + + return this; + } + + public FloatList put(Vector4b v) { + int bits = (v.x << 24) | (v.y << 16) | (v.z << 8) | v.w; + return put(Float.intBitsToFloat(bits)); + } + + public FloatList put(Vector3f v) { + return put(v.x).put(v.y).put(v.z); + } + + public FloatList put(Vector2f v) { + return put(v.x).put(v.y); + } + + public FloatList put(Vector4f v) { + return put(v.x).put(v.y).put(v.z).put(v.w); + } + + public int getPointer() { + return pointer; + } + + public void setPointer(int pointer) { + if (this.pointer >= this.capacity) + throw new IllegalArgumentException("pointer bigger than capacity: " + pointer); + this.pointer = pointer; + } + + public int getSize() { + return size; + } + + public float[] getData() { + if (realDataCache == null || this.hasChanged) { + float[] realData = new float[size]; + System.arraycopy(data, 0, realData, 0, size); + this.realDataCache = realData; + hasChanged = false; + + } + return this.realDataCache; + } + + public float[] getDataPointer() { + return data; + } + + public void storeInBuffer(FloatBuffer buffer) { + if (data == null) + return; + buffer.put(data, 0, size); + } + + public void postDispose() { + this.data = null; + realDataCache = null; + + } + + @Override + public String toString() { + return Arrays.toString(getData()); + } + +} diff --git a/src/main/java/xueli/game/utils/Light.java b/src/main/java/xueli/game/utils/Light.java new file mode 100644 index 00000000..5bbadfe3 --- /dev/null +++ b/src/main/java/xueli/game/utils/Light.java @@ -0,0 +1,54 @@ +package xueli.game.utils; + +import org.lwjgl.utils.vector.Vector4b; + +public class Light { + + public static final Light FULL_LIGHT = new Light((byte) 15, (byte) 15, (byte) 15, (byte) 15); + + private byte sun, r, g, b; + + public Light(byte sun, byte r, byte g, byte b) { + this.sun = sun; + this.r = r; + this.g = g; + this.b = b; + } + + public byte getSunLight() { + return sun; + } + + public void setSunLight(byte sun) { + this.sun = sun; + } + + public byte getR() { + return r; + } + + public void setR(byte r) { + this.r = r; + } + + public byte getG() { + return g; + } + + public void setG(byte g) { + this.g = g; + } + + public byte getB() { + return b; + } + + public void setB(byte b) { + this.b = b; + } + + public Vector4b getLightBuffer() { + return new Vector4b(r, g, b, sun); + } + +} diff --git a/src/main/java/xueli/game/utils/NVGColors.java b/src/main/java/xueli/game/utils/NVGColors.java new file mode 100644 index 00000000..7a5eb353 --- /dev/null +++ b/src/main/java/xueli/game/utils/NVGColors.java @@ -0,0 +1,39 @@ +package xueli.game.utils; + +import static org.lwjgl.nanovg.NanoVG.nvgRGB; +import static org.lwjgl.nanovg.NanoVG.nvgRGBA; + +import org.lwjgl.nanovg.NVGColor; + +@Deprecated +public class NVGColors { + + public static NVGColor GREEN = NVGColor.create(); + public static NVGColor BLUE = NVGColor.create(); + public static NVGColor WHITE = NVGColor.create(); + public static NVGColor BLACK = NVGColor.create(); + public static NVGColor YELLOW = NVGColor.create(); + public static NVGColor PURPLE = NVGColor.create(); + public static NVGColor DARK_GRAY = NVGColor.create(); + public static NVGColor TRANSPARENT_BLACK = NVGColor.create(); + + static { + nvgRGB((byte) 0, (byte) 255, (byte) 0, GREEN); + nvgRGB((byte) 0, (byte) 0, (byte) 255, BLUE); + nvgRGB((byte) 255, (byte) 255, (byte) 255, WHITE); + nvgRGB((byte) 0, (byte) 0, (byte) 0, BLACK); + nvgRGB((byte) 255, (byte) 255, (byte) 0, YELLOW); + nvgRGB((byte) 128, (byte) 0, (byte) 128, PURPLE); + nvgRGB((byte) 60, (byte) 60, (byte) 60, DARK_GRAY); + nvgRGBA((byte) 0, (byte) 0, (byte) 0, (byte) 128, TRANSPARENT_BLACK); + + } + + public static NVGColor nvgColor(int c) { + NVGColor color = NVGColor.create(); + nvgRGBA((byte) (c & 0xFF), (byte) ((c >> 8) & 0xFF), (byte) ((c >> 16) & 0xFF), (byte) ((c >> 24) & 0xFF), + color); + return color; + } + +} diff --git a/src/main/java/xueli/game/utils/math/MathUtils.java b/src/main/java/xueli/game/utils/math/MathUtils.java new file mode 100644 index 00000000..264311ae --- /dev/null +++ b/src/main/java/xueli/game/utils/math/MathUtils.java @@ -0,0 +1,76 @@ +package xueli.game.utils.math; + +import java.math.BigDecimal; + +@Deprecated +public class MathUtils { + + public static float doubleToFloat(double value) { + if (Double.isNaN(value)) + return -Float.MAX_VALUE; + return new BigDecimal(String.valueOf(value)).floatValue(); + } + + public static double floatToDouble(float value) { + if (Float.isNaN(value)) + return -Double.MAX_VALUE; + return new BigDecimal(String.valueOf(value)).doubleValue(); + } + + public static int floatToInt(float value) { + if (Double.isNaN(value)) + return -Integer.MAX_VALUE; + return (int) Math.floor(value); + } + + public static int floatToInt2(float value) { + return (int) value; + } + + public static long vert2ToLong(int x, int z) { + return (long) x & 4294967295L | ((long) z & 4294967295L) << 32; + } + + public static float interpolate(float a, float b, float blend) { + double theta = blend * Math.PI; + float f = (float) (1f - Math.cos(theta)) * 0.5f; + return a * (1f - f) + b * f; + } + + public static float mixLinear(float a, float b, float blend) { + return blend * (b - a) + a; + } + + public static double min(double... nums) { + double min = nums[0]; + for (int i = 0; i < nums.length; i++) { + min = Math.min(min, nums[i]); + } + return min; + } + + public static int min(int... nums) { + int min = nums[0]; + for (int i = 0; i < nums.length; i++) { + min = Math.min(min, nums[i]); + } + return min; + } + + public static double max(double... nums) { + double max = nums[0]; + for (int i = 0; i < nums.length; i++) { + max = Math.max(max, nums[i]); + } + return max; + } + + public static int max(int... nums) { + int max = nums[0]; + for (int i = 0; i < nums.length; i++) { + max = Math.max(max, nums[i]); + } + return max; + } + +} diff --git a/src/main/java/xueli/game2/Timer.java b/src/main/java/xueli/game2/Timer.java new file mode 100644 index 00000000..18e96593 --- /dev/null +++ b/src/main/java/xueli/game2/Timer.java @@ -0,0 +1,61 @@ +package xueli.game2; + +public class Timer { + + private static final int DUE_TICK_PER_SECONDS = 20; + private static final double DUE_SECOND_PER_TICK = 1.0 * 1000 / DUE_TICK_PER_SECONDS; + private static final int MAX_TICK_PER_INVOKE = 100; + + private long lastTime = 0; + + private int numShouldTick = 0; + private int numHasTick = 0; + + private long remain = 0; + private float remainProgress = 0; + + public void tick() { + if (lastTime == 0) { + lastTime = System.currentTimeMillis(); + return; + } + + long thisTime = System.currentTimeMillis(); + long delta = thisTime - lastTime; + remain += delta; + + this.numShouldTick = (int) (this.remain / DUE_SECOND_PER_TICK); + this.remain -= this.numShouldTick * DUE_SECOND_PER_TICK; + this.remainProgress = (float) (this.remain / DUE_SECOND_PER_TICK); + + int numExceed = this.numShouldTick - MAX_TICK_PER_INVOKE; + if (numExceed > 0) { + System.out.printf("Can't keep up! Drop %d ticks%n", numExceed); + this.numShouldTick = MAX_TICK_PER_INVOKE; + } + + lastTime = thisTime; + + } + + @Deprecated + public void runTick(Runnable tick) { + for (int i = 0; i < numShouldTick; i++) { + this.numHasTick++; + tick.run(); + } + } + + public float getRemainProgress() { + return remainProgress; + } + + public int getNumHasTick() { + return numHasTick; + } + + public int getNumShouldTick() { + return numShouldTick; + } + +} diff --git a/src/main/java/xueli/game2/Vector.java b/src/main/java/xueli/game2/Vector.java new file mode 100644 index 00000000..f1a9abfd --- /dev/null +++ b/src/main/java/xueli/game2/Vector.java @@ -0,0 +1,40 @@ +package xueli.game2; + +import java.io.Serializable; + +public class Vector implements Serializable { + + private static final long serialVersionUID = 1957284979693368762L; + + public double x; + public double y; + public double z; + public double rotX, rotY, rotZ; + + public Vector() { + x = y = z = rotX = rotY = rotZ = 0; + } + + public Vector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + rotX = rotY = rotZ = 0; + } + + public Vector(double x, double y, double z, double rotX, double rotY, double rotZ) { + this.x = x; + this.y = y; + this.z = z; + this.rotX = rotX; + this.rotY = rotY; + this.rotZ = rotZ; + } + + @Override + public String toString() { + return "Vector [x=" + x + ", y=" + y + ", z=" + z + ", rotX=" + rotX + ", rotY=" + rotY + ", rotZ=" + rotZ + + "]"; + } + +} diff --git a/src/main/java/xueli/game2/camera3d/BoundCamera.java b/src/main/java/xueli/game2/camera3d/BoundCamera.java new file mode 100644 index 00000000..5eab52bb --- /dev/null +++ b/src/main/java/xueli/game2/camera3d/BoundCamera.java @@ -0,0 +1,27 @@ +package xueli.game2.camera3d; + +import org.lwjgl.utils.vector.Matrix4f; + +public class BoundCamera implements ICamera { + + private ICamera camera; + + public BoundCamera(ICamera camera) { + this.camera = camera; + } + + @Override + public Matrix4f getCameraMatrix() { + if (camera == null) { + Matrix4f matrix = new Matrix4f(); + matrix.setIdentity(); + return matrix; + } + return camera.getCameraMatrix(); + } + + public void setCamera(ICamera camera) { + this.camera = camera; + } + +} diff --git a/src/main/java/xueli/game2/camera3d/ICamera.java b/src/main/java/xueli/game2/camera3d/ICamera.java new file mode 100644 index 00000000..d86ab643 --- /dev/null +++ b/src/main/java/xueli/game2/camera3d/ICamera.java @@ -0,0 +1,9 @@ +package xueli.game2.camera3d; + +import org.lwjgl.utils.vector.Matrix4f; + +public interface ICamera { + + public Matrix4f getCameraMatrix(); + +} diff --git a/src/main/java/xueli/game2/camera3d/MovableCamera.java b/src/main/java/xueli/game2/camera3d/MovableCamera.java new file mode 100644 index 00000000..0fb468eb --- /dev/null +++ b/src/main/java/xueli/game2/camera3d/MovableCamera.java @@ -0,0 +1,45 @@ +package xueli.game2.camera3d; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.Vector; + +public class MovableCamera extends Vector implements ICamera { + + private static final long serialVersionUID = 5715380559084534935L; + + public MovableCamera() { + super(); + } + + public MovableCamera(float x, float y, float z) { + super(x, y, z); + } + + public MovableCamera(float x, float y, float z, float rotX, float rotY, float rotZ) { + super(x, y, z, rotX, rotY, rotZ); + } + + public MovableCamera(Vector other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + this.rotX = other.rotX; + this.rotY = other.rotY; + this.rotZ = other.rotZ; + } + + @Override + public Matrix4f getCameraMatrix() { + Matrix4f viewMatrix = new Matrix4f(); + viewMatrix.setIdentity(); + Matrix4f.rotate((float) Math.toRadians(-this.rotX), new Vector3f(1, 0, 0), viewMatrix, viewMatrix); + Matrix4f.rotate((float) Math.toRadians(this.rotY), new Vector3f(0, 1, 0), viewMatrix, viewMatrix); + Matrix4f.rotate((float) Math.toRadians(this.rotZ), new Vector3f(0, 0, 1), viewMatrix, viewMatrix); + Vector3f negativeCamPos = new Vector3f((float) -this.x, (float) -this.y, (float) -this.z); + Matrix4f.translate(negativeCamPos, viewMatrix, viewMatrix); + return viewMatrix; + } + +} diff --git a/src/main/java/xueli/game2/display/CursorPositionListener.java b/src/main/java/xueli/game2/display/CursorPositionListener.java new file mode 100644 index 00000000..42df2b23 --- /dev/null +++ b/src/main/java/xueli/game2/display/CursorPositionListener.java @@ -0,0 +1,5 @@ +package xueli.game2.display; + +public interface CursorPositionListener { + public void onCursorPos(double x, double y); +} diff --git a/src/main/java/xueli/game2/display/Display.java b/src/main/java/xueli/game2/display/Display.java new file mode 100644 index 00000000..dc2c1bd2 --- /dev/null +++ b/src/main/java/xueli/game2/display/Display.java @@ -0,0 +1,338 @@ +package xueli.game2.display; + +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR; +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL; +import static org.lwjgl.glfw.GLFW.GLFW_FALSE; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_FORWARD_COMPAT; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_PRESS; +import static org.lwjgl.glfw.GLFW.GLFW_RELEASE; +import static org.lwjgl.glfw.GLFW.GLFW_TRUE; +import static org.lwjgl.glfw.GLFW.GLFW_VISIBLE; +import static org.lwjgl.glfw.GLFW.glfwCreateWindow; +import static org.lwjgl.glfw.GLFW.glfwDestroyWindow; +import static org.lwjgl.glfw.GLFW.glfwGetMouseButton; +import static org.lwjgl.glfw.GLFW.glfwHideWindow; +import static org.lwjgl.glfw.GLFW.glfwInit; +import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent; +import static org.lwjgl.glfw.GLFW.glfwPollEvents; +import static org.lwjgl.glfw.GLFW.glfwSetCursorPosCallback; +import static org.lwjgl.glfw.GLFW.glfwSetInputMode; +import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback; +import static org.lwjgl.glfw.GLFW.glfwSetMouseButtonCallback; +import static org.lwjgl.glfw.GLFW.glfwSetScrollCallback; +import static org.lwjgl.glfw.GLFW.glfwSetWindowPos; +import static org.lwjgl.glfw.GLFW.glfwSetWindowSizeCallback; +import static org.lwjgl.glfw.GLFW.glfwShowWindow; +import static org.lwjgl.glfw.GLFW.glfwSwapBuffers; +import static org.lwjgl.glfw.GLFW.glfwSwapInterval; +import static org.lwjgl.glfw.GLFW.glfwTerminate; +import static org.lwjgl.glfw.GLFW.glfwWindowHint; +import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.glfw.GLFWCursorPosCallback; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWKeyCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; +import org.lwjgl.glfw.GLFWScrollCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; + +public class Display { + + private long window; + private boolean running = false; + private final String mainTitle; + + private int width, height; + private float displayScale; + + private float cursorX, cursorY; + private float cursorDx = 0, cursorDy = 0; + + private double wheelDelta; + + private boolean mouseGrabbed = false; + + private final ArrayList sizeCallbacks = new ArrayList<>(); + private final GLFWWindowSizeCallback windowSizeCallback = new GLFWWindowSizeCallback() { + @Override + public void invoke(long window, int w, int h) { + if (w != 0 || h != 0) { + width = w; + height = h; + displayScale = getScale(w, h); + + sizeCallbacks.forEach(c -> c.onSize(w, h)); + + } + } + }; + + private final ArrayList cursorPosCallbacks = new ArrayList<>(); + private final GLFWCursorPosCallback cursorPosCallback = new GLFWCursorPosCallback() { + @Override + public void invoke(long window, double xpos, double ypos) { + cursorDx = (float) (xpos - cursorX); + cursorDy = (float) (ypos - cursorY); + + cursorX = (float) xpos; + cursorY = (float) ypos; + + cursorPosCallbacks.forEach(c -> c.onCursorPos(xpos, ypos)); + + } + }; + + private final boolean[] mouse_buttons = new boolean[8]; + private final ArrayList mouseCallbacks = new ArrayList<>(); + + private final GLFWMouseButtonCallback mouseButtonCallback = new GLFWMouseButtonCallback() { + @Override + public void invoke(long window, int button, int action, int mods) { + if (button >= 0 && action == GLFW_RELEASE) { + mouse_buttons[button] = true; + } + mouseCallbacks.forEach(l -> l.onMouseButton(button, action, mods)); + + } + }; + + private final boolean[] keys = new boolean[65536]; + private final boolean[] keyboard_keys = new boolean[65536]; + private final ArrayList last_press_keys = new ArrayList<>(); + private final ArrayList keyCallbacks = new ArrayList<>(); + private final GLFWKeyCallback keyCallback = new GLFWKeyCallback() { + @Override + public void invoke(long window, int key, int scancode, int action, int mods) { + if (key >= 0 && action == GLFW_PRESS) { + keyboard_keys[key] = true; + last_press_keys.add(key); + } + if (key >= 0) + keys[key] = action != GLFW_RELEASE; + + keyCallbacks.forEach(c -> c.onKey(key, scancode, action, mods)); + + } + }; + + private final GLFWScrollCallback scrollCallback = new GLFWScrollCallback() { + @Override + public void invoke(long window, double xoffset, double yoffset) { + wheelDelta = yoffset; + } + }; + + public Display(int width, int height, String title) { + this.width = width; + this.height = height; + displayScale = getScale(width, height); + this.mainTitle = title; + + } + + private float getScale(int width, int height) { + return Math.min(width, height) / 400.0f * 0.6f; + } + + public void create() { + GLFWErrorCallback.createPrint(System.err).set(); + if (!glfwInit()) { + throw new RuntimeException("Can't init GLFW!"); + } + + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + + window = glfwCreateWindow(width, height, mainTitle, 0, 0); + + if (window == 0) { + throw new RuntimeException("Can't create window!"); + } + + // get width and height + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + // center the window + glfwSetWindowPos(window, (screenSize.width - width) / 2, (screenSize.height - height) / 2); + + glfwMakeContextCurrent(window); + GL.createCapabilities(); + + glfwSwapInterval(1); + + glfwSetWindowSizeCallback(window, windowSizeCallback); + glfwSetCursorPosCallback(window, cursorPosCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + glfwSetKeyCallback(window, keyCallback); + glfwSetScrollCallback(window, scrollCallback); + + GL11.glEnable(GL11.GL_TEXTURE_2D); + + this.running = true; + + } + + public void addKeyListener(KeyInputListener callback) { + keyCallbacks.add(callback); + } + + public void removeKeyListener(KeyInputListener callback) { + keyCallbacks.remove(callback); + } + + public void addWindowSizedListener(WindowSizeListener callback) { + sizeCallbacks.add(callback); + } + + public void removeWindowSizedListener(WindowSizeListener callback) { + sizeCallbacks.remove(callback); + } + + public void addMouseInputListener(MouseInputListener callback) { + mouseCallbacks.add(callback); + } + + public void removeMouseInputListener(MouseInputListener callback) { + mouseCallbacks.remove(callback); + } + + public void addMousePositionListener(CursorPositionListener callback) { + cursorPosCallbacks.add(callback); + } + + public void removeMousePositionListener(CursorPositionListener callback) { + cursorPosCallbacks.remove(callback); + } + + public void show() { + glfwShowWindow(window); + + } + + public void hide() { + glfwHideWindow(window); + + } + + private void callbackTick() { + for (int i = 0; i < 8; i++) + mouse_buttons[i] = false; + + for (Integer r : last_press_keys) + keyboard_keys[r] = false; + last_press_keys.clear(); + + wheelDelta = 0; + cursorDx = 0; + cursorDy = 0; + + } + + public void update() { + callbackTick(); + + glfwSwapBuffers(window); + glfwPollEvents(); + + if (glfwWindowShouldClose(window)) + running = false; + + } + + public void setMouseGrabbed(boolean mouseGrabbed) { + if (this.mouseGrabbed == mouseGrabbed) + return; + + glfwSetInputMode(window, GLFW_CURSOR, mouseGrabbed ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); + this.mouseGrabbed = mouseGrabbed; + + } + + public void release() { + this.running = false; + glfwDestroyWindow(window); + glfwTerminate(); + + } + + public double getWheelDelta() { + return wheelDelta; + } + + public boolean isRunning() { + return running; + } + + public void setRunning(boolean running) { + this.running = running; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public float getCursorX() { + return cursorX; + } + + public float getCursorY() { + return cursorY; + } + + public float getCursorDX() { + return cursorDx; + } + + public float getCursorDY() { + return cursorDy; + } + + public float getDisplayScale() { + return displayScale; + } + + public boolean isKeyDown(int key) { + return keys[key]; + } + + @Deprecated + public boolean isKeyDownOnce(int key) { + return keyboard_keys[key]; + } + + public boolean isMouseDown(int mouse) { + return glfwGetMouseButton(window, mouse) == GLFW_PRESS; + } + + @Deprecated + public boolean isMouseDownOnce(int mouse) { + return mouse_buttons[mouse]; + } + + public boolean isMouseGrabbed() { + return mouseGrabbed; + } + + public List getPressedKeyList() { + return last_press_keys; + } + +} diff --git a/src/main/java/xueli/game2/display/FPSCalculator.java b/src/main/java/xueli/game2/display/FPSCalculator.java new file mode 100644 index 00000000..ef7a86b4 --- /dev/null +++ b/src/main/java/xueli/game2/display/FPSCalculator.java @@ -0,0 +1,31 @@ +package xueli.game2.display; + +public class FPSCalculator { + + private long thisTime; + private long lastTimeCountFPS = 0; + + private int count = 0; + + private int fps = 0; + + public void tick() { + thisTime = System.currentTimeMillis(); + if (thisTime - lastTimeCountFPS < 1000) { + count++; + } else { + fps = count; + count = 0; + + lastTimeCountFPS = thisTime; + System.out.println("FPS: " + fps); + + } + + } + + public int getFps() { + return fps; + } + +} diff --git a/src/main/java/xueli/game2/display/GameDisplay.java b/src/main/java/xueli/game2/display/GameDisplay.java new file mode 100644 index 00000000..fd756a71 --- /dev/null +++ b/src/main/java/xueli/game2/display/GameDisplay.java @@ -0,0 +1,203 @@ +package xueli.game2.display; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +import xueli.game2.Timer; +import xueli.game2.display.event.CursorPositionEvent; +import xueli.game2.display.event.WindowKeyEvent; +import xueli.game2.display.event.WindowMouseButtonEvent; +import xueli.game2.display.event.WindowSizedEvent; +import xueli.game2.lifecycle.RunnableLifeCycle; +import xueli.game2.resource.manager.BackwardResourceManager; +import xueli.game2.resource.provider.ClassLoaderResourceProvider; +import xueli.game2.resource.provider.ResourceProvider; +import xueli.game2.resource.submanager.render.shader.ShaderRenderResource; +import xueli.game2.resource.submanager.render.texture.TextureRenderResource; +import xueli.gui.WindowSizeProvider; +import xueli.utils.concurrent.ControllerExecutorService; +import xueli.utils.events.EventBus; +import xueli.utils.exception.CrashReport; +import xueli.utils.logger.Logger; + +public abstract class GameDisplay implements RunnableLifeCycle, WindowSizeProvider { + + private static final Logger LOGGER = new Logger(); + + protected Display display; + + public final Timer timer = new Timer(); + public final FPSCalculator fps = new FPSCalculator(); + + private boolean shouldCrash = false; + + public final BackwardResourceManager resourceManager; + public final TextureRenderResource textureResource; + public final ShaderRenderResource shaderResource; + +// public final OverlayManager overlayManager; + + public final EventBus eventbus = new EventBus(); + + private final ExecutorService asyncExecutor = Executors.newWorkStealingPool(); + private final ControllerExecutorService mainThreadExecutor = new ControllerExecutorService(); + + public GameDisplay(int initialWidth, int initialHeight, String mainTitle) { + this.display = new Display(initialWidth, initialHeight, mainTitle); + + List resourceProviders = List.of(new ClassLoaderResourceProvider(true)); + this.resourceManager = new BackwardResourceManager(resourceProviders); + + this.textureResource = new TextureRenderResource(resourceManager); + this.shaderResource = new ShaderRenderResource(resourceManager); +// this.fontResource = new FontRenderResource(gui, resourceManager); + +// this.overlayManager = new OverlayManager(this); + + } + + @Override + public void init() { + this.display.create(); + this.display.addKeyListener( + (key, scancode, action, mods) -> eventbus.post(new WindowKeyEvent(key, scancode, action, mods))); + this.display.addWindowSizedListener((w, h) -> eventbus.post(new WindowSizedEvent(w, h))); + this.display.addMouseInputListener( + (btn, action, mods) -> eventbus.post(new WindowMouseButtonEvent(btn, action, mods))); + this.display.addMousePositionListener((x, y) -> eventbus.post(new CursorPositionEvent(x, y))); + +// this.resourceManager.addResourceHolder(this.overlayManager); + + try { +// this.overlayManager.init(); + renderInit(); + } catch (Exception e) { + this.announceCrash("Init", e); + } + + this.printDeviceInfo(); + + this.display.show(); + + } + + @Override + public void tick() { + timer.tick(); + fps.tick(); + try { + mainThreadExecutor.tick(); + } catch (Exception e) { + this.announceCrash("MainThreadExecutor", e); + } + + GL30.glViewport(0, 0, display.getWidth(), display.getHeight()); + GL30.glClear(GL30.GL_COLOR_BUFFER_BIT | GL30.GL_STENCIL_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT); + +// if(this.overlayManager.hasOverlay()) { +// getDisplay().setMouseGrabbed(false); +// this.overlayManager.tick(); +// } else { +// getDisplay().setMouseGrabbed(true); +// } + +// try { + this.render(); +// } catch (Exception e) { +// this.announceCrash("Render", e); +// } + + this.display.update(); + + this.checkGLError("Post-Render"); + + } + + @Override + public boolean isRunning() { + return this.display.isRunning() && !shouldCrash; + } + + @Override + public void release() { + this.display.hide(); +// this.overlayManager.release(); + this.renderRelease(); + + asyncExecutor.shutdownNow(); + mainThreadExecutor.shutdownNow(); + + try { + this.resourceManager.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + this.display.release(); + + } + + protected abstract void renderInit(); + + protected abstract void render(); + + protected abstract void renderRelease(); + + public void announceClose() { + this.display.setRunning(false); + } + + public void announceCrash(String state, Throwable t) { + this.shouldCrash = true; + new CrashReport(state, t).showCrashReport(); + } + + public void checkGLError(String state) { + int error = -1; + while (error != 0) { + error = GL11.glGetError(); + if (error != 0) + System.out.println("[" + state + "] OpenGL Error: " + error); + } + } + + private void printDeviceInfo() { + String nameString = GL11.glGetString(GL11.GL_VENDOR); + String platform = GL11.glGetString(GL11.GL_RENDERER); + String version = GL11.glGetString(GL11.GL_VERSION); + LOGGER.warning("[DeviceInfo] OpenGL: " + nameString + ", " + platform + ", " + version); + + } + + public ExecutorService getAsyncExecutor() { + return asyncExecutor; + } + + public ExecutorService getMainThreadExecutor() { + return mainThreadExecutor; + } + + public Display getDisplay() { + return display; + } + + @Override + public int getWidth() { + return display.getWidth(); + } + + @Override + public int getHeight() { + return display.getHeight(); + } + + public float getDisplayScale() { + return display.getDisplayScale(); + } + +} diff --git a/src/main/java/xueli/game2/display/KeyInputListener.java b/src/main/java/xueli/game2/display/KeyInputListener.java new file mode 100644 index 00000000..6c896b30 --- /dev/null +++ b/src/main/java/xueli/game2/display/KeyInputListener.java @@ -0,0 +1,7 @@ +package xueli.game2.display; + +public interface KeyInputListener { + + public void onKey(int key, int scancode, int action, int mods); + +} diff --git a/src/main/java/xueli/game2/display/MouseInputListener.java b/src/main/java/xueli/game2/display/MouseInputListener.java new file mode 100644 index 00000000..9eda7830 --- /dev/null +++ b/src/main/java/xueli/game2/display/MouseInputListener.java @@ -0,0 +1,8 @@ +package xueli.game2.display; + +@FunctionalInterface +public interface MouseInputListener { + + public void onMouseButton(int button, int action, int mods); + +} diff --git a/src/main/java/xueli/game2/display/RenderResourceProvider.java b/src/main/java/xueli/game2/display/RenderResourceProvider.java new file mode 100644 index 00000000..d201291c --- /dev/null +++ b/src/main/java/xueli/game2/display/RenderResourceProvider.java @@ -0,0 +1,10 @@ +package xueli.game2.display; + +import xueli.game2.resource.manager.ResourceManager; + +// TODO: make GameDisplay extend this +public interface RenderResourceProvider { + + public T getResourceManager(Class clazz); + +} diff --git a/src/main/java/xueli/game2/display/WindowSizeListener.java b/src/main/java/xueli/game2/display/WindowSizeListener.java new file mode 100644 index 00000000..2e5e1da4 --- /dev/null +++ b/src/main/java/xueli/game2/display/WindowSizeListener.java @@ -0,0 +1,7 @@ +package xueli.game2.display; + +public interface WindowSizeListener { + + public void onSize(int width, int height); + +} diff --git a/src/main/java/xueli/game2/display/event/CursorPositionEvent.java b/src/main/java/xueli/game2/display/event/CursorPositionEvent.java new file mode 100644 index 00000000..9b0cda04 --- /dev/null +++ b/src/main/java/xueli/game2/display/event/CursorPositionEvent.java @@ -0,0 +1,4 @@ +package xueli.game2.display.event; + +public record CursorPositionEvent(double x, double y) { +} diff --git a/src/main/java/xueli/game2/display/event/WindowKeyEvent.java b/src/main/java/xueli/game2/display/event/WindowKeyEvent.java new file mode 100644 index 00000000..33758174 --- /dev/null +++ b/src/main/java/xueli/game2/display/event/WindowKeyEvent.java @@ -0,0 +1,4 @@ +package xueli.game2.display.event; + +public record WindowKeyEvent(int key, int scancode, int action, int mods) { +} diff --git a/src/main/java/xueli/game2/display/event/WindowMouseButtonEvent.java b/src/main/java/xueli/game2/display/event/WindowMouseButtonEvent.java new file mode 100644 index 00000000..bd49a560 --- /dev/null +++ b/src/main/java/xueli/game2/display/event/WindowMouseButtonEvent.java @@ -0,0 +1,4 @@ +package xueli.game2.display.event; + +public record WindowMouseButtonEvent(int button, int action, int mods) { +} diff --git a/src/main/java/xueli/game2/display/event/WindowSizedEvent.java b/src/main/java/xueli/game2/display/event/WindowSizedEvent.java new file mode 100644 index 00000000..2dbcb31b --- /dev/null +++ b/src/main/java/xueli/game2/display/event/WindowSizedEvent.java @@ -0,0 +1,4 @@ +package xueli.game2.display.event; + +public record WindowSizedEvent(int width, int height) { +} diff --git a/src/main/java/xueli/game2/ecs/ComponentInfo.java b/src/main/java/xueli/game2/ecs/ComponentInfo.java new file mode 100644 index 00000000..b9165352 --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ComponentInfo.java @@ -0,0 +1,26 @@ +package xueli.game2.ecs; + +import java.util.HashMap; +import java.util.Set; + +class ComponentInfo { + + private final HashMap components = new HashMap<>(); + + public void addComponent(long entityId, Object component) { + components.put(entityId, component); + } + + public Object getComponent(long entityId) { + return components.get(entityId); + } + + public void removeComponent(long entityId) { + components.remove(entityId); + } + + public Set query() { + return components.keySet(); + } + +} diff --git a/src/main/java/xueli/game2/ecs/ECSContext.java b/src/main/java/xueli/game2/ecs/ECSContext.java new file mode 100644 index 00000000..985c9f85 --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ECSContext.java @@ -0,0 +1,109 @@ +package xueli.game2.ecs; + +import java.util.Set; + +/** + * This is an ECS context which holds an Entity Component System. + */ +public interface ECSContext { + + /** + * Spawn an entity + * + * @return The ID of the entity + */ + public long spawn(); + + /** + * Delete an entity + */ + public void kill(long entityId); + + /** + * Add an component to the entity + */ + public void addComponent(long entityId, Object component); + + /** + * Get an component from the entity + * + * @param The type of the component + */ + public T getComponent(long entityId, Class componentClazz); + + /** + * Remove an component from the entity + * + * @param The type of the component + */ + public void removeComponent(long entityId, Class componentClazz); + + /** + * Add a resource. A resource is a component that isn't bound to any entity and + * only one instance of each type exists. + * + * @param The type of the resource + */ + public void addResource(Object resource); + + /** + * Get a resource. + * + * @param The type of the resource + * @return The unique resource instance in the type R, or null if we don't have + * the corresponding resource. + */ + public R getResource(Class resourceClazz); + + /** + * Remove a resource + */ + public void removeResource(Class resourceClazz); + + /** + * Query all the entity that has component T + * + * @param The type of the entities that you want to find + * @return A list contains all the corresponding entities + */ + public Set query(Class clazz); + + /** + * Add a system + */ + public void addSystem(ECSSystem system); + + /** + * Broadcast an event to the context. When next update occurs, every system + * should have access to the events. And when it is the following update, these + * events will become inaccessible. + * + * @param The type of the event + */ + public void broadcast(Object e); + + /** + * Read an event + * + * @param The type of the event + * @return The instance of the event, or null if there is no event belonging to + * type E now + */ + public E readEvent(Class clazz); + + /** + * Startup all systems + */ + public void startUp(); + + /** + * Update the systems + */ + public void update(); + + /** + * Shut down all systems + */ + public void shutdown(); + +} diff --git a/src/main/java/xueli/game2/ecs/ECSContextImpl.java b/src/main/java/xueli/game2/ecs/ECSContextImpl.java new file mode 100644 index 00000000..9306c73d --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ECSContextImpl.java @@ -0,0 +1,114 @@ +package xueli.game2.ecs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import xueli.utils.events.DeferredEventBus; + +public class ECSContextImpl implements ECSContext { + + private long entityIndex = 0; + + private final HashMap, ComponentInfo> componentInstances = new HashMap<>(); + + @Override + public long spawn() { + return entityIndex++; + } + + @Override + public void kill(long entityId) { + componentInstances.forEach((c, i) -> { + i.removeComponent(entityId); + }); + } + + @Override + public void addComponent(long entityId, Object component) { + componentInstances.computeIfAbsent(component.getClass(), c -> new ComponentInfo()).addComponent(entityId, + component); + } + + @SuppressWarnings("unchecked") + @Override + public T getComponent(long entityId, Class componentClazz) { + ComponentInfo info = componentInstances.get(componentClazz); + if (info == null) + return null; + return (T) info.getComponent(entityId); + } + + @Override + public void removeComponent(long entityId, Class componentClazz) { + ComponentInfo info = componentInstances.get(componentClazz); + if (info == null) + return; + info.removeComponent(entityId); + } + + final ResourceList resources = new ResourceListImpl<>(); + + @Override + public void addResource(Object resource) { + resources.add(resource); + } + + public R getResource(Class resourceClazz) { + return resources.get(resourceClazz); + } + + @Override + public void removeResource(Class resourceClazz) { + resources.remove(resourceClazz); + } + + @Override + public Set query(Class clazz) { + ComponentInfo info = componentInstances.get(clazz); + if (info == null) + return new HashSet<>(); + return info.query(); + } + + private final ArrayList systems = new ArrayList<>(); + + @Override + public void addSystem(ECSSystem system) { + systems.add(system); + } + + private final DeferredEventBus busTemp = new DeferredEventBus(), bus = new DeferredEventBus(); + + @Override + public void broadcast(Object e) { + busTemp.post(e); + } + + @Override + public E readEvent(Class clazz) { + return bus.read(clazz); + } + + @Override + public void startUp() { + systems.forEach(s -> s.start(this)); + } + + @Override + public void update() { + systems.forEach(s -> s.update(this)); + + this.bus.clear(); + this.busTemp.copyTo(bus); + this.busTemp.clear(); + + } + + @Override + public void shutdown() { + systems.forEach(s -> s.shutdown(this)); + } + +} diff --git a/src/main/java/xueli/game2/ecs/ECSSystem.java b/src/main/java/xueli/game2/ecs/ECSSystem.java new file mode 100644 index 00000000..a7234e4d --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ECSSystem.java @@ -0,0 +1,11 @@ +package xueli.game2.ecs; + +public interface ECSSystem { + + public void start(ECSContext ctx); + + public void update(ECSContext ctx); + + public void shutdown(ECSContext ctx); + +} diff --git a/src/main/java/xueli/game2/ecs/ResourceList.java b/src/main/java/xueli/game2/ecs/ResourceList.java new file mode 100644 index 00000000..a8ea3251 --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ResourceList.java @@ -0,0 +1,15 @@ +package xueli.game2.ecs; + +import java.util.List; + +interface ResourceList { + + public void add(T t); + + public S get(Class clazz); + + public void remove(Class clazz); + + public List values(); + +} diff --git a/src/main/java/xueli/game2/ecs/ResourceListGeneric.java b/src/main/java/xueli/game2/ecs/ResourceListGeneric.java new file mode 100644 index 00000000..7ac26ab4 --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ResourceListGeneric.java @@ -0,0 +1,67 @@ +package xueli.game2.ecs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ResourceListGeneric { + + private final HashMap, Integer> classToIndexMap = new HashMap<>(); + private final ArrayList components = new ArrayList<>(); + + public ResourceListGeneric() { + } + + public void add(E t) { + Class clazz = t.getClass(); + Integer previousIndex = classToIndexMap.get(clazz); + if (previousIndex != null) { + components.set(previousIndex, t); + return; + } + + int index = components.size(); + components.add(t); + classToIndexMap.put(clazz, index); + + } + + @SuppressWarnings("unchecked") + public E get(Class clazz) { + Integer index = classToIndexMap.get(clazz); + if (index == null) { + return null; + } + return (E) components.get(index); + } + + public void remove(Class clazz) { + Integer indexBoxed = classToIndexMap.get(clazz); + if (indexBoxed == null) { + return; + } + int index = indexBoxed; + + int componentsLastIndex = components.size() - 1; + if (index != components.size()) { + // Pick up the last component + T lastComponent = components.get(componentsLastIndex); + // Switch it to the place where our leaving element is + components.set(index, lastComponent); + + // Don't forget to change the value in the map + Class lastCompClazz = lastComponent.getClass(); + classToIndexMap.put(lastCompClazz, indexBoxed); + + } + + components.remove(componentsLastIndex); + classToIndexMap.remove(clazz); + + } + + public List values() { + return components; + } + +} diff --git a/src/main/java/xueli/game2/ecs/ResourceListImpl.java b/src/main/java/xueli/game2/ecs/ResourceListImpl.java new file mode 100644 index 00000000..0cbbc11b --- /dev/null +++ b/src/main/java/xueli/game2/ecs/ResourceListImpl.java @@ -0,0 +1,78 @@ +package xueli.game2.ecs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ResourceListImpl implements ResourceList { + + private final HashMap, Integer> classToIndexMap = new HashMap<>(); + private final ArrayList components = new ArrayList<>(); + + public ResourceListImpl() { + } + + @SuppressWarnings("unchecked") + @Override + public void add(T t) { + Class clazz = (Class) t.getClass(); + Integer previousIndex = classToIndexMap.get(clazz); + if (previousIndex != null) { + components.set(previousIndex, t); + return; + } + + int index = components.size(); + components.add(t); + classToIndexMap.put(clazz, index); + + } + + @SuppressWarnings("unchecked") + @Override + public S get(Class clazz) { + Integer index = classToIndexMap.get(clazz); + if (index == null) { + return null; + } + return (S) components.get(index); + } + + @SuppressWarnings("unchecked") + @Override + public void remove(Class clazz) { + Integer indexBoxed = classToIndexMap.get(clazz); + if (indexBoxed == null) { + return; + } + int index = indexBoxed; + + int componentsLastIndex = components.size() - 1; + if (index != components.size()) { + // Pick up the last component + T lastComponent = components.get(componentsLastIndex); + // Switch it to the place where our leaving element is + components.set(index, lastComponent); + + // Don't forget to change the value in the map + Class lastCompClazz = (Class) lastComponent.getClass(); + classToIndexMap.put(lastCompClazz, indexBoxed); + + } + + components.remove(componentsLastIndex); + classToIndexMap.remove(clazz); + + } + + @Override + public List values() { + return components; + } + + @Override + public String toString() { + return "ComponentListImpl [classToIndexMap=" + classToIndexMap + ", components=" + components + "]"; + } + +} diff --git a/src/main/java/xueli/game2/ecs/SparseSet.java b/src/main/java/xueli/game2/ecs/SparseSet.java new file mode 100644 index 00000000..9a4baaf0 --- /dev/null +++ b/src/main/java/xueli/game2/ecs/SparseSet.java @@ -0,0 +1,137 @@ +package xueli.game2.ecs; + +import java.util.ArrayList; +import java.util.Arrays; + +public class SparseSet { + + private final int sparsePageSize; + private final ArrayList data = new ArrayList<>(); + private final ArrayList sparse = new ArrayList<>(); + + public SparseSet() { + this(32); + } + + public SparseSet(int sparsePageSize) { + this.sparsePageSize = sparsePageSize; + } + + /** + * Add an element to this set + * + * @param i The element to be added to the set + * @return "True" means we successfully add the element while "False" means we + * find the same element in the set + */ + public boolean add(int i) { + // Because the initial value of array is 0, so we add one to the real data to + // avoid some problems. + if (!setSparse(i, data.size() + 1, false)) + return false; + data.add(i); + return true; + } + + /** + * Get the index of an element. When the value is 0, then this element isn't in + * the set. So the index actually starts at 1. + * + * @param i + * @return The index of element + */ + public int indexOf(int i) { + return getSparse(i); + } + + /** + * + * @param i the element to be removed + * @return "True" means we successfully find the element and remove it while + * "False" means we don't find that element + */ + // To save the cost, I duplicate some codes. + public boolean remove(int i) { + // Get sparse data of the element to be removed and its page array. + int yInSparse = i / sparsePageSize; + if (yInSparse > sparse.size()) + return false; + int[] targetPage = this.sparse.get(yInSparse); + int xInSparse = i % sparsePageSize; + // Remember this index is started from 1. We have to subtract the number by one + // to get its real index. + int dataIndex = targetPage[xInSparse]; + if (dataIndex <= 0) + return false; + + // To save the cost of moving every element backward, + // First we exchange the position of the element to be removed and the last + // element. + // But we don't have to literally set the last element to the element to be + // removed. + int lastElementIndex = this.data.size() - 1; + int lastElement = this.data.get(lastElementIndex); + setSparse(lastElement, dataIndex, true); + this.data.set(dataIndex - 1, lastElement); + // Then we "pop" the last element + this.data.remove(lastElementIndex); + targetPage[xInSparse] = 0; + + return true; + } + + public boolean contains(int i) { + return this.getSparse(i) > 0; + } + + private boolean setSparse(int index, int data, boolean force) { + int yInSparse = index / sparsePageSize; + int xInSparse = index % sparsePageSize; + + int[] targetPage; + int sparseOriginSize = sparse.size(); + if (yInSparse >= sparseOriginSize) { + targetPage = new int[this.sparsePageSize]; + + int needGrowCount = yInSparse - sparseOriginSize; + for (int i = 0; i < needGrowCount; i++) { + this.sparse.add(null); + } + this.sparse.add(targetPage); + + } else { + targetPage = this.sparse.get(yInSparse); + if (targetPage == null) { + this.sparse.set(yInSparse, targetPage = new int[this.sparsePageSize]); + } + } + + // This element is at the list, so we ignore it + if (targetPage[xInSparse] > 0 && !force) + return false; + + targetPage[xInSparse] = data; + return true; + } + + private int getSparse(int index) { + int yInSparse = index / sparsePageSize; + if (yInSparse > sparse.size()) + // Because when creating an array, Java sets all its elements to zero, so we set + // it to zero + return 0; + + int[] targetPage = this.sparse.get(yInSparse); + int xInSparse = index % sparsePageSize; + return targetPage[xInSparse]; + } + + void debugPrint() { + System.out.println("-- Data --"); + System.out.println(this.data.toString()); + System.out.println("-- Sparse --"); + this.sparse.forEach(e -> System.out.println(Arrays.toString(e))); + + } + +} diff --git a/src/main/java/xueli/game2/input/DefaultInputListener.java b/src/main/java/xueli/game2/input/DefaultInputListener.java new file mode 100644 index 00000000..45abeecd --- /dev/null +++ b/src/main/java/xueli/game2/input/DefaultInputListener.java @@ -0,0 +1,24 @@ +package xueli.game2.input; + +import xueli.game2.input.KeyBindings.KeyBinding; + +public class DefaultInputListener { + + private final KeyBindings context; + + public DefaultInputListener(KeyBindings context) { + this.context = context; + } + + public void onInput(int key, boolean pressOrRelease) { + KeyBinding binding = context.getKeyBinding(key); + if (pressOrRelease) { + binding.clickCount++; + binding.pressed = true; + } else { + binding.pressed = false; + } +// System.out.println(binding); + } + +} diff --git a/src/main/java/xueli/game2/input/DefaultKeyListener.java b/src/main/java/xueli/game2/input/DefaultKeyListener.java new file mode 100644 index 00000000..fe4c802f --- /dev/null +++ b/src/main/java/xueli/game2/input/DefaultKeyListener.java @@ -0,0 +1,23 @@ +package xueli.game2.input; + +import org.lwjgl.glfw.GLFW; + +import xueli.game2.display.KeyInputListener; + +public class DefaultKeyListener extends DefaultInputListener implements KeyInputListener { + + public DefaultKeyListener(KeyBindings context) { + super(context); + } + + @Override + public void onKey(int key, int scancode, int action, int mods) { + if (action == GLFW.GLFW_PRESS) { + this.onInput(key, true); + } else if (action == GLFW.GLFW_RELEASE) { + this.onInput(key, false); + } + + } + +} diff --git a/src/main/java/xueli/game2/input/DefaultMouseListener.java b/src/main/java/xueli/game2/input/DefaultMouseListener.java new file mode 100644 index 00000000..b2b64408 --- /dev/null +++ b/src/main/java/xueli/game2/input/DefaultMouseListener.java @@ -0,0 +1,23 @@ +package xueli.game2.input; + +import org.lwjgl.glfw.GLFW; + +import xueli.game2.display.MouseInputListener; + +public class DefaultMouseListener extends DefaultInputListener implements MouseInputListener { + + public DefaultMouseListener(KeyBindings context) { + super(context); + } + + @Override + public void onMouseButton(int button, int action, int mods) { + if (action == GLFW.GLFW_PRESS) { + this.onInput(button, true); + } else if (action == GLFW.GLFW_RELEASE) { + this.onInput(button, false); + } + + } + +} diff --git a/src/main/java/xueli/game2/input/KeyBindings.java b/src/main/java/xueli/game2/input/KeyBindings.java new file mode 100644 index 00000000..b95665aa --- /dev/null +++ b/src/main/java/xueli/game2/input/KeyBindings.java @@ -0,0 +1,52 @@ +package xueli.game2.input; + +import java.util.HashMap; + +/** + * Might add DirectX support to the engine, then there should be lots of public + * constant, each one for each key + */ +public class KeyBindings { + + private final HashMap bindings = new HashMap<>(); + + public KeyBindings() { + } + + public KeyBinding getKeyBinding(int keyId) { + return bindings.computeIfAbsent(keyId, i -> new KeyBinding()); + } + + public boolean consumeClick(int keyId) { + return this.getKeyBinding(keyId).consumeClick(); + } + + public boolean isPressed(int keyId) { + return this.getKeyBinding(keyId).isPressed(); + } + + public static class KeyBinding { + + int clickCount = 0; + boolean pressed = false; + + public boolean consumeClick() { + if (clickCount > 0) { + clickCount--; + return true; + } + return false; + } + + public boolean isPressed() { + return pressed; + } + + @Override + public String toString() { + return "KeyBinding [clickCount=" + clickCount + ", pressed=" + pressed + "]"; + } + + } + +} diff --git a/src/main/java/xueli/game2/lifecycle/LifeCycle.java b/src/main/java/xueli/game2/lifecycle/LifeCycle.java new file mode 100644 index 00000000..3e3a64d7 --- /dev/null +++ b/src/main/java/xueli/game2/lifecycle/LifeCycle.java @@ -0,0 +1,14 @@ +package xueli.game2.lifecycle; + +public interface LifeCycle { + + public void init(); + + /** + * In something about rendering, this is called "Render" + */ + public void tick(); + + public void release(); + +} diff --git a/src/main/java/xueli/game2/lifecycle/RunnableLifeCycle.java b/src/main/java/xueli/game2/lifecycle/RunnableLifeCycle.java new file mode 100644 index 00000000..40d99a9f --- /dev/null +++ b/src/main/java/xueli/game2/lifecycle/RunnableLifeCycle.java @@ -0,0 +1,16 @@ +package xueli.game2.lifecycle; + +public interface RunnableLifeCycle extends LifeCycle, Runnable { + + public boolean isRunning(); + + @Override + default void run() { + init(); + while (isRunning()) { + tick(); + } + release(); + } + +} diff --git a/src/main/java/xueli/game2/math/Frustum.java b/src/main/java/xueli/game2/math/Frustum.java new file mode 100644 index 00000000..e0a81bd9 --- /dev/null +++ b/src/main/java/xueli/game2/math/Frustum.java @@ -0,0 +1,146 @@ +package xueli.game2.math; + +import org.lwjgl.utils.vector.Matrix4f; + +public class Frustum { + + private final float[][] frustumPlane = new float[6][4]; + + public Frustum(Matrix4f projMatrix, Matrix4f viewMatrix) { + this.calcFrustumPlane(Matrix4f.mul(projMatrix, viewMatrix, null)); + + } + + private void calcFrustumPlane(Matrix4f matrix) { + double temp; + frustumPlane[0][0] = matrix.m03 - matrix.m00; + frustumPlane[0][1] = matrix.m13 - matrix.m10; + frustumPlane[0][2] = matrix.m23 - matrix.m20; + frustumPlane[0][3] = matrix.m33 - matrix.m30; + temp = Math.sqrt(frustumPlane[0][0] * frustumPlane[0][0] + frustumPlane[0][1] * frustumPlane[0][1] + + frustumPlane[0][2] * frustumPlane[0][2]); + frustumPlane[0][0] /= temp; + frustumPlane[0][1] /= temp; + frustumPlane[0][2] /= temp; + frustumPlane[0][3] /= temp; + + frustumPlane[1][0] = matrix.m03 + matrix.m00; + frustumPlane[1][1] = matrix.m13 + matrix.m10; + frustumPlane[1][2] = matrix.m23 + matrix.m20; + frustumPlane[1][3] = matrix.m33 + matrix.m30; + temp = Math.sqrt(frustumPlane[1][0] * frustumPlane[1][0] + frustumPlane[1][1] * frustumPlane[1][1] + + frustumPlane[1][2] * frustumPlane[1][2]); + frustumPlane[1][0] /= temp; + frustumPlane[1][1] /= temp; + frustumPlane[1][2] /= temp; + frustumPlane[1][3] /= temp; + + frustumPlane[2][0] = matrix.m03 + matrix.m01; + frustumPlane[2][1] = matrix.m13 + matrix.m11; + frustumPlane[2][2] = matrix.m23 + matrix.m21; + frustumPlane[2][3] = matrix.m33 + matrix.m31; + temp = Math.sqrt(frustumPlane[2][0] * frustumPlane[2][0] + frustumPlane[2][1] * frustumPlane[2][1] + + frustumPlane[2][2] * frustumPlane[2][2]); + frustumPlane[2][0] /= temp; + frustumPlane[2][1] /= temp; + frustumPlane[2][2] /= temp; + frustumPlane[2][3] /= temp; + + frustumPlane[3][0] = matrix.m03 - matrix.m01; + frustumPlane[3][1] = matrix.m13 - matrix.m11; + frustumPlane[3][2] = matrix.m23 - matrix.m21; + frustumPlane[3][3] = matrix.m33 - matrix.m31; + temp = Math.sqrt(frustumPlane[3][0] * frustumPlane[3][0] + frustumPlane[3][1] * frustumPlane[3][1] + + frustumPlane[3][2] * frustumPlane[3][2]); + frustumPlane[3][0] /= temp; + frustumPlane[3][1] /= temp; + frustumPlane[3][2] /= temp; + frustumPlane[3][3] /= temp; + + frustumPlane[4][0] = matrix.m03 - matrix.m02; + frustumPlane[4][1] = matrix.m13 - matrix.m12; + frustumPlane[4][2] = matrix.m23 - matrix.m22; + frustumPlane[4][3] = matrix.m33 - matrix.m32; + temp = Math.sqrt(frustumPlane[4][0] * frustumPlane[4][0] + frustumPlane[4][1] * frustumPlane[4][1] + + frustumPlane[4][2] * frustumPlane[4][2]); + frustumPlane[4][0] /= temp; + frustumPlane[4][1] /= temp; + frustumPlane[4][2] /= temp; + frustumPlane[4][3] /= temp; + + frustumPlane[5][0] = matrix.m03 + matrix.m02; + frustumPlane[5][1] = matrix.m13 + matrix.m12; + frustumPlane[5][2] = matrix.m23 + matrix.m22; + frustumPlane[5][3] = matrix.m33 + matrix.m32; + temp = Math.sqrt(frustumPlane[5][0] * frustumPlane[5][0] + frustumPlane[5][1] * frustumPlane[5][1] + + frustumPlane[5][2] * frustumPlane[5][2]); + frustumPlane[5][0] /= temp; + frustumPlane[5][1] /= temp; + frustumPlane[5][2] /= temp; + frustumPlane[5][3] /= temp; + + } + + public boolean isPointInFrustum(float x, float y, float z) { + for (int p = 0; p < 6; p++) { + if (frustumPlane[p][0] * x + frustumPlane[p][1] * y + frustumPlane[p][2] * z + frustumPlane[p][3] <= 0) + return false; + } + return true; + } + + public boolean isCubeInFrustum(double x1, double y1, double z1, double x2, double y2, double z2) { + for (int p = 0; p < 6; p++) { + if (frustumPlane[p][0] * x1 + frustumPlane[p][1] * y1 + frustumPlane[p][2] * z1 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x2 + frustumPlane[p][1] * y1 + frustumPlane[p][2] * z1 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x1 + frustumPlane[p][1] * y2 + frustumPlane[p][2] * z1 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x2 + frustumPlane[p][1] * y2 + frustumPlane[p][2] * z1 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x1 + frustumPlane[p][1] * y1 + frustumPlane[p][2] * z2 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x2 + frustumPlane[p][1] * y1 + frustumPlane[p][2] * z2 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x1 + frustumPlane[p][1] * y2 + frustumPlane[p][2] * z2 + frustumPlane[p][3] > 0) + continue; + if (frustumPlane[p][0] * x2 + frustumPlane[p][1] * y2 + frustumPlane[p][2] * z2 + frustumPlane[p][3] > 0) + continue; + return false; + } + return true; + } + +// public boolean isChunkInFrustum(int x, int y, int z) { +// for (int p = 0; p < 6; p++) { +// if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * y * 16 + frustumPlane[p][2] * z * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * y * 16 + frustumPlane[p][2] * z * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * (y + 1) * 16 + frustumPlane[p][2] * z * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * (y + 1) * 16 + frustumPlane[p][2] * z * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * y * 16 + frustumPlane[p][2] * (z + 1) * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * y * 16 + frustumPlane[p][2] * (z + 1) * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * x * 16 + frustumPlane[p][1] * (y + 1) * 16 + frustumPlane[p][2] * (z + 1) * 16 +// + frustumPlane[p][3] > 0) +// continue; +// if (frustumPlane[p][0] * (x + 1) * 16 + frustumPlane[p][1] * (y + 1) * 16 +// + frustumPlane[p][2] * (z + 1) * 16 + frustumPlane[p][3] > 0) +// continue; +// return false; +// } +// return true; +// } + +} diff --git a/src/main/java/xueli/game2/math/MathUtils.java b/src/main/java/xueli/game2/math/MathUtils.java new file mode 100644 index 00000000..5a5cbdf4 --- /dev/null +++ b/src/main/java/xueli/game2/math/MathUtils.java @@ -0,0 +1,18 @@ +package xueli.game2.math; + +public class MathUtils { + + private MathUtils() { + } + + public static float floorMod(float x, float y) { + float multiple = x / y; + return (float) (x - Math.floor(multiple) * y); + } + + public static double floorMod(double x, double y) { + double multiple = x / y; + return x - Math.floor(multiple) * y; + } + +} diff --git a/src/main/java/xueli/game2/math/MatrixHelper.java b/src/main/java/xueli/game2/math/MatrixHelper.java new file mode 100644 index 00000000..40ec10dc --- /dev/null +++ b/src/main/java/xueli/game2/math/MatrixHelper.java @@ -0,0 +1,95 @@ +package xueli.game2.math; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.Vector; + +public class MatrixHelper { + + public static Matrix4f perspective(float width, float height, float fov, float near, float far) { + Matrix4f projectionMatrix = new Matrix4f(); + + float ratio = width / height; + float y_scale = (float) ((1f / Math.tan(Math.toRadians(fov / 2F))) * ratio); + float x_scale = y_scale / ratio; + float frustum_length = far - near; + + projectionMatrix.setIdentity(); + projectionMatrix.m00 = x_scale; + projectionMatrix.m11 = y_scale; + projectionMatrix.m22 = -(far + near) / frustum_length; + projectionMatrix.m23 = -1; + projectionMatrix.m32 = -((2 * far * near) / frustum_length); + projectionMatrix.m33 = 0; + + return projectionMatrix; + } + + public static Matrix4f ortho(float left, float right, float bottom, float top, float near, float far) { + Matrix4f matrix = new Matrix4f(); + matrix.setIdentity(); + + matrix.m00 = 2.0f / (right - left); + matrix.m11 = 2.0f / (top - bottom); + matrix.m22 = -2.0f / (far - near); + matrix.m30 = -(right + left) / (right - left); + matrix.m31 = -(top + bottom) / (top - bottom); + matrix.m32 = -(far + near) / (far - near); + + return matrix; + } + + public static Matrix4f rotate(float rotX, float rotY, float rotZ) { + Matrix4f src = new Matrix4f(); + src.setIdentity(); + Matrix4f.rotate((float) Math.toRadians(rotX), new Vector3f(1, 0, 0), src, src); + Matrix4f.rotate((float) Math.toRadians(rotY), new Vector3f(0, 1, 0), src, src); + Matrix4f.rotate((float) Math.toRadians(rotZ), new Vector3f(0, 0, 1), src, src); + return src; + } + + public static Matrix4f player(Vector camera) { + Matrix4f viewMatrix = new Matrix4f(); + viewMatrix.setIdentity(); + Matrix4f.rotate((float) Math.toRadians(camera.rotX), new Vector3f(1, 0, 0), viewMatrix, viewMatrix); + Matrix4f.rotate((float) Math.toRadians(camera.rotY), new Vector3f(0, 1, 0), viewMatrix, viewMatrix); + Matrix4f.rotate((float) Math.toRadians(camera.rotZ), new Vector3f(0, 0, 1), viewMatrix, viewMatrix); + Vector3f negativeCamPos = new Vector3f((float) -camera.x, (float) -camera.y, (float) -camera.z); + Matrix4f.translate(negativeCamPos, viewMatrix, viewMatrix); + + return viewMatrix; + } + + public static Matrix4f lookAt(Vector3f eye, Vector3f center, Vector3f up) { + Matrix4f mat = new Matrix4f(); + mat.setIdentity(); + + Vector3f f = new Vector3f(); + Vector3f.sub(center, eye, f); + f.normalise(); + + Vector3f s = new Vector3f(); + Vector3f.cross(f, up, s); + s.normalise(); + + Vector3f u = new Vector3f(); + Vector3f.cross(s, f, u); + + mat.m00 = s.x; + mat.m10 = s.y; + mat.m20 = s.z; + mat.m01 = u.x; + mat.m11 = u.y; + mat.m21 = u.z; + mat.m02 = -f.x; + mat.m12 = -f.y; + mat.m22 = -f.z; + mat.m30 = -Vector3f.dot(s, eye); + mat.m31 = -Vector3f.dot(u, eye); + mat.m32 = Vector3f.dot(f, eye); + + return mat; + } + +} diff --git a/src/main/java/xueli/game2/math/MousePicker.java b/src/main/java/xueli/game2/math/MousePicker.java new file mode 100644 index 00000000..559fd71b --- /dev/null +++ b/src/main/java/xueli/game2/math/MousePicker.java @@ -0,0 +1,53 @@ +package xueli.game2.math; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector3f; +import org.lwjgl.utils.vector.Vector4f; + +import xueli.game2.Vector; + +// TODO: Use when the ray is not emitted from the center of the screen +public class MousePicker { + + private final Matrix4f projMatrix, viewMatrix; + private Vector3f ray, camPos; + + public MousePicker(Vector camPos, Matrix4f projMatrix, Matrix4f viewMatrix) { + this.camPos = new Vector3f((float) camPos.x, (float) camPos.y, (float) camPos.z); + + this.projMatrix = projMatrix; + this.viewMatrix = viewMatrix; + + this.ray(); + + } + + private void ray() { + if (!valid()) + return; + + Vector4f clipCoords = new Vector4f(0, 0, -1f, 1f); + + Matrix4f invertedProj = Matrix4f.invert(projMatrix, null); + Vector4f eyeCoords_origin = Matrix4f.transform(invertedProj, clipCoords, null); + Vector4f eyeCoords = new Vector4f(eyeCoords_origin.x, eyeCoords_origin.y, -1f, 0f); + Matrix4f invertedView = Matrix4f.invert(viewMatrix, null); + Vector4f rayWorld = Matrix4f.transform(invertedView, eyeCoords, null); + ray = new Vector3f(rayWorld.x, rayWorld.y, rayWorld.z); + ray.normalise(); + + } + + public Vector3f getPointOnRay(float distance) { +// if (ray == null) +// return null; + Vector3f scaledRay = new Vector3f(ray.x * distance, ray.y * distance, ray.z * distance); + Vector3f rayEnd = Vector3f.add(camPos, scaledRay, null); + return new Vector3f(rayEnd.x, rayEnd.y, rayEnd.z); + } + + public boolean valid() { + return !(projMatrix == null || viewMatrix == null); + } + +} diff --git a/src/main/java/xueli/game2/math/TriFuncMap.java b/src/main/java/xueli/game2/math/TriFuncMap.java new file mode 100644 index 00000000..cf58fc85 --- /dev/null +++ b/src/main/java/xueli/game2/math/TriFuncMap.java @@ -0,0 +1,45 @@ +package xueli.game2.math; + +public class TriFuncMap { + + private static final int scale = 20; + + private static final int mapLength = 360 * scale; + private static final double[] sinMap = new double[mapLength]; + private static final double[] cosMap = new double[mapLength]; + private static final double[] tanMap = new double[mapLength]; + + static { + for (int i = 0; i < mapLength; i++) { + double degree = (double) i / mapLength * 360; + double radius = Math.toRadians(degree); + sinMap[i] = Math.sin(radius); + cosMap[i] = Math.cos(radius); + tanMap[i] = Math.tan(radius); + } + } + + public static double sin(double degree) { + int fittingIndex = getFittingIndex(degree); + return sinMap[fittingIndex]; + } + + public static double cos(double degree) { + int fittingIndex = getFittingIndex(degree); + return cosMap[fittingIndex]; + } + + public static double tan(double degree) { + int fittingIndex = getFittingIndex(degree); + return tanMap[fittingIndex]; + } + + private static int getFittingIndex(double degree) { + // degree = MathUtils.floorMod(degree, 360.0); // When degree is + // -0.000000000000007077671781985373 or something, this method will output 360, + // which leads to an ArrayOutOfBoundsException + double index = degree * scale; + return (int) MathUtils.floorMod(Math.floor(index), mapLength); + } + +} diff --git a/src/main/java/xueli/game2/network/ByteBufReadable.java b/src/main/java/xueli/game2/network/ByteBufReadable.java new file mode 100644 index 00000000..159973fa --- /dev/null +++ b/src/main/java/xueli/game2/network/ByteBufReadable.java @@ -0,0 +1,20 @@ +package xueli.game2.network; + +import java.io.IOException; + +import io.netty.buffer.ByteBuf; + +public class ByteBufReadable implements Readable { + + private final ByteBuf buf; + + public ByteBufReadable(ByteBuf buf) { + this.buf = buf; + } + + @Override + public byte readByte() throws IOException { + return buf.readByte(); + } + +} diff --git a/src/main/java/xueli/game2/network/ByteBufWritable.java b/src/main/java/xueli/game2/network/ByteBufWritable.java new file mode 100644 index 00000000..5914fe8b --- /dev/null +++ b/src/main/java/xueli/game2/network/ByteBufWritable.java @@ -0,0 +1,22 @@ +package xueli.game2.network; + +import java.io.IOException; + +import io.netty.buffer.ByteBuf; + +public class ByteBufWritable implements Writable { + + private final ByteBuf buf; + + public ByteBufWritable(ByteBuf buf) { + this.buf = buf; + + } + + @Override + public void writeByte(byte b) throws IOException { + buf.writeByte(b); + + } + +} diff --git a/src/main/java/xueli/game2/network/ByteBufferWritable.java b/src/main/java/xueli/game2/network/ByteBufferWritable.java new file mode 100644 index 00000000..0ae11f53 --- /dev/null +++ b/src/main/java/xueli/game2/network/ByteBufferWritable.java @@ -0,0 +1,19 @@ +package xueli.game2.network; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ByteBufferWritable implements Writable { + + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + @Override + public void writeByte(byte b) throws IOException { + out.write(b); + } + + public byte[] toByteArray() { + return out.toByteArray(); + } + +} diff --git a/src/main/java/xueli/game2/network/ClientConnection.java b/src/main/java/xueli/game2/network/ClientConnection.java new file mode 100644 index 00000000..d8209086 --- /dev/null +++ b/src/main/java/xueli/game2/network/ClientConnection.java @@ -0,0 +1,143 @@ +package xueli.game2.network; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import xueli.game2.network.pipeline.PacketDecoder; +import xueli.game2.network.pipeline.PacketEncoder; +import xueli.game2.network.pipeline.PacketSizeDecodeHandler; +import xueli.game2.network.pipeline.PacketSizePrefixer; + +public class ClientConnection extends SimpleChannelInboundHandler { + + private static final EventLoopGroup workerGroup = new NioEventLoopGroup(1); + + private ChannelHandlerContext channel; + + private String hostname; + private int port; + + private boolean connected = true; + + ClientConnection() { + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + this.channel = ctx; + super.channelActive(ctx); + + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Packet msg) throws Exception { + this.packetRead(msg); + } + + protected void packetRead(Packet msg) { + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.close(); + super.exceptionCaught(ctx, cause); + + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + this.connected = false; + + } + + public void writeAndFlush(Object p) { + this.writeAndFlush(p, true); + } + + public void writeAndFlush(Object p, PacketListener listener) { + ChannelFuture future = this.channel.writeAndFlush(p); + future.addListener(f -> { + if (f.isDone()) { + listener.onPacketSentSuccessfully(); + } else { + Packet failurePacket = listener.onPacketSendFailure(); + if (failurePacket != null) { + this.channel.writeAndFlush(failurePacket) + .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + } + + }); + + } + + public void writeAndFlush(Object p, boolean async) { + ChannelFuture future = this.channel.writeAndFlush(p); + if (!async) { + try { + future.sync(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + } + + public void tick() { + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public void close() { + if (this.channel != null) { + this.channel.close().awaitUninterruptibly(); + this.connected = false; + } + + } + + public boolean isConnected() { + return connected; + } + + public static ClientConnection connectToServer(Protocol clientboundProtocol, Protocol serverboundProtocol, + InetSocketAddress addr) throws IOException { + ClientConnection c = new ClientConnection(); + c.hostname = addr.getHostName(); + c.port = addr.getPort(); + + new Bootstrap().group(workerGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new PacketSizePrefixer()).addLast(new PacketEncoder(serverboundProtocol)) + + .addLast(new PacketSizeDecodeHandler()).addLast(new PacketDecoder(clientboundProtocol)) + .addLast(c); + } + }).connect(addr).syncUninterruptibly(); + return c; + } + + public static void shutdown() { + workerGroup.shutdownGracefully(); + } + +} diff --git a/src/main/java/xueli/game2/network/CodecConstants.java b/src/main/java/xueli/game2/network/CodecConstants.java new file mode 100644 index 00000000..74ae69ef --- /dev/null +++ b/src/main/java/xueli/game2/network/CodecConstants.java @@ -0,0 +1,10 @@ +package xueli.game2.network; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class CodecConstants { + + public static final Charset STRING_CHARSET = StandardCharsets.UTF_8; + +} diff --git a/src/main/java/xueli/game2/network/ConnectionStageListener.java b/src/main/java/xueli/game2/network/ConnectionStageListener.java new file mode 100644 index 00000000..3eec7156 --- /dev/null +++ b/src/main/java/xueli/game2/network/ConnectionStageListener.java @@ -0,0 +1,23 @@ +package xueli.game2.network; + +import xueli.game2.network.processor.PacketProcessor; + +public class ConnectionStageListener { + + private final T ctx; + protected final PacketProcessor processor = new PacketProcessor(); + + public ConnectionStageListener(T ctx) { + this.ctx = ctx; + + } + + public void doProcess(Packet packet) { + processor.doProcess(packet); + } + + public T getConnection() { + return ctx; + } + +} diff --git a/src/main/java/xueli/game2/network/Packet.java b/src/main/java/xueli/game2/network/Packet.java new file mode 100644 index 00000000..17fa0e88 --- /dev/null +++ b/src/main/java/xueli/game2/network/Packet.java @@ -0,0 +1,22 @@ +package xueli.game2.network; + +import java.io.IOException; + +public abstract class Packet { + + public Packet() { + } + + public Packet(Readable buf) { + try { + read(buf); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public abstract void read(Readable buf) throws IOException; + + public abstract void write(Writable buf) throws IOException; + +} diff --git a/src/main/java/xueli/game2/network/PacketListener.java b/src/main/java/xueli/game2/network/PacketListener.java new file mode 100755 index 00000000..9797880e --- /dev/null +++ b/src/main/java/xueli/game2/network/PacketListener.java @@ -0,0 +1,11 @@ +package xueli.game2.network; + +public interface PacketListener { + + public void onPacketSentSuccessfully(); + + default Packet onPacketSendFailure() { + return null; + } + +} diff --git a/src/main/java/xueli/game2/network/PrimitiveCodec.java b/src/main/java/xueli/game2/network/PrimitiveCodec.java new file mode 100644 index 00000000..205a3f5a --- /dev/null +++ b/src/main/java/xueli/game2/network/PrimitiveCodec.java @@ -0,0 +1,339 @@ +package xueli.game2.network; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.lwjgl.utils.vector.Vector3i; + +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.Tag; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.flowpowered.nbt.stream.NBTOutputStream; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +public interface PrimitiveCodec { + + public T read(Readable r) throws IOException; + + public void write(T t, Writable w) throws IOException; + + default List readArr(Readable r) throws IOException { + int count = VAR_INT.read(r); + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + list.add(read(r)); + } + return list; + } + + default void writeArr(T[] arr, Writable w) throws IOException { + int count = arr.length; + VAR_INT.write(count, w); + for (int i = 0; i < count; i++) { + write(arr[i], w); + } + } + + PrimitiveCodec INT = new PrimitiveCodec<>() { + @Override + public Integer read(Readable r) throws IOException { + return r.readInteger(); + } + + @Override + public void write(Integer integer, Writable w) throws IOException { + w.writeInteger(integer); + } + }; + + PrimitiveCodec BYTE = new PrimitiveCodec<>() { + @Override + public Byte read(Readable r) throws IOException { + return r.readByte(); + } + + @Override + public void write(Byte aByte, Writable w) throws IOException { + w.writeByte(aByte); + } + }; + + PrimitiveCodec SHORT = new PrimitiveCodec<>() { + @Override + public Short read(Readable r) throws IOException { + return r.readShort(); + } + + @Override + public void write(Short aShort, Writable w) throws IOException { + w.writeShort(aShort); + } + }; + + PrimitiveCodec LONG = new PrimitiveCodec<>() { + @Override + public Long read(Readable r) throws IOException { + return r.readLong(); + } + + @Override + public void write(Long aLong, Writable w) throws IOException { + w.writeLong(aLong); + } + }; + + PrimitiveCodec UNSIGNED_BYTE = new PrimitiveCodec<>() { + @Override + public Integer read(Readable r) throws IOException { + return Byte.toUnsignedInt(BYTE.read(r)); + } + + @Override + public void write(Integer integer, Writable w) throws IOException { + BYTE.write(integer.byteValue(), w); + } + }; + + PrimitiveCodec UNSIGNED_SHORT = new PrimitiveCodec<>() { + @Override + public Integer read(Readable r) throws IOException { + return Short.toUnsignedInt(SHORT.read(r)); + } + + @Override + public void write(Integer integer, Writable w) throws IOException { + SHORT.write(integer.shortValue(), w); + } + }; + + PrimitiveCodec FLOAT = new PrimitiveCodec<>() { + @Override + public Float read(Readable r) throws IOException { + return r.readFloat(); + } + + @Override + public void write(Float aFloat, Writable w) throws IOException { + w.writeFloat(aFloat); + } + }; + + PrimitiveCodec DOUBLE = new PrimitiveCodec<>() { + @Override + public Double read(Readable r) throws IOException { + return r.readDouble(); + } + + @Override + public void write(Double d, Writable w) throws IOException { + w.writeDouble(d); + } + }; + + PrimitiveCodec BOOLEAN = new PrimitiveCodec<>() { + @Override + public Boolean read(Readable r) throws IOException { + return r.readBoolean(); + } + + @Override + public void write(Boolean aBoolean, Writable w) throws IOException { + w.writeBoolean(aBoolean); + } + }; + + PrimitiveCodec VAR_INT = new PrimitiveCodec<>() { + + @Override + public Integer read(Readable r) throws IOException { + byte readByte; + int loopCount = 0; + int result = 0; + + while (true) { + readByte = r.readByte(); + result |= (((int) readByte) & (0b1111111)) << (loopCount * 7); + + if ((readByte & (1 << 7)) == 0) + break; + loopCount++; + + } + + return result; + } + + @Override + public void write(Integer integer, Writable w) throws IOException { + int i = integer; + int nextI; + + boolean flag; + byte thisByte; + + while (true) { + thisByte = (byte) (i & 0b1111111); + + nextI = i >>> 7; + flag = nextI != 0; + if (flag) { + thisByte |= (1 << 7); + } + + w.writeByte(thisByte); + + if (!flag) + break; + i = nextI; + + } + } + + }; + + PrimitiveCodec VAR_LONG = new PrimitiveCodec<>() { + @Override + public Long read(Readable r) throws IOException { + byte readByte; + int loopCount = 0; + long result = 0; + + while (true) { + readByte = r.readByte(); + result |= (((long) readByte) & (0b1111111)) << (loopCount * 7); + + if ((readByte & (1 << 7)) == 0) + break; + loopCount++; + + } + + return result; + } + + @Override + public void write(Long aLong, Writable w) throws IOException { + long i = aLong; + long nextI; + + boolean flag; + byte thisByte; + + while (true) { + thisByte = (byte) (i & 0b1111111); + + nextI = i >>> 7; + flag = nextI != 0; + if (flag) { + thisByte |= (1 << 7); + } + + w.writeByte(thisByte); + + if (!flag) + break; + i = nextI; + + } + + } + }; + + PrimitiveCodec STRING = new PrimitiveCodec<>() { + @Override + public String read(Readable r) throws IOException { + int size = VAR_INT.read(r); + return r.readString(size); + } + + @Override + public void write(String s, Writable w) throws IOException { + byte[] bytes = s.getBytes(CodecConstants.STRING_CHARSET); + VAR_INT.write(bytes.length, w); + w.writeBytes(bytes); + } + }; + + // Write "e37d953a-33b2-4554-ab88-b58fb440ab16" + // But read "ffffffff-ffff-ab16-ffff-ffffffb24554" ??? + // It fixed. + // Oh, all of java's integers are signed!!! + PrimitiveCodec UUID = new PrimitiveCodec<>() { + @Override + public java.util.UUID read(Readable r) throws IOException { + long mostSigBits = r.readLong(); + long leaseSigBits = r.readLong(); + return new UUID(mostSigBits, leaseSigBits); + } + + @Override + public void write(java.util.UUID uuid, Writable w) throws IOException { + w.writeLong(uuid.getMostSignificantBits()); + w.writeLong(uuid.getLeastSignificantBits()); + + } + }; + + PrimitiveCodec JSON = new PrimitiveCodec<>() { + + private static final Gson GSON = new Gson(); + + @Override + public JsonObject read(Readable r) throws IOException { + String str = STRING.read(r); + return GSON.fromJson(str, JsonObject.class); + } + + @Override + public void write(JsonObject jsonElement, Writable w) throws IOException { + String str = jsonElement.toString(); + STRING.write(str, w); + + } + }; + + PrimitiveCodec NBT_NOT_COMPRESSED = new PrimitiveCodec<>() { + @Override + public CompoundTag read(Readable r) throws IOException { + NBTInputStream nbtIn = new NBTInputStream(r.toInputStream(), false); + Tag tag = nbtIn.readTag(); + nbtIn.close(); + return (CompoundTag) tag; + } + + @Override + public void write(CompoundTag compoundTag, Writable w) throws IOException { + NBTOutputStream nbtOut = new NBTOutputStream(w.toOutputStream(), false); + nbtOut.writeTag(compoundTag); + nbtOut.flush(); + nbtOut.close(); + + } + }; + + PrimitiveCodec BLOCK_POS = new PrimitiveCodec<>() { + + @Override + public Vector3i read(Readable r) throws IOException { + long longVal = LONG.read(r); + int x = (int) (longVal >> 38); + int z = (int) ((longVal << 52) >> 52); + int y = (int) ((longVal << 26) >> 38); + return new Vector3i(x, y, z); + } + + public void write(Vector3i t, Writable w) throws IOException { + long val = 0L; + val |= ((long) (t.getX() & 0x3FFFFFF) << 38); + val |= ((long) (t.getZ() & 0x3FFFFFF) << 12); + val |= (t.getY() & 0xFFF); + LONG.write(val, w); + + }; + + }; + +} diff --git a/src/main/java/xueli/game2/network/Protocol.java b/src/main/java/xueli/game2/network/Protocol.java new file mode 100644 index 00000000..0c684ffe --- /dev/null +++ b/src/main/java/xueli/game2/network/Protocol.java @@ -0,0 +1,26 @@ +package xueli.game2.network; + +import java.util.HashMap; +import java.util.function.Function; + +public class Protocol { + + private final HashMap> deserializers = new HashMap<>(); + private final HashMap, Integer> packets = new HashMap<>(); + + @SuppressWarnings("unchecked") + public Protocol register(int id, Class clazz, Function deserializer) { + this.deserializers.put(id, (Function) deserializer); + this.packets.put(clazz, id); + return this; + } + + public Function getDeserializerById(int id) { + return this.deserializers.get(id); + } + + public int getIdByClazz(Class clazz) { + return this.packets.get(clazz); + } + +} diff --git a/src/main/java/xueli/game2/network/Readable.java b/src/main/java/xueli/game2/network/Readable.java new file mode 100644 index 00000000..fb9d4ea6 --- /dev/null +++ b/src/main/java/xueli/game2/network/Readable.java @@ -0,0 +1,56 @@ +package xueli.game2.network; + +import java.io.IOException; +import java.io.InputStream; + +import xueli.utils.Bytes; + +public interface Readable { + + default public int readInteger() throws IOException { + return Bytes.getInt(readBytes(4)); + } + + default public short readShort() throws IOException { + return Bytes.getShort(readBytes(2)); + } + + default public long readLong() throws IOException { + return Bytes.getLong(readBytes(8)); + } + default public float readFloat() throws IOException { + return Float.intBitsToFloat(readInteger()); + } + + default public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + default public boolean readBoolean() throws IOException { + return readByte() != 0; + } + + default public String readString(int length) throws IOException { + return new String(readBytes(length), CodecConstants.STRING_CHARSET); + } + + default public byte[] readBytes(int length) throws IOException { + byte[] bs = new byte[length]; + for (int i = 0; i < length; i++) { + bs[i] = readByte(); + } + return bs; + } + + public byte readByte() throws IOException; + + default InputStream toInputStream() { + return new InputStream() { + @Override + public int read() throws IOException { + return readByte(); + } + }; + } + +} diff --git a/src/main/java/xueli/game2/network/Server.java b/src/main/java/xueli/game2/network/Server.java new file mode 100755 index 00000000..f4b6a3d7 --- /dev/null +++ b/src/main/java/xueli/game2/network/Server.java @@ -0,0 +1,112 @@ +package xueli.game2.network; + +import java.util.LinkedList; +import java.util.Iterator; +import java.util.function.Supplier; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import xueli.game2.lifecycle.RunnableLifeCycle; +import xueli.game2.network.pipeline.PacketDecoder; +import xueli.game2.network.pipeline.PacketEncoder; +import xueli.game2.network.pipeline.PacketSizeDecodeHandler; +import xueli.game2.network.pipeline.PacketSizePrefixer; + +public class Server implements RunnableLifeCycle { + + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + + private int port; + + private final Protocol clientboundProtocol, serverboundProtocol; + private final Supplier connFunc; + + private final LinkedList connections = new LinkedList<>(); + + public Server(int port, Supplier connFunc, Protocol clientboundProtocol, Protocol serverboundProtocol) { + this.port = port; + + this.clientboundProtocol = clientboundProtocol; + this.serverboundProtocol = serverboundProtocol; + + this.connFunc = connFunc; + + } + + private ChannelFuture serverFuture; + private boolean isRunning = false; + + @Override + public void init() { + this.serverFuture = new ServerBootstrap().group(workerGroup).channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) throws Exception { + T conn = connFunc.get(); + synchronized(connections) { + connections.add(conn); + } + + ch.pipeline().addLast(new PacketSizePrefixer()).addLast(new PacketEncoder(clientboundProtocol)) + + .addLast(new PacketSizeDecodeHandler()).addLast(new PacketDecoder(serverboundProtocol)) + .addLast(conn); + + } + }).bind(port).syncUninterruptibly(); + this.isRunning = true; + + } + + @Override + public void tick() { + // we can get an iterator of the list and we can remove it immediately, learnt + // from source code of Minecraft + // But the code must be "synchronized", or it'll throw a ConcurrentModificationException + //@see java.util.ConcurrentModificationException + synchronized(connections) { + Iterator iterable = connections.iterator(); + while (iterable.hasNext()) { + T t = iterable.next(); + t.tick(); + if (!t.isConnected()) { + iterable.remove(); + } + } + } + + } + + public void broadcast(Object obj) { + synchronized(connections) { + this.connections.forEach(l -> l.writeAndFlush(obj)); + } + + } + + @Override + public void release() { + this.isRunning = false; + if (serverFuture != null) { + try { + serverFuture.channel().close().sync(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + workerGroup.shutdownGracefully(); + + } + + @Override + public boolean isRunning() { + return this.isRunning; + } + +} diff --git a/src/main/java/xueli/game2/network/ServerClientConnection.java b/src/main/java/xueli/game2/network/ServerClientConnection.java new file mode 100755 index 00000000..b812a95b --- /dev/null +++ b/src/main/java/xueli/game2/network/ServerClientConnection.java @@ -0,0 +1,8 @@ +package xueli.game2.network; + +public class ServerClientConnection extends ClientConnection { + + public ServerClientConnection() { + } + +} diff --git a/src/main/java/xueli/game2/network/Writable.java b/src/main/java/xueli/game2/network/Writable.java new file mode 100644 index 00000000..7ec2108d --- /dev/null +++ b/src/main/java/xueli/game2/network/Writable.java @@ -0,0 +1,55 @@ +package xueli.game2.network; + +import java.io.IOException; +import java.io.OutputStream; + +import xueli.utils.Bytes; + +public interface Writable { + + default public void writeInteger(int i) throws IOException { + writeBytes(Bytes.getBytes(i)); + } + + default public void writeShort(short i) throws IOException { + writeBytes(Bytes.getBytes(i)); + } + + default public void writeLong(long i) throws IOException { + writeBytes(Bytes.getBytes(i)); + } + + default public void writeFloat(float f) throws IOException { + writeInteger(Float.floatToIntBits(f)); + } + + default public void writeDouble(double d) throws IOException { + writeLong(Double.doubleToLongBits(d)); + } + + default public void writeBoolean(boolean b) throws IOException { + writeByte((byte) (b ? 1 : 0)); + } + + default public void writeString(String s) throws IOException { + writeBytes(s.getBytes(CodecConstants.STRING_CHARSET)); + } + + default public void writeBytes(byte[] b) throws IOException { + for (int i = 0; i < b.length; i++) { + writeByte(b[i]); + } + } + + public void writeByte(byte b) throws IOException; + + default public OutputStream toOutputStream() { + return new OutputStream() { + @Override + public void write(int b) throws IOException { + writeByte((byte) (b & 0xFF)); + } + }; + } + +} diff --git a/src/main/java/xueli/game2/network/codec/CodecParam.java b/src/main/java/xueli/game2/network/codec/CodecParam.java new file mode 100644 index 00000000..eb7be895 --- /dev/null +++ b/src/main/java/xueli/game2/network/codec/CodecParam.java @@ -0,0 +1,5 @@ +package xueli.game2.network.codec; + +public @interface CodecParam { + +} diff --git a/src/main/java/xueli/game2/network/codec/PacketCodec.java b/src/main/java/xueli/game2/network/codec/PacketCodec.java new file mode 100644 index 00000000..c49def0d --- /dev/null +++ b/src/main/java/xueli/game2/network/codec/PacketCodec.java @@ -0,0 +1,21 @@ +package xueli.game2.network.codec; + +/** + * In the "codec", we are going to use "reflection" to automatically get + * everything on place + */ +public class PacketCodec { + + public PacketCodec() { + + } + + public void codecRead() { + + } + + public void codecWrite() { + + } + +} diff --git a/src/main/java/xueli/game2/network/pipeline/PacketDecoder.java b/src/main/java/xueli/game2/network/pipeline/PacketDecoder.java new file mode 100644 index 00000000..600b9d21 --- /dev/null +++ b/src/main/java/xueli/game2/network/pipeline/PacketDecoder.java @@ -0,0 +1,51 @@ +package xueli.game2.network.pipeline; + +import java.util.List; +import java.util.function.Function; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import xueli.game2.network.ByteBufReadable; +import xueli.game2.network.Packet; +import xueli.game2.network.PrimitiveCodec; +import xueli.game2.network.Protocol; +import xueli.game2.network.Readable; +import xueli.utils.logger.Logger; + +public class PacketDecoder extends ByteToMessageDecoder { + + private static final Logger LOGGER = new Logger(); + + private Protocol protocol; + + public PacketDecoder(Protocol protocol) { + this.protocol = protocol; + + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + Readable readable = new ByteBufReadable(in); + int id = PrimitiveCodec.VAR_INT.read(readable); + + Function deserializer = protocol.getDeserializerById(id); + if (deserializer != null) { + Packet packet = deserializer.apply(readable); + out.add(packet); + + LOGGER.info("Receive Packet: " + packet); + + } else { + LOGGER.info("Receive Packet Error: " + id); + + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/src/main/java/xueli/game2/network/pipeline/PacketEncoder.java b/src/main/java/xueli/game2/network/pipeline/PacketEncoder.java new file mode 100644 index 00000000..cbea05a4 --- /dev/null +++ b/src/main/java/xueli/game2/network/pipeline/PacketEncoder.java @@ -0,0 +1,43 @@ +package xueli.game2.network.pipeline; + +import java.io.IOException; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import xueli.game2.network.ByteBufWritable; +import xueli.game2.network.Packet; +import xueli.game2.network.PrimitiveCodec; +import xueli.game2.network.Protocol; +import xueli.game2.network.Writable; +import xueli.utils.logger.Logger; + +public class PacketEncoder extends MessageToByteEncoder { + + private static final Logger LOGGER = new Logger(); + + private Protocol protocol; + + public PacketEncoder(Protocol protocol) { + this.protocol = protocol; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Packet msg, ByteBuf out) throws Exception { + int id = protocol.getIdByClazz(msg.getClass()); + Writable writable = new ByteBufWritable(out) { + @Override + public void writeByte(byte b) throws IOException { + super.writeByte(b); +// System.out.println(b); + } + }; + + PrimitiveCodec.VAR_INT.write(id, writable); + msg.write(writable); + + LOGGER.info("Write Packet: " + msg); + + } + +} diff --git a/src/main/java/xueli/game2/network/pipeline/PacketSizeDecodeHandler.java b/src/main/java/xueli/game2/network/pipeline/PacketSizeDecodeHandler.java new file mode 100644 index 00000000..278f4666 --- /dev/null +++ b/src/main/java/xueli/game2/network/pipeline/PacketSizeDecodeHandler.java @@ -0,0 +1,33 @@ +package xueli.game2.network.pipeline; + +import java.util.List; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import xueli.game2.network.ByteBufReadable; +import xueli.game2.network.PrimitiveCodec; + +public class PacketSizeDecodeHandler extends ByteToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + in.markReaderIndex(); + + ByteBufReadable readable = new ByteBufReadable(in); + int read = PrimitiveCodec.VAR_INT.read(readable); + + if (read <= in.readableBytes()) { + out.add(in.readBytes(read)); + } else { + in.resetReaderIndex(); + } + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + } + +} diff --git a/src/main/java/xueli/game2/network/pipeline/PacketSizePrefixer.java b/src/main/java/xueli/game2/network/pipeline/PacketSizePrefixer.java new file mode 100644 index 00000000..129250ea --- /dev/null +++ b/src/main/java/xueli/game2/network/pipeline/PacketSizePrefixer.java @@ -0,0 +1,20 @@ +package xueli.game2.network.pipeline; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import xueli.game2.network.ByteBufWritable; +import xueli.game2.network.PrimitiveCodec; + +public class PacketSizePrefixer extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { + int byteCount = msg.readableBytes(); + ByteBufWritable writable = new ByteBufWritable(out); + PrimitiveCodec.VAR_INT.write(byteCount, writable); + out.writeBytes(msg, msg.readerIndex(), byteCount); + + } + +} diff --git a/src/main/java/xueli/game2/network/processor/PacketProcessor.java b/src/main/java/xueli/game2/network/processor/PacketProcessor.java new file mode 100644 index 00000000..19a79b3a --- /dev/null +++ b/src/main/java/xueli/game2/network/processor/PacketProcessor.java @@ -0,0 +1,37 @@ +package xueli.game2.network.processor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.function.Consumer; + +import xueli.game2.network.Packet; + +public class PacketProcessor { + + private final HashMap, ArrayList>> packetProcessors = new HashMap<>(); + + public PacketProcessor() { + } + + public void addProcessor(Class clazz, Consumer processor) { + ArrayList> list = packetProcessors.computeIfAbsent(clazz, c -> new ArrayList<>()); + list.add(processor); + + } + + @SuppressWarnings("unchecked") + public void doProcess(T packet) { + Class clazz = packet.getClass(); + ArrayList> list = packetProcessors.get(clazz); + if (list == null) { + System.err.println("Bugs? No Packet Processor for " + packet); + return; + } + + list.forEach(consumer -> { + ((Consumer) consumer).accept(packet); + }); + + } + +} diff --git a/src/main/java/xueli/game2/phys/aabb/AABB.java b/src/main/java/xueli/game2/phys/aabb/AABB.java new file mode 100644 index 00000000..d1f7124a --- /dev/null +++ b/src/main/java/xueli/game2/phys/aabb/AABB.java @@ -0,0 +1,69 @@ +package xueli.game2.phys.aabb; + +import org.lwjgl.utils.vector.Vector3d; + +public class AABB { + + protected Vector3d v1 = new Vector3d(), v2 = new Vector3d(); + + private AABB() { + } + + public AABB(Vector3d v1, Vector3d v2) { + this.v1.x = Math.min(v1.x, v2.x); + this.v1.y = Math.min(v1.y, v2.y); + this.v1.z = Math.min(v1.z, v2.z); + + this.v2.x = Math.max(v1.x, v2.x); + this.v2.y = Math.max(v1.y, v2.y); + this.v2.z = Math.max(v1.z, v2.z); + + } + + public Vector3d getVmin() { + return v1; + } + + public Vector3d getVmax() { + return v2; + } + + public AABB add(Vector3d pos) { + return new AABB(Vector3d.add(this.v1, pos), Vector3d.add(this.v2, pos)); + } + + public AABB expand(Vector3d v) { + AABB aabb = new AABB(); + if (v.x < 0) { + aabb.v1.x = this.v1.x + v.x; + aabb.v2.x = this.v2.x; + } else { + aabb.v1.x = this.v1.x; + aabb.v2.x = this.v2.x + v.x; + } + + if (v.y < 0) { + aabb.v1.y = this.v1.y + v.y; + aabb.v2.y = this.v2.y; + } else { + aabb.v1.y = this.v1.y; + aabb.v2.y = this.v2.y + v.y; + } + + if (v.z < 0) { + aabb.v1.z = this.v1.z + v.z; + aabb.v2.z = this.v2.z; + } else { + aabb.v1.z = this.v1.z; + aabb.v2.z = this.v2.z + v.z; + } + + return aabb; + } + + @Override + public String toString() { + return "AABB{" + "v1=" + v1 + ", v2=" + v2 + '}'; + } + +} diff --git a/src/main/java/xueli/game2/phys/aabb/BoxFace.java b/src/main/java/xueli/game2/phys/aabb/BoxFace.java new file mode 100644 index 00000000..a7a0f631 --- /dev/null +++ b/src/main/java/xueli/game2/phys/aabb/BoxFace.java @@ -0,0 +1,20 @@ +package xueli.game2.phys.aabb; + +import org.lwjgl.utils.vector.Vector3i; + +public enum BoxFace { + + X_MINUS(new Vector3i(-1, 0, 0)), X_PLUS(new Vector3i(1, 0, 0)), Y_MINUS(new Vector3i(0, -1, 0)), + Y_PLUS(new Vector3i(0, 1, 0)), Z_MINUS(new Vector3i(0, 0, -1)), Z_PLUS(new Vector3i(0, 0, 1)); + + private Vector3i faceTo; + + private BoxFace(Vector3i faceTo) { + this.faceTo = faceTo; + } + + public Vector3i getFaceToVector() { + return faceTo; + } + +} diff --git a/src/main/java/xueli/game2/phys/aabb/NameableAABB.java b/src/main/java/xueli/game2/phys/aabb/NameableAABB.java new file mode 100644 index 00000000..f815fe5f --- /dev/null +++ b/src/main/java/xueli/game2/phys/aabb/NameableAABB.java @@ -0,0 +1,23 @@ +package xueli.game2.phys.aabb; + +import org.lwjgl.utils.vector.Vector3d; + +public class NameableAABB extends AABB { + + private final String name; + + public NameableAABB(String name, Vector3d v1, Vector3d v2) { + super(v1, v2); + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "NameableAABB{" + "name='" + name + '\'' + ", v1=" + v1 + ", v2=" + v2 + '}'; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/BackRenderBuffer.java b/src/main/java/xueli/game2/renderer/legacy/BackRenderBuffer.java new file mode 100644 index 00000000..ce7f6d17 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/BackRenderBuffer.java @@ -0,0 +1,17 @@ +package xueli.game2.renderer.legacy; + +import xueli.game2.renderer.legacy.buffer.BufferStorable; + +public interface BackRenderBuffer { + + default public void applyToBuffer(int id, BufferStorable... storable) { + for (int i = 0; i < storable.length; i++) { + this.applyToBuffer(id, storable[i]); + } + } + + public void applyToBuffer(int id, BufferStorable storable); + + public void flip(); + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/BufferPool.java b/src/main/java/xueli/game2/renderer/legacy/BufferPool.java new file mode 100644 index 00000000..61d59a31 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/BufferPool.java @@ -0,0 +1,91 @@ +package xueli.game2.renderer.legacy; + +import java.util.LinkedList; + +import xueli.utils.buffer.LotsOfByteBuffer; + +// Won't be deleted before figuring out why? —— Xueli +/* + Some Exceptions natively will be thrown: + + Current thread (0x000001532bcffd70): JavaThread "main" [_thread_in_native, id=11436, stack(0x00000004dc400000,0x00000004dc500000)] + + Stack: [0x00000004dc400000,0x00000004dc500000], sp=0x00000004dc4fe710, free space=1017k + Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) + C [ntdll.dll+0xa6d98] + C [ntdll.dll+0x406e1] + C [AcLayers.DLL+0x72af] + C [atio6axx.dll+0x1af18bc] + + Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) + J 1274 org.lwjgl.opengl.GL15C.nglBufferData(IJJI)V (0 bytes) @ 0x000001533b0985f6 [0x000001533b0985a0+0x0000000000000056] + j org.lwjgl.opengl.GL15C.glBufferData(ILjava/nio/ByteBuffer;I)V+11 + j org.lwjgl.opengl.GL15.glBufferData(ILjava/nio/ByteBuffer;I)V+3 + j xueli.game2.renderer.legacy.buffer.AttributeBuffer.lambda$tick$0()V+8 + j xueli.game2.renderer.legacy.buffer.AttributeBuffer$$Lambda$92+0x0000000800cc78f0.run()V+4 + J 1275 c1 xueli.game2.renderer.legacy.buffer.Bindable.bind(Ljava/lang/Runnable;)V (19 bytes) @ 0x0000015333d238a4 [0x0000015333d236e0+0x00000000000001c4] + j xueli.game2.renderer.legacy.buffer.AttributeBuffer.tick()V+21 + j xueli.game2.renderer.legacy.buffer.VertexAttribute$$Lambda$91+0x0000000800cc76c8.accept(Ljava/lang/Object;)V+4 + J 1279 c1 java.util.HashMap$Values.forEach(Ljava/util/function/Consumer;)V java.base@17-ea (119 bytes) @ 0x0000015333d2491c [0x0000015333d24700+0x000000000000021c] + j xueli.game2.renderer.legacy.buffer.VertexAttribute.render(I)V+16 + j xueli.game2.renderer.legacy.VertexAttributeRenderBuffer.render()V+8 + j xueli.mcremake.classic.client.renderer.gui.RenderTypeTexture2D.lambda$render$1(Ljava/lang/Integer;Lxueli/game2/renderer/legacy/RenderBuffer;)V+16 + j xueli.mcremake.classic.client.renderer.gui.RenderTypeTexture2D$$Lambda$90+0x0000000800cc74a0.accept(Ljava/lang/Object;Ljava/lang/Object;)V+8 + J 1242 c1 java.util.HashMap.forEach(Ljava/util/function/BiConsumer;)V java.base@17-ea (112 bytes) @ 0x0000015333d15ba4 [0x0000015333d15980+0x0000000000000224] + j xueli.mcremake.classic.client.renderer.gui.RenderTypeTexture2D.render()V+16 + j xueli.mcremake.classic.client.renderer.gui.MyFont.tick()V+54 + j xueli.game2.renderer.ui.MyGui.tick()V+4 + j xueli.mcremake.classic.client.CraftGameClient.render()V+22 + j xueli.game2.display.GameDisplay.tick()V+72 + j xueli.game2.lifecycle.RunnableLifeCycle.run()V+16 + j xueli.mcremake.classic.client.CraftGameMain.main([Ljava/lang/String;)V+7 + v ~StubRoutines::call_stub + + siginfo: EXCEPTION_ACCESS_VIOLATION (0xc0000005), reading address 0xffffffffffffffff + +*/ +@Deprecated +public class BufferPool { + + private final LinkedList bufferPool = new LinkedList<>(); + private final int maxCount; + private final int allocateSizeAccordingToBefore; + + public BufferPool(int maxCount, int allocateSizeAccordingToBefore) { + this.maxCount = maxCount; + this.allocateSizeAccordingToBefore = allocateSizeAccordingToBefore; + } + + public LotsOfByteBuffer newBuffer() { + if (bufferPool.size() > this.maxCount) { + LotsOfByteBuffer buf = bufferPool.pop(); + buf.release(); + } + + LotsOfByteBuffer buf; + if (allocateSizeAccordingToBefore > 0) { + int accordCount = Math.min(bufferPool.size(), this.allocateSizeAccordingToBefore); + int sizeSum = 0; + + for (int i = 1; i <= accordCount; i++) { + LotsOfByteBuffer lastBuf = bufferPool.get(bufferPool.size() - i); + sizeSum += lastBuf.getSize(); + } + + if (sizeSum > 0) { + buf = new LotsOfByteBuffer(sizeSum / accordCount); + } else { + buf = new LotsOfByteBuffer(); + } + + } else { + buf = new LotsOfByteBuffer(); + } + bufferPool.addLast(buf); + +// System.out.println(this.bufferPool); + + return buf; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/FrameBuffer.java b/src/main/java/xueli/game2/renderer/legacy/FrameBuffer.java new file mode 100644 index 00000000..45a82d92 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/FrameBuffer.java @@ -0,0 +1,161 @@ +package xueli.game2.renderer.legacy; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_LINEAR; +import static org.lwjgl.opengl.GL11.GL_RGBA; +import static org.lwjgl.opengl.GL11.GL_STENCIL_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glClear; +import static org.lwjgl.opengl.GL11.glDeleteTextures; +import static org.lwjgl.opengl.GL11.glGenTextures; +import static org.lwjgl.opengl.GL11.glReadPixels; +import static org.lwjgl.opengl.GL11.glTexImage2D; +import static org.lwjgl.opengl.GL11.glTexParameteri; +import static org.lwjgl.opengl.GL11.glViewport; +import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT0; +import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8; +import static org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL_ATTACHMENT; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER; +import static org.lwjgl.opengl.GL30.glBindFramebuffer; +import static org.lwjgl.opengl.GL30.glBindRenderbuffer; +import static org.lwjgl.opengl.GL30.glCheckFramebufferStatus; +import static org.lwjgl.opengl.GL30.glDeleteFramebuffers; +import static org.lwjgl.opengl.GL30.glDeleteRenderbuffers; +import static org.lwjgl.opengl.GL30.glFramebufferRenderbuffer; +import static org.lwjgl.opengl.GL30.glFramebufferTexture2D; +import static org.lwjgl.opengl.GL30.glGenFramebuffers; +import static org.lwjgl.opengl.GL30.glGenRenderbuffers; +import static org.lwjgl.opengl.GL30.glRenderbufferStorage; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +public class FrameBuffer { + + protected int width, height; + + protected int fbo, textureId, rbo; + + public FrameBuffer(int width, int height) { + this.width = width; + this.height = height; + + create(); + + } + + protected void create() { + this.fbo = glGenFramebuffers(); + + this.textureId = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, textureId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + this.rbo = glGenRenderbuffers(); + glBindRenderbuffer(GL_RENDERBUFFER, rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("FrameBuffer not complete!"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + } + + // TODO: UPSIDE DOWN + public void save(String path) { + int[] data = new int[width * height]; + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + for (int i = 0; i < data.length; i++) { + int origin = data[i]; + int r = (origin >> 0) & 0xff; + int g = (origin >> 8) & 0xff; + int b = (origin >> 16) & 0xff; + int a = (origin >> 24) & 0xff; + data[i] = (a << 24) + (r << 16) + (g << 8) + b; + + } + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + image.setRGB(0, 0, width, height, data, 0, width); + +// BufferedImage newImage = new BufferedImage(width, height, image.getType()); +// Graphics2D g = newImage.createGraphics(); +// g.rotate(Math.toRadians(0), width / 2, height / 2); +// g.drawImage(image, null, 0, 0); + + try { + ImageIO.write(image, "png", new File(path)); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public void bind() { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glViewport(0, 0, width, height); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + + public void unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // TODO: Need glViewport again? + } + + public void resize(int width, int height) { + this.width = width; + this.height = height; + + delete(); + create(); + + } + + public void delete() { + glDeleteTextures(textureId); + glDeleteFramebuffers(fbo); + glDeleteRenderbuffers(rbo); + + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getTextureId() { + return textureId; + } + + public int getFbo() { + return fbo; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/RenderBuffer.java b/src/main/java/xueli/game2/renderer/legacy/RenderBuffer.java new file mode 100644 index 00000000..8a6cc0be --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/RenderBuffer.java @@ -0,0 +1,19 @@ +package xueli.game2.renderer.legacy; + +import xueli.utils.buffer.LotsOfByteBuffer; + +public interface RenderBuffer { + + public void applyBuffer(int id, LotsOfByteBuffer buf); + + public void setVertexCount(int count); + + public BackRenderBuffer createBackBuffer(); + + public void render(); + + public void clear(); + + public void release(); + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/RenderSystem.java b/src/main/java/xueli/game2/renderer/legacy/RenderSystem.java new file mode 100644 index 00000000..5bf40175 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/RenderSystem.java @@ -0,0 +1,28 @@ +package xueli.game2.renderer.legacy; + +import java.util.HashMap; + +import xueli.registry.Identifier; + +public class RenderSystem> { + + private final HashMap renderTypes = new HashMap<>(); + + public RenderSystem() { + } + + public void registerRenderType(Identifier namespace, T renderType) { + this.renderTypes.put(namespace, renderType); + } + + public void tick() { + renderTypes.values().forEach(T::render); + + } + + public void release() { + renderTypes.values().forEach(T::release); + + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/RenderType.java b/src/main/java/xueli/game2/renderer/legacy/RenderType.java new file mode 100644 index 00000000..5c1cadfc --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/RenderType.java @@ -0,0 +1,56 @@ +package xueli.game2.renderer.legacy; + +import java.util.HashMap; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.lwjgl.utils.vector.Matrix4f; + +public abstract class RenderType { + + protected final HashMap buffers = new HashMap<>(); + + private final Function bufferSupplier; + + public RenderType(Function bufferSupplier) { + this.bufferSupplier = bufferSupplier; + + } + + public RenderBuffer getRenderBuffer(T key) { + return buffers.computeIfAbsent(key, bufferSupplier); + } + + public RenderBuffer releaseRenderBuffer(T key) { + RenderBuffer buffer = buffers.remove(key); + buffer.release(); + return buffer; + } + + public final void render() { + this.render(t -> true); + } + + public void render(Predicate selector) { + buffers.forEach((t, b) -> { + if (selector.test(t)) { + b.render(); + } + }); + + } + + public abstract void applyMatrix(String name, Matrix4f matrix); + + public void clear() { + buffers.values().forEach(RenderBuffer::clear); + } + + public void release() { + buffers.values().forEach(RenderBuffer::release); + this.doRelease(); + } + + protected abstract void doRelease(); + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/VertexAcceptable.java b/src/main/java/xueli/game2/renderer/legacy/VertexAcceptable.java new file mode 100644 index 00000000..1f0c54ba --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/VertexAcceptable.java @@ -0,0 +1,9 @@ +package xueli.game2.renderer.legacy; + +import xueli.game2.renderer.legacy.buffer.BufferStorable; + +public interface VertexAcceptable { + + public void acceptVertex(BufferStorable storable); + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/VertexAttributeRenderBuffer.java b/src/main/java/xueli/game2/renderer/legacy/VertexAttributeRenderBuffer.java new file mode 100644 index 00000000..8aaff7ae --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/VertexAttributeRenderBuffer.java @@ -0,0 +1,104 @@ +package xueli.game2.renderer.legacy; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import xueli.game2.renderer.legacy.buffer.AttributeBuffer; +import xueli.game2.renderer.legacy.buffer.BufferStorable; +import xueli.game2.renderer.legacy.buffer.VertexAttribute; +import xueli.game2.renderer.legacy.shape.ShapeType; +import xueli.utils.buffer.BufferSyncor; +import xueli.utils.buffer.LotsOfByteBuffer; + +public class VertexAttributeRenderBuffer implements RenderBuffer { + + protected final VertexAttribute attr; + private int vertCount = 0; + + public VertexAttributeRenderBuffer(ShapeType shapeType) { + this.attr = new VertexAttribute(shapeType); + + } + + @Override + public void applyBuffer(int id, LotsOfByteBuffer buf) { + AttributeBuffer atb = this.attr.getAttributeBuffer(id); + atb.updateBuffer(buf); + + } + + @Override + public void setVertexCount(int count) { + this.vertCount = count; + } + + @Override + public void clear() { + this.vertCount = 0; + } + + protected static class VertexBuffer { + public int submitCount = 0; + public BufferSyncor.BackBuffer buf; + + public VertexBuffer(BufferSyncor.BackBuffer buf) { + this.buf = buf; + } + + }; + + @Override + public BackRenderBuffer createBackBuffer() { + return new BackRenderBuffer() { + private final HashMap applyCount = new HashMap<>() { + private static final long serialVersionUID = 5062673955963603115L; + + { + attr.forEachAttribute(i -> { + AttributeBuffer buf = attr.getAttributeBuffer(i); + VertexBuffer vertexBuffer = new VertexBuffer(buf.createBackBuffer()); + this.put(i, vertexBuffer); + }); + } + }; + + @Override + public void applyToBuffer(int id, BufferStorable storable) { + VertexBuffer obj = applyCount.get(id); + storable.store(obj.buf.getBuffer()); + obj.submitCount++; + + } + + @Override + public void flip() { + AtomicInteger vertCount = new AtomicInteger(Integer.MAX_VALUE); + applyCount.forEach((i, b) -> { + vertCount.set(Math.min(vertCount.get(), b.submitCount)); + b.buf.markSync(); + }); + + VertexAttributeRenderBuffer.this.setVertexCount(vertCount.get()); + + } + + }; + } + + @Override + public void render() { + this.attr.render(vertCount); + + } + + @Override + public void release() { + this.attr.release(); + + } + + protected int getVertCount() { + return vertCount; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/VertexType.java b/src/main/java/xueli/game2/renderer/legacy/VertexType.java new file mode 100644 index 00000000..74720535 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/VertexType.java @@ -0,0 +1,25 @@ +package xueli.game2.renderer.legacy; + +import org.lwjgl.opengl.GL11; + +public enum VertexType { + + INT(4, GL11.GL_INT), FLOAT(4, GL11.GL_FLOAT), DOUBLE(8, GL11.GL_DOUBLE), SHORT(2, GL11.GL_SHORT); + + private int size; + private int glValue; + + VertexType(int size, int glValue) { + this.size = size; + this.glValue = glValue; + } + + public int getSize() { + return size; + } + + public int getGlValue() { + return glValue; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/buffer/AttributeBuffer.java b/src/main/java/xueli/game2/renderer/legacy/buffer/AttributeBuffer.java new file mode 100644 index 00000000..70f548bf --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/buffer/AttributeBuffer.java @@ -0,0 +1,87 @@ +package xueli.game2.renderer.legacy.buffer; + +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; + +import xueli.game2.renderer.legacy.VertexType; +import xueli.utils.buffer.BufferSyncor; +import xueli.utils.buffer.LotsOfByteBuffer; + +public class AttributeBuffer implements Bindable { + + private final int id; + private final int attributeSize; + private final int bufferType = GL15.GL_DYNAMIC_DRAW; + private final VertexType type; + + private int vbo; + + private BufferSyncor bufferManager = new BufferSyncor(); + + public AttributeBuffer(int id, int attributeSize, VertexType type) { + this.id = id; + this.attributeSize = attributeSize; + this.type = type; + + this.vbo = GL30.glGenBuffers(); + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo); + GL30.glBufferData(GL30.GL_ARRAY_BUFFER, 0, bufferType); + + GL30.glVertexAttribPointer(id, attributeSize, type.getGlValue(), false, 0, 0); + GL30.glEnableVertexAttribArray(id); + + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0); + + } + + public void tick() { + synchronized (this) { + bufferManager.doingSyncIfNecessary(buf -> { + this.bind(() -> GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf.getBuffer(), bufferType)); + }); + } + } + + public void updateBuffer(LotsOfByteBuffer buf) { + bufferManager.updateBuffer(buf); + } + + public BufferSyncor.BackBuffer createBackBuffer() { + return bufferManager.createBackBuffer(); + } + +// public ByteBuffer getLatestBuffer() { +// LotsOfByteBuffer latestBuffer = bufferManager.getLatestBuffer(); +// if (latestBuffer == null) +// return null; +// return latestBuffer.getBuffer(); +// } + + @Override + public void bind() { + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo); + } + + @Override + public void unbind() { + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0); + } + + public void release() { + GL30.glDeleteBuffers(this.vbo); + + } + + public int getId() { + return id; + } + + public int getAttributeSize() { + return attributeSize; + } + + public VertexType getType() { + return type; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/buffer/Bindable.java b/src/main/java/xueli/game2/renderer/legacy/buffer/Bindable.java new file mode 100644 index 00000000..c881414a --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/buffer/Bindable.java @@ -0,0 +1,15 @@ +package xueli.game2.renderer.legacy.buffer; + +public interface Bindable { + + public void bind(); + + public void unbind(); + + default public void bind(Runnable r) { + bind(); + r.run(); + unbind(); + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/buffer/BufferStorable.java b/src/main/java/xueli/game2/renderer/legacy/buffer/BufferStorable.java new file mode 100644 index 00000000..f6d2b0d6 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/buffer/BufferStorable.java @@ -0,0 +1,9 @@ +package xueli.game2.renderer.legacy.buffer; + +import xueli.utils.buffer.LotsOfByteBuffer; + +public interface BufferStorable { + + public void store(LotsOfByteBuffer buf); + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/buffer/ElementBuffer.java b/src/main/java/xueli/game2/renderer/legacy/buffer/ElementBuffer.java new file mode 100644 index 00000000..a4b3b167 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/buffer/ElementBuffer.java @@ -0,0 +1,58 @@ +package xueli.game2.renderer.legacy.buffer; + +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL30; + +import xueli.utils.buffer.BufferSyncor; +import xueli.utils.buffer.LotsOfByteBuffer; + +public class ElementBuffer implements Bindable { + + private int ebo; + private final int bufferType = GL15.GL_DYNAMIC_DRAW; + + private BufferSyncor bufferManager = new BufferSyncor(); + + public ElementBuffer() { + this.ebo = GL30.glGenBuffers(); + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.ebo); + GL30.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, 0, bufferType); + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, 0); + + } + + public void tick() { + synchronized (this) { + bufferManager.doingSyncIfNecessary(buf -> { + this.bind(() -> GL30.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, buf.getBuffer(), bufferType)); + }); + } + } + + public void updateBuffer(LotsOfByteBuffer buf) { + bufferManager.updateBuffer(buf); + } + + public BufferSyncor.BackBuffer createBackBuffer() { + return bufferManager.createBackBuffer(); + } + + @Override + public void bind() { + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.ebo); + } + + @Override + public void unbind() { + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, 0); + } + + public void release() { + GL30.glDeleteBuffers(this.ebo); + } + + public int getId() { + return ebo; + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/buffer/VertexAttribute.java b/src/main/java/xueli/game2/renderer/legacy/buffer/VertexAttribute.java new file mode 100644 index 00000000..7d768220 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/buffer/VertexAttribute.java @@ -0,0 +1,76 @@ +package xueli.game2.renderer.legacy.buffer; + +import java.util.HashMap; +import java.util.function.Consumer; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +import xueli.game2.renderer.legacy.VertexType; +import xueli.game2.renderer.legacy.shape.ShapeType; + +public class VertexAttribute implements Bindable { + + private final int vao; + private final HashMap buffers = new HashMap<>(); + + private final ShapeType shapeType; + + public VertexAttribute(ShapeType shapeType) { + this.shapeType = shapeType; + this.vao = GL30.glGenVertexArrays(); + + } + + // TODO: change to VertexFormatSpec and pass it from constructor + public void addAttributeBuffer(int id, int attrSize, VertexType type) { + AttributeBuffer buf = new AttributeBuffer(id, attrSize, type); + this.buffers.put(id, buf); + + } + + public AttributeBuffer getAttributeBuffer(int id) { + return this.buffers.get(id); + } + + public void forEachAttribute(Consumer c) { + buffers.keySet().forEach(c); + } + + @Override + public void bind() { + GL30.glBindVertexArray(vao); + } + + public void render(int vertCount) { + this.bind(); + buffers.values().forEach(AttributeBuffer::tick); + GL30.glDrawArrays(shapeType.getGLValue(), 0, vertCount); + this.unbind(); + + } + + public void renderElement(ElementBuffer ebo, int vertCount) { + this.bind(); + ebo.bind(); + buffers.values().forEach(AttributeBuffer::tick); + GL30.glDrawElements(shapeType.getGLValue(), vertCount, GL11.GL_UNSIGNED_INT, 0); + ebo.unbind(); + this.unbind(); + } + + @Override + public void unbind() { + GL30.glBindVertexArray(0); + } + + public void release() { + this.bind(); + buffers.values().forEach(AttributeBuffer::release); + this.unbind(); + + GL30.glDeleteVertexArrays(this.vao); + + } + +} diff --git a/src/main/java/xueli/game2/renderer/legacy/shape/ShapeType.java b/src/main/java/xueli/game2/renderer/legacy/shape/ShapeType.java new file mode 100644 index 00000000..f826f2e9 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/legacy/shape/ShapeType.java @@ -0,0 +1,20 @@ +package xueli.game2.renderer.legacy.shape; + +import org.lwjgl.opengl.GL11; + +public enum ShapeType { + + POINT(GL11.GL_POINTS), LINE(GL11.GL_LINES), LINE_LOOP(GL11.GL_LINE_LOOP), LINE_STRIP(GL11.GL_LINE_STRIP), + TRIANGLES(GL11.GL_TRIANGLES), TRIANGLE_FAN(GL11.GL_TRIANGLE_FAN), TRIANGLE_STRIP(GL11.GL_TRIANGLE_STRIP); + + private int glVal; + + ShapeType(int glVal) { + this.glVal = glVal; + } + + public int getGLValue() { + return glVal; + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/FrameBufferStack.java b/src/main/java/xueli/game2/renderer/ui/FrameBufferStack.java new file mode 100644 index 00000000..10a19e58 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/FrameBufferStack.java @@ -0,0 +1,49 @@ +package xueli.game2.renderer.ui; + +import static org.lwjgl.nanovg.NanoVGGL3.nvgluBindFramebuffer; + +import java.util.Stack; + +import org.lwjgl.nanovg.NVGLUFramebuffer; + +//Push this frame buffer into the stack instead of just bind it +// so that when calling "pop" the last frame buffer can be bound +// This is really a good feature +public class FrameBufferStack { + + private final NanoGui gui; + private final Stack framebuffers = new Stack<>(); + + public FrameBufferStack(NanoGui gui) { + this.gui = gui; + } + + public void push(NanoFrameBuffer framebuffer) { + framebuffers.push(framebuffer); + this.syncToTopElement(); + } + + public void pop() { + framebuffers.pop(); + this.syncToTopElement(); + } + + public boolean isEmpty() { + return framebuffers.empty(); + } + + private void syncToTopElement() { + if (framebuffers.empty()) { + nvgluBindFramebuffer(gui.nvg, null); + return; + } + NVGLUFramebuffer top = framebuffers.peek().rawFramebuffer; + if (top == null) + nvgluBindFramebuffer(gui.nvg, null); + else + nvgluBindFramebuffer(gui.nvg, top); + + + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/MatrixStack.java b/src/main/java/xueli/game2/renderer/ui/MatrixStack.java new file mode 100644 index 00000000..c3189de6 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/MatrixStack.java @@ -0,0 +1,60 @@ +package xueli.game2.renderer.ui; + +import org.lwjgl.utils.vector.Matrix3f; +import org.lwjgl.utils.vector.Vector2f; + +import java.util.Stack; + +public class MatrixStack { + + // Just store the result so that repeat calculation can be saved + private final Stack matResults = new Stack<>(); + + public MatrixStack() { + } + + public void pushMatrix(Matrix3f mat) { + if(matResults.empty()) { + matResults.push(mat); + return; + } + + var lastResult = matResults.peek(); + var result = Matrix3f.mul(mat, lastResult, null); + matResults.push(result); + + } + + public void popMatrix() { + if(matResults.empty()) return; + matResults.pop(); + + } + + public void pushMark() { + + } + + public void popToMark() { + + } + + public Vector2f transform(Vector2f src) { + if(matResults.empty()) return src; + var transMat = matResults.peek(); + return new Vector2f( + transMat.m00 * src.x + transMat.m10 * src.y + transMat.m20, + transMat.m01 * src.x + transMat.m11 * src.y + transMat.m21 + ); + } + + public Vector2f delta(Vector2f src) { + if(matResults.empty()) return src; + var transMat = matResults.peek(); + return new Vector2f( + transMat.m00 * src.x + transMat.m01 * src.y, + transMat.m10 * src.x + transMat.m11 * src.y + ); + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/NanoFrameBuffer.java b/src/main/java/xueli/game2/renderer/ui/NanoFrameBuffer.java new file mode 100644 index 00000000..f47929ab --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/NanoFrameBuffer.java @@ -0,0 +1,75 @@ +package xueli.game2.renderer.ui; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVGGL3.nvgluCreateFramebuffer; +import static org.lwjgl.nanovg.NanoVGGL3.nvgluDeleteFramebuffer; + +import org.lwjgl.nanovg.NVGLUFramebuffer; + +import xueli.gui.driver.FrameBuffer; +import xueli.gui.driver.GraphicDriver; + +public class NanoFrameBuffer implements FrameBuffer { + + private final NanoGui gui; + private int width, height; + NVGLUFramebuffer rawFramebuffer; + + public NanoFrameBuffer(int width, int height, NanoGui gui) { + this.gui = gui; + this.width = width; + this.height = height; + + this.rawFramebuffer = nvgluCreateFramebuffer(gui.nvg, width, height, NVG_IMAGE_NEAREST); + if(this.rawFramebuffer == null) + throw new RuntimeException("Can't create frame buffer!"); + + } + + @Override + public void resize(int width, int height) { +// long time1 = System.currentTimeMillis(); + + // This can take too much time for a smooth size change animation! + nvgluDeleteFramebuffer(gui.nvg, rawFramebuffer); + + this.rawFramebuffer = nvgluCreateFramebuffer(gui.nvg, width, height, NVG_IMAGE_NEAREST); + if(this.rawFramebuffer == null) + throw new RuntimeException("Can't create frame buffer!"); + + + this.width = width; + this.height = height; + +// long time2 = System.currentTimeMillis(); +// System.out.println(time2 - time1); + + } + + @Override + public GraphicDriver getGraphicDriver() { + return this.gui; + } + + @Override + public int getImageId() { + return this.rawFramebuffer.image(); + } + + @Override + public void release() { + nvgluDeleteFramebuffer(gui.nvg, rawFramebuffer); + this.rawFramebuffer = null; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/NanoGui.java b/src/main/java/xueli/game2/renderer/ui/NanoGui.java new file mode 100644 index 00000000..add68ad6 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/NanoGui.java @@ -0,0 +1,315 @@ +package xueli.game2.renderer.ui; + +import org.lwjgl.nanovg.NVGColor; +import org.lwjgl.nanovg.NVGPaint; +import org.lwjgl.opengl.GL11; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.utils.vector.Matrix3f; +import org.lwjgl.utils.vector.Vector2f; +import xueli.game2.resource.Resource; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.BufferUtils; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.gui.driver.FrameBuffer; +import xueli.gui.driver.GraphicDriver; +import xueli.utils.logger.InvokeDaemon; + +import java.awt.*; +import java.io.IOException; +import java.nio.ByteBuffer; + +import io.github.javaherobrine.GameUtils; + +import static org.lwjgl.nanovg.NanoVG.*; +import static org.lwjgl.nanovg.NanoVGGL3.*; + +public class NanoGui implements ResourceHolder, GraphicDriver { + + private final InvokeDaemon daemon = new InvokeDaemon(getClass()); + + long nvg = 0; + final FrameBufferStack frameBufferStack = new FrameBufferStack(this); + final MatrixStack matrixStack = new MatrixStack(); + final ScissorStack scissorStack = new ScissorStack(matrixStack); + + private final NVGColor colorBuf = NVGColor.create(); + private final NVGPaint paintBuf = NVGPaint.create(); + + public NanoGui() { + } + + @Override + public void reload() { + daemon.announce(); + + if (this.nvg != 0) { + nvgDelete(nvg); + } + + this.nvg = nvgCreate(NVG_STENCIL_STROKES | NVG_ANTIALIAS | NVG_DEBUG); + if (this.nvg == 0) { + throw new RuntimeException("Couldn't init NanoVG!"); + } + + } + + @Override + public int registerImage(Resource res) throws IOException { + daemon.announce(); + + byte[] bytes = res.readAll(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length); + buffer.put(bytes); + buffer.flip(); + + int id = nvgCreateImageMem(nvg, NVG_IMAGE_NEAREST, buffer); + if (id <= 0) { + throw new IOException("Can't register image: " + res.toString()); + } + return id; + } + + public int registerImage(Texture tex) { + daemon.announce(); + return nvglCreateImageFromHandle(nvg, tex.id(), tex.width(), tex.height(), NVG_IMAGE_NEAREST); + } + + @Override + public int registerFont(String name, Resource res) throws IOException { + daemon.announce(); + + byte[] bytes = res.readAll(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(bytes.length); + buffer.put(bytes); + buffer.flip(); + + //int id = nvgCreateFontMem(nvg, name, buffer, 0); + byte[] name0=new StringBuilder(name).append('\0').toString().getBytes(); + long addr0=GameUtils.address(name0); + int id=nnvgCreateFontMem(nvg, addr0, MemoryUtil.memAddress0(buffer),bytes.length, 0); + GameUtils.allowGC(addr0,name0); + if (id < 0) { + throw new IOException("Can't register font: " + name); + } + + return id; + } + + @Override + public void clearColor(float r, float g, float b, float a) { + daemon.announce(); + + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT); + GL11.glClearColor(r, g, b, a); + + } + + @Override + public void begin(int width, int height) { + daemon.announce(); + nvgBeginFrame(nvg, width, height, (float) width / height); + } + + @Override + public void setTextLetterSpacing(float s) { + daemon.announce(); + nvgTextLetterSpacing(nvg, s); + } + + private int getFontAlignNvgValue(FontAlign align) { + daemon.announce(); + return switch (align) { + case LEFT -> NVG_ALIGN_LEFT; + case CENTER -> NVG_ALIGN_CENTER; + case RIGHT -> NVG_ALIGN_RIGHT; + case TOP -> NVG_ALIGN_TOP; + case MIDDLE -> NVG_ALIGN_MIDDLE; + case BOTTOM -> NVG_ALIGN_BOTTOM; + case BASE_LINE -> NVG_ALIGN_BASELINE; + }; + } + + @Override + public float drawFont(float x, float y, float size, String str, int fontId, FontAlign... aligns) { + daemon.announce(); + Vector2f transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); +// Vector2f transformedSize = this.matrixStack.delta(new Vector2f()); + + int align = 0; + for (int i = 0; i < aligns.length; i++) { + align |= getFontAlignNvgValue(aligns[i]); + } + + nvgFontSize(nvg, size); + nvgFontFaceId(nvg, fontId); + nvgTextAlign(nvg, align); + return nvgText(nvg, transformedPosition.x, transformedPosition.y, str); + } + + @Override + public void drawFilledRect(float x, float y, float width, float height, FillType type) { + daemon.announce(); + Vector2f transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); + Vector2f transformedSize = this.matrixStack.delta(new Vector2f(width, height)); + + nvgBeginPath(nvg); + nvgRect(nvg, transformedPosition.x, transformedPosition.y, transformedSize.x, transformedSize.y); + this.fill(type); + + } + + @Override + public void drawFilledCircle(float x, float y, float radius, FillType type) { + daemon.announce(); + Vector2f transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); +// Vector2f transformedSize = this.matrixStack.delta(new Vector2f()); + + nvgBeginPath(nvg); + nvgCircle(nvg, transformedPosition.x, transformedPosition.y, radius); + this.fill(type); + + } + + @Override + public void setTexturedPaint(float x, float y, float width, float height, float angle, float alpha, int imageId) { + daemon.announce(); + Vector2f transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); + Vector2f transformedSize = this.matrixStack.delta(new Vector2f(width, height)); + + nvgImagePattern(nvg, transformedPosition.x, transformedPosition.y, transformedSize.x, transformedSize.y, angle, imageId, alpha, paintBuf); + + } + + @Override + public void drawImage(float x, float y, float width, float height, float alpha, int imageId) { + daemon.announce(); + Vector2f transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); + Vector2f transformedSize = this.matrixStack.delta(new Vector2f(width, height)); + + this.setTexturedPaint(transformedPosition.x, transformedPosition.y, transformedSize.x, transformedSize.y, 0, alpha, imageId); + this.drawFilledRect(transformedPosition.x, transformedPosition.y, transformedSize.x, transformedSize.y, FillType.PAINT); + + } + + @Override + public void drawImageCircle(float x, float y, float radius, int imageId, float angle, float alpha) { + daemon.announce(); + + // No need to calculate from matrix! + this.setTexturedPaint(x - radius, y - radius, radius * 2, radius * 2, angle, alpha, imageId); + this.drawFilledCircle(x, y, radius, FillType.PAINT); + + } + + @Override + public void setColor(Color color) { + daemon.announce(); + + int c = color.getRGB(); + nvgRGBA((byte) ((c >> 16) & 0xFF), (byte) ((c >> 8) & 0xFF), (byte) (c & 0xFF), (byte) ((c >> 24) & 0xFF), + this.colorBuf); + nvgFillColor(nvg, this.colorBuf); + + } + + @Override + public void fill(FillType type) { + daemon.announce(); + + switch (type) { + case COLOR -> nvgFillColor(nvg, colorBuf); + case PAINT -> nvgFillPaint(nvg, paintBuf); + } + nvgFill(nvg); + } + + // The scissor rectangle is transformed by the current transform. + @Override + public void scissorPush(float x, float y, float width, float height) { + daemon.announce(); + + var scissorResult = scissorStack.push(x, y, width, height); + nvgResetScissor(nvg); + nvgScissor(nvg, scissorResult.x(), scissorResult.y(), scissorResult.width(), scissorResult.height()); + + } + + @Override + public void scissorPop() { + daemon.announce(); + nvgResetScissor(nvg); + + var scissorResult = scissorStack.pop(); + if(scissorResult == null) return; + nvgScissor(nvg, scissorResult.x(), scissorResult.y(), scissorResult.width(), scissorResult.height()); + + } + + @Override + public void scissorReset() { + daemon.announce(); + nvgResetScissor(nvg); + scissorStack.reset(); + + } + + @Override + public float measureTextWidth(float size, String text, int fontId) { + daemon.announce(); + + nvgFontSize(nvg, size); + nvgTextAlign(nvg, NVG_ALIGN_LEFT); + nvgFontFaceId(nvg, fontId); + return nvgText(nvg, 0, -10000000, text); + } + + @Override + public FrameBuffer createFrameBuffer(int width, int height) { + daemon.announce(); + return new NanoFrameBuffer(width, height, this); + } + + @Override + public void pushFrameBuffer(FrameBuffer buffer) { + daemon.announce(); + frameBufferStack.push(((NanoFrameBuffer) buffer)); + // TODO: change the state of this state manager! + } + + @Override + public void popFrameBuffer() { + daemon.announce(); + frameBufferStack.pop(); + + } + + @Override + public void pushMatrix(Matrix3f matrix) { + daemon.announce(); + matrixStack.pushMatrix(matrix); + } + + @Override + public void popMatrix() { + daemon.announce(); + matrixStack.popMatrix(); + } + + @Override + public void finish() { + daemon.announce(); + nvgEndFrame(nvg); + } + + public long getContext() { + return nvg; + } + + public void release() { + nvgDelete(nvg); + + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/Overlay.java b/src/main/java/xueli/game2/renderer/ui/Overlay.java new file mode 100644 index 00000000..808fc292 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/Overlay.java @@ -0,0 +1,13 @@ +package xueli.game2.renderer.ui; + +public interface Overlay { + + public void init(NanoGui gui); + + public void reload(); + + public void render(); + + public void release(); + +} diff --git a/src/main/java/xueli/game2/renderer/ui/OverlayManager.java b/src/main/java/xueli/game2/renderer/ui/OverlayManager.java new file mode 100644 index 00000000..8481aadb --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/OverlayManager.java @@ -0,0 +1,69 @@ +package xueli.game2.renderer.ui; + +import xueli.game2.display.GameDisplay; +import xueli.game2.lifecycle.LifeCycle; +import xueli.game2.resource.ResourceHolder; + +public class OverlayManager implements LifeCycle, ResourceHolder { + + private final GameDisplay display; +// private final Gui gui; + + private Overlay overlay = null; + + public OverlayManager(GameDisplay display) { + this.display = display; +// this.gui = display.getGuiManager(); + + } + + @Override + public void init() { +// if(overlay != null) { +// overlay.init(gui); +// } + } + + @Override + public void tick() { +// if(overlay != null) { +// gui.begin(display.getWidth(), display.getHeight()); +// overlay.render(); +// gui.finish(); +// } + + } + + @Override + public void reload() { +// if(overlay != null) { +// overlay.reload(); +// } + + } + + @Override + public void release() { +// if(this.overlay != null) { +// this.overlay.release(); +// } + + } + + public void setOverlay(Overlay overlay) { +// if(this.overlay != null) { +// this.overlay.release(); +// } +// this.overlay = overlay; +// if(this.overlay != null) { +// this.overlay.init(gui); +// this.overlay.reload(); +// } + + } + + public boolean hasOverlay() { + return this.overlay != null; + } + +} diff --git a/src/main/java/xueli/game2/renderer/ui/ScissorStack.java b/src/main/java/xueli/game2/renderer/ui/ScissorStack.java new file mode 100644 index 00000000..09e4e638 --- /dev/null +++ b/src/main/java/xueli/game2/renderer/ui/ScissorStack.java @@ -0,0 +1,49 @@ +package xueli.game2.renderer.ui; + +import org.lwjgl.utils.vector.Vector2f; + +import java.util.Stack; + +public class ScissorStack { + + public static record ScissorResult(float x, float y, float width, float height) {} + + private final MatrixStack matrixStack; + + // Just store the result so that repeat calculation can be saved + private final Stack scissorStack = new Stack<>(); + + public ScissorStack(MatrixStack matrixStack) { + this.matrixStack = matrixStack; + } + + public ScissorResult push(float x, float y, float width, float height) { + if(scissorStack.empty()) { + var thisScissor = new ScissorResult(x, y, width, height); + this.scissorStack.push(thisScissor); + return thisScissor; + } + + var lastScissor = scissorStack.peek(); + var transformedPosition = this.matrixStack.transform(new Vector2f(x, y)); + var transformedSize = this.matrixStack.delta(new Vector2f(width, height)); + + float newX1 = Math.max(lastScissor.x, transformedPosition.x); + float newY1 = Math.max(lastScissor.y, transformedPosition.y); + float newX2 = Math.min(lastScissor.x + lastScissor.width, transformedPosition.x + transformedSize.x); + float newY2 = Math.min(lastScissor.y + lastScissor.height, transformedPosition.y + transformedSize.y); + var newScissor = new ScissorResult(newX1, newY1, newX2 - newX1, newY2 - newY1); + this.scissorStack.push(newScissor); + return newScissor; + } + + public ScissorResult pop() { + if(scissorStack.empty()) return null; + return scissorStack.pop(); + } + + public void reset() { + scissorStack.clear(); + } + +} diff --git a/src/main/java/xueli/game2/resource/ReloadableResourceTicket.java b/src/main/java/xueli/game2/resource/ReloadableResourceTicket.java new file mode 100644 index 00000000..5a79c6eb --- /dev/null +++ b/src/main/java/xueli/game2/resource/ReloadableResourceTicket.java @@ -0,0 +1,9 @@ +package xueli.game2.resource; + +import java.util.function.Supplier; + +/** + * We get the target resource just like a ticket + */ +public interface ReloadableResourceTicket extends Supplier { +} diff --git a/src/main/java/xueli/game2/resource/Resource.java b/src/main/java/xueli/game2/resource/Resource.java new file mode 100644 index 00000000..f2edb429 --- /dev/null +++ b/src/main/java/xueli/game2/resource/Resource.java @@ -0,0 +1,23 @@ +package xueli.game2.resource; + +import java.io.IOException; +import java.io.InputStream; + +public interface Resource { + + public String getName(); + + public InputStream openInputStream() throws IOException; + + default byte[] readAll() throws IOException { + InputStream in = openInputStream(); + byte[] all = in.readAllBytes(); + in.close(); + return all; + } + + default String readAllString() throws IOException { + return new String(readAll()); + } + +} diff --git a/src/main/java/xueli/game2/resource/ResourceHolder.java b/src/main/java/xueli/game2/resource/ResourceHolder.java new file mode 100644 index 00000000..04236b2f --- /dev/null +++ b/src/main/java/xueli/game2/resource/ResourceHolder.java @@ -0,0 +1,6 @@ +package xueli.game2.resource; + +import xueli.game2.resource.manager.IReloadable; + +public interface ResourceHolder extends IReloadable { +} diff --git a/src/main/java/xueli/game2/resource/ResourceURL.java b/src/main/java/xueli/game2/resource/ResourceURL.java new file mode 100644 index 00000000..a9f120a6 --- /dev/null +++ b/src/main/java/xueli/game2/resource/ResourceURL.java @@ -0,0 +1,44 @@ +package xueli.game2.resource; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class ResourceURL implements Resource { + + private URL url; + private String name; + + public ResourceURL(URL url) { + this.url = url; + compileName(); + } + + private void compileName() { + String path = url.getPath(); + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + int index = path.lastIndexOf('/'); + this.name = path.substring(Math.max(0, index + 1)); + + } + + @Override + public InputStream openInputStream() throws IOException { + InputStream in = url.openStream(); + return new BufferedInputStream(in); + } + + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return "ResourceURL{" + url + '}'; + } + +} diff --git a/src/main/java/xueli/game2/resource/ResourceZipEntry.java b/src/main/java/xueli/game2/resource/ResourceZipEntry.java new file mode 100644 index 00000000..dc66aba6 --- /dev/null +++ b/src/main/java/xueli/game2/resource/ResourceZipEntry.java @@ -0,0 +1,29 @@ +package xueli.game2.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ResourceZipEntry implements Resource { + + private ZipEntry entry; + private ZipFile file; + + public ResourceZipEntry(ZipEntry entry, ZipFile file) { + this.entry = entry; + this.file = file; + + } + + @Override + public String getName() { + return file.getName(); + } + + @Override + public InputStream openInputStream() throws IOException { + return file.getInputStream(entry); + } + +} diff --git a/src/main/java/xueli/game2/resource/manager/BackwardResourceManager.java b/src/main/java/xueli/game2/resource/manager/BackwardResourceManager.java new file mode 100644 index 00000000..ee2704a7 --- /dev/null +++ b/src/main/java/xueli/game2/resource/manager/BackwardResourceManager.java @@ -0,0 +1,55 @@ +package xueli.game2.resource.manager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.provider.ResourceProvider; +import xueli.registry.Identifier; + +public class BackwardResourceManager extends ChainedResourceManager { + + private List providers; + + public BackwardResourceManager(List providers) { + this.providers = Objects.requireNonNull(providers); + reload(); + } + + public List getProviders() { + return List.copyOf(this.providers); + } + + public void setProviders(List providers) { + this.providers = Objects.requireNonNull(providers); + reload(); + } + + @Override + public Resource getResource(Identifier location) throws IOException { + for (ResourceProvider provider : providers) { + try { + return provider.getResource(location); + } catch (IOException e) { + } + } + return null; + } + + @Override + public List findResources(Identifier location, Predicate fileNamePredicate) + throws IOException { + ArrayList resources = new ArrayList<>(); + for (ResourceProvider provider : providers) { + try { + resources.addAll(provider.findResources(location, fileNamePredicate)); + } catch (IOException e) { + } + } + return resources; + } + +} diff --git a/src/main/java/xueli/game2/resource/manager/ChainedResourceManager.java b/src/main/java/xueli/game2/resource/manager/ChainedResourceManager.java new file mode 100644 index 00000000..ddd1cf63 --- /dev/null +++ b/src/main/java/xueli/game2/resource/manager/ChainedResourceManager.java @@ -0,0 +1,51 @@ +package xueli.game2.resource.manager; + +import java.io.IOException; +import java.util.ArrayList; + +import xueli.game2.resource.ResourceHolder; +import xueli.utils.logger.Logger; + +public abstract class ChainedResourceManager implements ResourceManager { + + private static final Logger LOGGER = new Logger(); + + private final ArrayList resourceHolders = new ArrayList<>(); + private final ArrayList subManagers = new ArrayList<>(); + + public ChainedResourceManager() { + } + + public void addSubManager(ResourceManager manager) { + this.subManagers.add(manager); + LOGGER.info("Add sub-manager " + manager.getClass().getSimpleName() + " to " + this.getClass().getSimpleName()); + } + + public void addResourceHolder(ResourceHolder holder) { + holder.reload(); + this.resourceHolders.add(holder); + LOGGER.info("Add holder " + holder.getClass().getSimpleName() + " to " + this.getClass().getSimpleName()); + } + + @Override + public void reload() { + for (ResourceManager manager : subManagers) { + manager.reload(); + LOGGER.info("Reload: " + manager.getClass().getSimpleName()); + } + for (ResourceHolder holder : resourceHolders) { + holder.reload(); + LOGGER.info("Holder reload: " + holder.getClass().getSimpleName()); + } + + } + + @Override + public void close() throws IOException { + for (ResourceManager manager : subManagers) { + manager.close(); + LOGGER.info("Close: " + manager.getClass().getSimpleName()); + } + } + +} diff --git a/src/main/java/xueli/game2/resource/manager/IReloadable.java b/src/main/java/xueli/game2/resource/manager/IReloadable.java new file mode 100644 index 00000000..279fac43 --- /dev/null +++ b/src/main/java/xueli/game2/resource/manager/IReloadable.java @@ -0,0 +1,7 @@ +package xueli.game2.resource.manager; + +public interface IReloadable { + + public void reload(); + +} diff --git a/src/main/java/xueli/game2/resource/manager/ResourceManager.java b/src/main/java/xueli/game2/resource/manager/ResourceManager.java new file mode 100644 index 00000000..3c54e729 --- /dev/null +++ b/src/main/java/xueli/game2/resource/manager/ResourceManager.java @@ -0,0 +1,9 @@ +package xueli.game2.resource.manager; + +import java.io.Closeable; + +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.provider.ResourceProvider; + +public interface ResourceManager extends ResourceProvider, ResourceHolder, Closeable { +} diff --git a/src/main/java/xueli/game2/resource/manager/SubResourceManager.java b/src/main/java/xueli/game2/resource/manager/SubResourceManager.java new file mode 100644 index 00000000..f6e1c5a7 --- /dev/null +++ b/src/main/java/xueli/game2/resource/manager/SubResourceManager.java @@ -0,0 +1,34 @@ +package xueli.game2.resource.manager; + +import java.io.IOException; +import java.util.List; +import java.util.function.Predicate; + +import xueli.game2.resource.Resource; +import xueli.registry.Identifier; + +public class SubResourceManager extends ChainedResourceManager { + + private ChainedResourceManager upperResourceManager; + + public SubResourceManager(ChainedResourceManager superiorManager) { + this.upperResourceManager = superiorManager; + superiorManager.addSubManager(this); + } + + public ChainedResourceManager getUpperResourceManager() { + return upperResourceManager; + } + + @Override + public Resource getResource(Identifier location) throws IOException { + return upperResourceManager.getResource(location); + } + + @Override + public List findResources(Identifier location, Predicate fileNamePredicate) + throws IOException { + return upperResourceManager.findResources(location, fileNamePredicate); + } + +} diff --git a/src/main/java/xueli/game2/resource/provider/AbstractResourceProvider.java b/src/main/java/xueli/game2/resource/provider/AbstractResourceProvider.java new file mode 100644 index 00000000..58528b2a --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/AbstractResourceProvider.java @@ -0,0 +1,32 @@ +package xueli.game2.resource.provider; + +import java.io.IOException; +import java.util.List; +import java.util.function.Predicate; + +import xueli.game2.resource.Resource; +import xueli.registry.Identifier; + +public abstract class AbstractResourceProvider implements ResourceProvider { + + protected abstract String toVirtualPath(Identifier location); + + @Override + public Resource getResource(Identifier location) throws IOException { + String virtualPath = toVirtualPath(location); + return this.getResource(virtualPath); + } + + protected abstract Resource getResource(String virtualPath) throws IOException; + + @Override + public List findResources(Identifier location, Predicate fileNamePredicate) + throws IOException { + String virtualPath = toVirtualPath(location); + return findResources(virtualPath, fileNamePredicate); + } + + protected abstract List findResources(String virtualPath, Predicate fileNamePredicate) + throws IOException; + +} diff --git a/src/main/java/xueli/game2/resource/provider/ClassLoaderResourceProvider.java b/src/main/java/xueli/game2/resource/provider/ClassLoaderResourceProvider.java new file mode 100644 index 00000000..4aca7a5a --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/ClassLoaderResourceProvider.java @@ -0,0 +1,130 @@ +package xueli.game2.resource.provider; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import xueli.registry.Identifier; + +public class ClassLoaderResourceProvider extends URLResourceProvider { + + public static final String ROOT_FOLDER = "/assets/"; + + private static URL currentBinPath = ClassLoaderResourceProvider.class.getProtectionDomain().getCodeSource() + .getLocation(); + private static JarFile currentBinJarFile = null; + static { + if (currentBinPath.getProtocol().equalsIgnoreCase("jar")) { + JarURLConnection connection; + try { + connection = (JarURLConnection) currentBinPath.openConnection(); + currentBinJarFile = connection.getJarFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private boolean onlyCurrentModule = true; + + public ClassLoaderResourceProvider() { + } + + public ClassLoaderResourceProvider(boolean currentModule) { + this.onlyCurrentModule = currentModule; + } + + @Override + protected String toVirtualPath(Identifier location) { + return ROOT_FOLDER + location.namespace() + "/" + location.location(); + } + + @Override + protected URL getResourceURL(String virtualPath) throws IOException { +// System.out.println(virtualPath); + URL url = getClass().getResource(virtualPath); + if (url == null) + throw new IOException("Can't find resource: " + virtualPath); + return url; + } + + @Override + protected List findResources(String virtualPath) throws IOException { + // When it comes to ClassLoader, a "/" is unnecessary; but it's not in + // Class.getResource + while (virtualPath.startsWith("/")) { + virtualPath = virtualPath.substring(1); + } + // Final to get access in lambda + String finalVirtualPath = virtualPath; + + Enumeration resources = ClassLoaderResourceProvider.class.getClassLoader().getResources(virtualPath); + ArrayList urls = new ArrayList<>(); + + resources.asIterator().forEachRemaining(u -> { + String protocol = u.getProtocol(); + // Theoretically(According to general theory æŒ‰æ™Žéį†æ€§č€Œč¨€), the protocol equals to + // only "file" and "jar" + if (protocol.equalsIgnoreCase("file")) { + File file = new File(u.getPath()); + if (file.isFile()) { + urls.add(u); + } else if (file.isDirectory()) { + File[] files = file.listFiles(); + List filesToUrls = Stream.of(files).map(f -> { + try { + return f.toURI().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toList()); + urls.addAll(filesToUrls); + } + } else if (protocol.equalsIgnoreCase("jar")) { + try { + JarURLConnection jarConnection = (JarURLConnection) u.openConnection(); + JarFile file = jarConnection.getJarFile(); + + if (this.onlyCurrentModule && !file.equals(currentBinJarFile)) { + return; + } + + Enumeration entries = file.entries(); + entries.asIterator().forEachRemaining(e -> { + String filePathInJar = e.getName(); + + // Here a lot of results will be filtered, leaving files only in the folder + if (e.isDirectory()) + return; + if (!filePathInJar.startsWith(finalVirtualPath)) + return; + + // A '/' is a must to remove the prefix + String folderName = finalVirtualPath.endsWith("/") ? finalVirtualPath : finalVirtualPath + "/"; + String fileName = filePathInJar.substring(folderName.length()); + if (fileName.contains("/")) + return; + + urls.add(u); + }); + + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + + return urls; + } + +} diff --git a/src/main/java/xueli/game2/resource/provider/FolderResourceProvider.java b/src/main/java/xueli/game2/resource/provider/FolderResourceProvider.java new file mode 100644 index 00000000..47300543 --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/FolderResourceProvider.java @@ -0,0 +1,58 @@ +package xueli.game2.resource.provider; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import xueli.registry.Identifier; + +public class FolderResourceProvider extends URLResourceProvider { + + private File folderFile; + + public FolderResourceProvider(File folder) { + this.folderFile = folder; + } + + @Override + protected String toVirtualPath(Identifier location) { + return folderFile.getPath() + "/" + location.namespace() + "/" + location.location(); + } + + @Override + protected URL getResourceURL(String virtualPath) throws IOException { + File file = new File(virtualPath); + return file.toURI().toURL(); + } + + @Override + protected List findResources(String virtualPath) throws IOException { + File file = new File(virtualPath); + ArrayList urls = new ArrayList<>(); + + if (file.isFile()) { + urls.add(file.toURI().toURL()); + } else if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files == null) + return urls; + List filesToUrls = Stream.of(files).map(f -> { + try { + return f.toURI().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + }).collect(Collectors.toList()); + urls.addAll(filesToUrls); + } + + return urls; + } + +} diff --git a/src/main/java/xueli/game2/resource/provider/ResourceProvider.java b/src/main/java/xueli/game2/resource/provider/ResourceProvider.java new file mode 100644 index 00000000..eca187c8 --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/ResourceProvider.java @@ -0,0 +1,20 @@ +package xueli.game2.resource.provider; + +import java.io.IOException; +import java.util.List; +import java.util.function.Predicate; + +import xueli.game2.resource.Resource; +import xueli.registry.Identifier; + +public interface ResourceProvider { + + public Resource getResource(Identifier location) throws IOException; + + /** + * Find resource files only in the root of the pack + */ + public List findResources(Identifier location, Predicate fileNamePredicate) + throws IOException; + +} diff --git a/src/main/java/xueli/game2/resource/provider/URLResourceProvider.java b/src/main/java/xueli/game2/resource/provider/URLResourceProvider.java new file mode 100644 index 00000000..90a65736 --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/URLResourceProvider.java @@ -0,0 +1,31 @@ +package xueli.game2.resource.provider; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.ResourceURL; + +public abstract class URLResourceProvider extends AbstractResourceProvider { + + @Override + protected Resource getResource(String virtualPath) throws IOException { + URL url = getResourceURL(virtualPath); + return new ResourceURL(url); + } + + protected abstract URL getResourceURL(String virtualPath) throws IOException; + + @Override + protected List findResources(String virtualPath, Predicate fileNamePredicate) throws IOException { + List resources = findResources(virtualPath); + return resources.stream().filter(url -> fileNamePredicate.test(url.getPath())).map(ResourceURL::new) + .collect(Collectors.toList()); + } + + protected abstract List findResources(String virtualPath) throws IOException; + +} diff --git a/src/main/java/xueli/game2/resource/provider/ZipFileResourceProvider.java b/src/main/java/xueli/game2/resource/provider/ZipFileResourceProvider.java new file mode 100644 index 00000000..afdfad42 --- /dev/null +++ b/src/main/java/xueli/game2/resource/provider/ZipFileResourceProvider.java @@ -0,0 +1,72 @@ +package xueli.game2.resource.provider; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.function.Predicate; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.ResourceZipEntry; +import xueli.registry.Identifier; + +public class ZipFileResourceProvider extends AbstractResourceProvider { + + public static final String ROOT_FOLDER = "/assets/"; + + private ZipFile zipFile; + + public ZipFileResourceProvider(File file) { + try { + this.zipFile = new ZipFile(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected String toVirtualPath(Identifier location) { + return ROOT_FOLDER + location.namespace() + "/" + location.location(); + } + + @Override + protected Resource getResource(String virtualPath) throws IOException { + ZipEntry entry = zipFile.getEntry(virtualPath); + return new ResourceZipEntry(entry, zipFile); + } + + @Override + protected List findResources(String virtualPath, Predicate fileNamePredicate) throws IOException { + List resources = new ArrayList<>(); + + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + String pathEntry = e.getName(); + + if (e.isDirectory()) + continue; + if (!pathEntry.startsWith(virtualPath)) + continue; + + if (pathEntry.equals(virtualPath)) { + resources.add(new ResourceZipEntry(e, zipFile)); + break; + } + + String folderName = virtualPath.endsWith("/") ? virtualPath : virtualPath + "/"; + String fileName = pathEntry.substring(folderName.length()); + if (fileName.contains("/")) + continue; + + resources.add(new ResourceZipEntry(e, zipFile)); + + } + + return resources; + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/BufferUtils.java b/src/main/java/xueli/game2/resource/submanager/render/BufferUtils.java new file mode 100644 index 00000000..f571034b --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/BufferUtils.java @@ -0,0 +1,27 @@ +package xueli.game2.resource.submanager.render; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Is this serious? Can the buffer be borrowed or transfered just like Rust do? + */ +public class BufferUtils { + + private static ArrayList bufferStack = new ArrayList<>(); + + private BufferUtils() { + } + + public static ByteBuffer createByteBuffer(int capacity) { + ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(capacity); + bufferStack.add(buffer); + return buffer; + } + + public static void free(Buffer buffer) { + bufferStack.remove(buffer); + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/RenderResource.java b/src/main/java/xueli/game2/resource/submanager/render/RenderResource.java new file mode 100644 index 00000000..479a5ac6 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/RenderResource.java @@ -0,0 +1,55 @@ +package xueli.game2.resource.submanager.render; + +import java.io.IOException; +import java.util.HashMap; + +import xueli.game2.resource.ReloadableResourceTicket; +import xueli.game2.resource.manager.ChainedResourceManager; +import xueli.game2.resource.manager.SubResourceManager; +import xueli.utils.logger.Logger; + +public abstract class RenderResource extends SubResourceManager { + + public static final Logger LOGGER = new Logger(); + + private HashMap registers = new HashMap<>(); + private final HashMap registerMusts = new HashMap<>(); + + public RenderResource(ChainedResourceManager superiorManager) { + super(superiorManager); + } + + public ReloadableResourceTicket register(K k, boolean must) { + registerMusts.put(k, must); + return () -> registers.computeIfAbsent(k, k1 -> { + V v1 = this.doRegister(k1, must); + LOGGER.info("Register \"" + k + "\" in " + getClass().getSimpleName()); + return v1; + }); + } + + @Override + public void close() throws IOException { + super.close(); + registers.forEach(this::close); + } + + @Override + public void reload() { + super.reload(); + + HashMap newRegisters = new HashMap<>(); + registers.forEach((k, v) -> { + this.close(k, v); + V newV = this.doRegister(k, registerMusts.get(k)); + newRegisters.put(k, newV); + }); + this.registers = newRegisters; + + } + + protected abstract V doRegister(K k, boolean must); + + protected abstract void close(K k, V v); + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/shader/Shader.java b/src/main/java/xueli/game2/resource/submanager/render/shader/Shader.java new file mode 100644 index 00000000..1240c478 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/shader/Shader.java @@ -0,0 +1,182 @@ +package xueli.game2.resource.submanager.render.shader; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.HashMap; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL32; +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.renderer.legacy.buffer.Bindable; + +public class Shader implements Bindable { + + public static final Shader EMPTY_SHADER = new Shader(0, 0, 0, 0); + + private int shaderID, vertID, fragID, geoID = -1; + private HashMap uniforms = new HashMap(); + private FloatBuffer b = BufferUtils.createFloatBuffer(16); + + private boolean bound = false; + + public Shader(String vertPath, String fragPath) { + vertID = compile(vertPath, GL20.GL_VERTEX_SHADER); + fragID = compile(fragPath, GL20.GL_FRAGMENT_SHADER); + + this.shaderID = GL20.glCreateProgram(); + GL20.glAttachShader(this.shaderID, vertID); + GL20.glAttachShader(this.shaderID, fragID); + GL20.glLinkProgram(this.shaderID); + GL20.glValidateProgram(this.shaderID); + + } + + public Shader(String vertPath, String geoPath, String fragPath) { + vertID = compile(vertPath, GL20.GL_VERTEX_SHADER); + geoID = compile(geoPath, GL32.GL_GEOMETRY_SHADER); + fragID = compile(fragPath, GL20.GL_FRAGMENT_SHADER); + + this.shaderID = GL20.glCreateProgram(); + GL20.glAttachShader(this.shaderID, vertID); + GL20.glAttachShader(this.shaderID, geoID); + GL20.glAttachShader(this.shaderID, fragID); + GL20.glLinkProgram(this.shaderID); + GL20.glValidateProgram(this.shaderID); + + } + + private Shader(int shaderID, int vertID, int fragID, int geoID) { + this.shaderID = shaderID; + this.vertID = vertID; + this.fragID = fragID; + this.geoID = geoID; + } + + public static Shader compile(String vertCode, String fragCode) { + int vertID = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); + GL20.glShaderSource(vertID, vertCode); + GL20.glCompileShader(vertID); + if (GL20.glGetShaderi(vertID, GL20.GL_COMPILE_STATUS) != GL11.GL_TRUE) { + throw new RuntimeException(GL20.glGetShaderInfoLog(vertID, 500)); + } + + int fragID = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); + GL20.glShaderSource(fragID, fragCode); + GL20.glCompileShader(fragID); + if (GL20.glGetShaderi(fragID, GL20.GL_COMPILE_STATUS) != GL11.GL_TRUE) { + throw new RuntimeException(GL20.glGetShaderInfoLog(fragID, 500)); + } + + int id = GL20.glCreateProgram(); + GL20.glAttachShader(id, vertID); + GL20.glAttachShader(id, fragID); + GL20.glLinkProgram(id); + GL20.glValidateProgram(id); + + return new Shader(id, vertID, fragID, 0); + } + + private int compile(String shaderPath, int type) { + StringBuilder source = new StringBuilder(); + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(new File(shaderPath))); + String line = null; + while ((line = reader.readLine()) != null) { + source.append(line).append("\r\n"); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + int id = GL20.glCreateShader(type); + GL20.glShaderSource(id, source.toString()); + GL20.glCompileShader(id); + + if (GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS) != GL11.GL_TRUE) { + throw new RuntimeException(GL20.glGetShaderInfoLog(id, 500)); + } + + return id; + } + + @Override + public void bind() { + if (this.bound) + return; + GL20.glUseProgram(this.shaderID); + this.bound = true; + + } + + protected void bindAttribute(String name, int attrib_id) { + GL20.glBindAttribLocation(this.shaderID, attrib_id, name); + } + + public int getUnifromLocation(String name) { + int location = -1; + if (uniforms.containsKey(name)) { + location = uniforms.get(name); + } else { + location = GL20.glGetUniformLocation(this.shaderID, name); + uniforms.put(name, location); + } + if (location == -1) + System.err.println("Can't find uniform location: " + name); + return location; + } + + public void setUniformMatrix(int loc, Matrix4f mat) { + b.clear(); + mat.store(b); + b.flip(); + GL20.glUniformMatrix4fv(loc, false, b); + } + + public void setUniformVector3f(int loc, Vector3f v) { + GL20.glUniform3f(loc, v.x, v.y, v.z); + } + + public void setUniformVector2f(int loc, Vector2f v) { + GL20.glUniform2f(loc, v.x, v.y); + } + + public void setInt(int loc, int v) { + GL20.glUniform1i(loc, v); + } + + public void setFloat(int loc, float v) { + GL20.glUniform1f(loc, v); + } + + @Override + public void unbind() { + if (!this.bound) + return; + this.bound = false; + GL20.glUseProgram(0); + + } + + public void release() { + GL20.glDetachShader(this.shaderID, vertID); + GL20.glDetachShader(this.shaderID, fragID); + GL20.glDeleteProgram(this.shaderID); + GL20.glDeleteShader(vertID); + GL20.glDeleteShader(fragID); + } + + public boolean isBound() { + return bound; + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderRenderResource.java b/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderRenderResource.java new file mode 100644 index 00000000..7b4361c5 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderRenderResource.java @@ -0,0 +1,40 @@ +package xueli.game2.resource.submanager.render.shader; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.manager.ChainedResourceManager; +import xueli.game2.resource.manager.ResourceManager; +import xueli.game2.resource.submanager.render.RenderResource; + +public class ShaderRenderResource extends RenderResource { + + public ShaderRenderResource(ChainedResourceManager superiorManager) { + super(superiorManager); + } + + @Override + protected Shader doRegister(ShaderResourceLocation k, boolean must) { + ResourceManager manager = getUpperResourceManager(); + try { + Resource vertResource = manager.getResource(k.vert()); + String vertCode = vertResource.readAllString(); + + Resource fragResource = manager.getResource(k.frag()); + String fragCode = fragResource.readAllString(); + + return Shader.compile(vertCode, fragCode); + } catch (Exception e) { + if (must) + throw new RuntimeException(e); + else { + e.printStackTrace(); + return Shader.EMPTY_SHADER; + } + } + } + + @Override + protected void close(ShaderResourceLocation k, Shader v) { + v.release(); + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderResourceLocation.java b/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderResourceLocation.java new file mode 100644 index 00000000..8686ae4a --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/shader/ShaderResourceLocation.java @@ -0,0 +1,6 @@ +package xueli.game2.resource.submanager.render.shader; + +import xueli.registry.Identifier; + +public record ShaderResourceLocation(Identifier vert, Identifier frag) { +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/AbstractTextureLoader.java b/src/main/java/xueli/game2/resource/submanager/render/texture/AbstractTextureLoader.java new file mode 100644 index 00000000..227c9789 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/AbstractTextureLoader.java @@ -0,0 +1,25 @@ +package xueli.game2.resource.submanager.render.texture; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.manager.ResourceManager; +import xueli.registry.Identifier; + +public abstract class AbstractTextureLoader implements TextureLoader { + + @Override + public Texture registerTexture(Identifier res, ResourceManager manager) throws IOException { + Resource resource = manager.getResource(res); + InputStream in = resource.openInputStream(); + BufferedImage image = ImageIO.read(in); + return registerTexture(image); + } + + public abstract Texture registerTexture(BufferedImage image); + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/NvgImageFlag.java b/src/main/java/xueli/game2/resource/submanager/render/texture/NvgImageFlag.java new file mode 100644 index 00000000..3711e9d5 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/NvgImageFlag.java @@ -0,0 +1,21 @@ +package xueli.game2.resource.submanager.render.texture; + +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_FLIPY; +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_GENERATE_MIPMAPS; +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_NEAREST; +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_PREMULTIPLIED; +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_REPEATX; +import static org.lwjgl.nanovg.NanoVG.NVG_IMAGE_REPEATY; + +public enum NvgImageFlag { + + GENERATE_MIPMAPS(NVG_IMAGE_GENERATE_MIPMAPS), REPEAT_X(NVG_IMAGE_REPEATX), REPEAT_Y(NVG_IMAGE_REPEATY), + FLIPY(NVG_IMAGE_FLIPY), PREMULTIPLIED(NVG_IMAGE_PREMULTIPLIED), NEAREST(NVG_IMAGE_NEAREST); + + int val; + + NvgImageFlag(int vgVal) { + this.val = vgVal; + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/Texture.java b/src/main/java/xueli/game2/resource/submanager/render/texture/Texture.java new file mode 100644 index 00000000..5e578065 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/Texture.java @@ -0,0 +1,4 @@ +package xueli.game2.resource.submanager.render.texture; + +public record Texture(int id, int width, int height) { +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoader.java b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoader.java new file mode 100644 index 00000000..4a501ef0 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoader.java @@ -0,0 +1,14 @@ +package xueli.game2.resource.submanager.render.texture; + +import java.io.IOException; + +import xueli.game2.resource.manager.ResourceManager; +import xueli.registry.Identifier; + +public interface TextureLoader { + + public Texture registerTexture(Identifier res, ResourceManager manager) throws IOException; + + public void releaseTexture(Texture id); + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderLegacy.java b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderLegacy.java new file mode 100644 index 00000000..4b55ff93 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderLegacy.java @@ -0,0 +1,54 @@ +package xueli.game2.resource.submanager.render.texture; + +import static org.lwjgl.opengl.GL11.GL_NEAREST; +import static org.lwjgl.opengl.GL11.GL_RGBA; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glDeleteTextures; +import static org.lwjgl.opengl.GL11.glGenTextures; +import static org.lwjgl.opengl.GL11.glTexImage2D; +import static org.lwjgl.opengl.GL11.glTexParameteri; +import static org.lwjgl.opengl.GL12.GL_TEXTURE_BASE_LEVEL; +import static org.lwjgl.opengl.GL12.GL_TEXTURE_MAX_LEVEL; +import static org.lwjgl.opengl.GL13.GL_CLAMP_TO_BORDER; + +import java.awt.image.BufferedImage; + +public class TextureLoaderLegacy extends AbstractTextureLoader { + + public static final TextureLoaderLegacy INSTANCE = new TextureLoaderLegacy(); + + @Override + public Texture registerTexture(BufferedImage image) { + int id = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + 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_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); + glBindTexture(GL_TEXTURE_2D, 0); + + int width = image.getWidth(); + int height = image.getHeight(); + int[] data = TextureLoaderUtils.imageToLegacyData(image); + + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindTexture(GL_TEXTURE_2D, 0); + + return new Texture(id, width, height); + } + + @Override + public void releaseTexture(Texture tex) { + glDeleteTextures(tex.id()); + } + +} \ No newline at end of file diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderUtils.java b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderUtils.java new file mode 100644 index 00000000..b1b9e252 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureLoaderUtils.java @@ -0,0 +1,22 @@ +package xueli.game2.resource.submanager.render.texture; + +import java.awt.image.BufferedImage; + +public class TextureLoaderUtils { + + public static int[] imageToLegacyData(BufferedImage image) { + int[] pixels = new int[image.getWidth() * image.getHeight()]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); + + int[] data = new int[image.getWidth() * image.getHeight()]; + for (int i = 0; i < image.getWidth() * image.getHeight(); i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + data[i] = a << 24 | b << 16 | g << 8 | r; + } + return data; + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/TextureMissing.java b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureMissing.java new file mode 100644 index 00000000..20300cca --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureMissing.java @@ -0,0 +1,34 @@ +package xueli.game2.resource.submanager.render.texture; + +import java.awt.Color; +import java.awt.image.BufferedImage; + +public class TextureMissing { + + public static final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_4BYTE_ABGR); + + static { + int rgb1 = new Color(0, 0, 0).getRGB(); + int rgb2 = new Color(248, 0, 248).getRGB(); + + for (int y = 0; y < 16; ++y) { + for (int x = 0; x < 16; ++x) { + if ((x < 8) ^ (y < 8)) { + image.setRGB(x, y, rgb1); + } else { + image.setRGB(x, y, rgb2); + } + } + } + + } + + public static Texture get(AbstractTextureLoader loader) { + try { + return loader.registerTexture(image); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/TextureRenderResource.java b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureRenderResource.java new file mode 100644 index 00000000..78aea547 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/TextureRenderResource.java @@ -0,0 +1,35 @@ +package xueli.game2.resource.submanager.render.texture; + +import java.io.IOException; + +import xueli.game2.resource.manager.ChainedResourceManager; +import xueli.game2.resource.submanager.render.RenderResource; +import xueli.registry.Identifier; + +public class TextureRenderResource extends RenderResource { + + private static final TextureLoaderLegacy LEGACY_LOADER = new TextureLoaderLegacy(); + + public TextureRenderResource(ChainedResourceManager manager) { + super(manager); + + } + + @Override + protected Texture doRegister(Identifier k, boolean must) { + try { + return LEGACY_LOADER.registerTexture(k, getUpperResourceManager()); + } catch (IOException | NullPointerException e) { + if (must) + throw new RuntimeException(e); + else + return TextureMissing.get(LEGACY_LOADER); + } + } + + @Override + protected void close(Identifier k, Texture v) { + LEGACY_LOADER.releaseTexture(v); + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceHolder.java b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceHolder.java new file mode 100644 index 00000000..c185f7f6 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceHolder.java @@ -0,0 +1,61 @@ +package xueli.game2.resource.submanager.render.texture.atlas; + +import java.util.Objects; + +import org.lwjgl.utils.vector.Vector2f; + +public final class AtlasResourceHolder { + +// private final int textureId; + + private final Vector2f leftTop; + private final Vector2f rightBottom; + private final Vector2f leftBottom; + private final Vector2f rightTop; + + public AtlasResourceHolder(Vector2f leftTop, Vector2f rightBottom) { + this.leftTop = leftTop; + this.rightBottom = rightBottom; + + this.leftBottom = new Vector2f(leftTop.x, rightBottom.y); + this.rightTop = new Vector2f(rightBottom.x, leftTop.y); + + } + + public Vector2f leftTop() { + return leftTop; + } + + public Vector2f rightBottom() { + return rightBottom; + } + + public Vector2f leftBottom() { + return leftBottom; + } + + public Vector2f rightTop() { + return rightTop; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + var that = (AtlasResourceHolder) obj; + return Objects.equals(this.leftTop, that.leftTop) && Objects.equals(this.rightBottom, that.rightBottom); + } + + @Override + public int hashCode() { + return Objects.hash(leftTop, rightBottom); + } + + @Override + public String toString() { + return "AtlasResourceHolder[" + "leftTop=" + leftTop + ", " + "rightBottom=" + rightBottom + ']'; + } + +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceLocation.java b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceLocation.java new file mode 100644 index 00000000..b6cdb9b0 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasResourceLocation.java @@ -0,0 +1,6 @@ +package xueli.game2.resource.submanager.render.texture.atlas; + +import xueli.registry.Identifier; + +public record AtlasResourceLocation(Identifier path, String name) { +} diff --git a/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasTextureRenderResource.java b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasTextureRenderResource.java new file mode 100644 index 00000000..8e2d41f4 --- /dev/null +++ b/src/main/java/xueli/game2/resource/submanager/render/texture/atlas/AtlasTextureRenderResource.java @@ -0,0 +1,143 @@ +package xueli.game2.resource.submanager.render.texture.atlas; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import javax.imageio.ImageIO; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.utils.vector.Vector2f; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.manager.SubResourceManager; +import xueli.game2.resource.submanager.render.texture.TextureLoaderLegacy; +import xueli.game2.resource.submanager.render.texture.TextureMissing; +import xueli.game2.resource.submanager.render.texture.TextureRenderResource; +import xueli.registry.Identifier; + +/** + * Maybe you should implement it yourself + */ +@Deprecated +public class AtlasTextureRenderResource extends SubResourceManager { + + private final ArrayList registerData = new ArrayList<>(); + private final ArrayList registeredTexture = new ArrayList<>(); + private final HashMap> atlasHolders = new HashMap<>(); + + public AtlasTextureRenderResource(TextureRenderResource superiorManager) { + super(superiorManager); + } + + private record RegisterData(Identifier path, Predicate selector) { + } + + public void findAndRegister(Identifier path, Predicate selector) { + registerData.add(new RegisterData(path, selector)); + + List resources = null; + try { + resources = this.findResources(path, selector); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + if (resources.size() == 0) { + System.err.println("Can't find atlas in: " + path); + return; + } + + int[] maxWidth = { 0 }, maxHeight = { 0 }; + int count = 0; + + List images = resources.stream().filter(res -> selector.test(res.getName())).map(res -> { + try { + InputStream in = res.openInputStream(); + BufferedImage image = ImageIO.read(in); + + maxWidth[0] = Math.max(maxWidth[0], image.getWidth()); + maxHeight[0] = Math.max(maxHeight[0], image.getHeight()); + + return image; + } catch (IOException e) { + e.printStackTrace(); + return TextureMissing.image; + } + }).toList(); + count = images.size(); + + int atlasSize = (int) Math.ceil(Math.sqrt(count)); + int atlasImageWidth = atlasSize * maxWidth[0]; + int atlasImageHeight = atlasSize * maxHeight[0]; + + HashMap list = atlasHolders.computeIfAbsent(path, key -> new HashMap<>()); + BufferedImage atlasImage = new BufferedImage(atlasImageWidth, atlasImageHeight, BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D g2d = atlasImage.createGraphics(); + + for (int i = 0; i < images.size(); i++) { + BufferedImage image = images.get(i); + int x = i % atlasSize; + int y = i / atlasSize; + + Vector2f leftTop = new Vector2f((float) x / atlasSize, (float) y / atlasSize); + Vector2f rightBottom = new Vector2f((float) (x * maxWidth[0] + image.getWidth()) / atlasImageWidth, + (float) (y * maxHeight[0] + image.getHeight()) / atlasImageHeight); + list.put(resources.get(i).getName(), new AtlasResourceHolder(leftTop, rightBottom)); + + g2d.drawImage(image, x * maxWidth[0], y * maxHeight[0], null); + + } + + g2d.dispose(); + + registeredTexture.add(TextureLoaderLegacy.INSTANCE.registerTexture(atlasImage).id()); + + } + + public Map getAllHolders(Identifier path) { + HashMap map = atlasHolders.get(path); + return map; + } + + public AtlasResourceHolder getHolder(Identifier path, String name) { + HashMap map = atlasHolders.get(path); + if (map == null) + return null; + AtlasResourceHolder holder = map.get(name); + if (holder == null) + return null; + return holder; + } + + @Override + public void reload() { + this.closeThis(); + + for (RegisterData data : registerData) { + this.findAndRegister(data.path(), data.selector()); + } + + super.reload(); + } + + private void closeThis() { + registeredTexture.forEach(GL11::glDeleteTextures); + registeredTexture.clear(); + atlasHolders.clear(); + } + + @Override + public void close() throws IOException { + closeThis(); + super.close(); + } + +} diff --git a/src/main/java/xueli/gui/BufferedPaintManager.java b/src/main/java/xueli/gui/BufferedPaintManager.java new file mode 100644 index 00000000..a95fcf15 --- /dev/null +++ b/src/main/java/xueli/gui/BufferedPaintManager.java @@ -0,0 +1,172 @@ +package xueli.gui; + +import xueli.gui.driver.FrameBuffer; +import xueli.gui.driver.GraphicDriver; + +import java.util.LinkedList; +import java.util.Objects; + +// This paint manager only draw the widget itself, but widget's "x" and "y" is relative to its parent +// It uses frame buffer, but the painting can lag if size change is too frequent +public class BufferedPaintManager extends PaintManager { + +// private static final Logger LOGGER = new Logger(); + + private FrameBuffer frameBuffer; + + private LinkedList announcements = new LinkedList<>(); // Not thread safe + + public BufferedPaintManager(WidgetAccess widget, GraphicDriver driver) { + super(widget, driver); + + // Can't put in parent class initialization because "announcements" can be null there! + this.announceSizeChange(); + this.announceRepaint(0, 0, getWidgetWidth(), getWidgetHeight()); + + } + + @Override + public void announceSizeChange() { + this.tryMergeAnnouncement(new SizeChangeAnnouncement()); + } + + @Override + public void announceRepaint(float x, float y, float width, float height) { + if(width == 0 || height == 0) return; + this.tryMergeAnnouncement(new RepaintAnnouncement(x, y, width, height)); + + } + + private void tryMergeAnnouncement(Announcement next) { + Announcement last = announcements.peekLast(); + if(last == null) { + announcements.add(next); + return; + } + + if(last instanceof SizeChangeAnnouncement && next instanceof SizeChangeAnnouncement) + return; + if(last instanceof RepaintAnnouncement && next instanceof RepaintAnnouncement nr) { + if(last.equals(nr)) + return; + } + + announcements.add(next); + + } + + @Override + public void doPaint() { + Runnable announcement; + while (!this.announcements.isEmpty()) { + announcement = this.announcements.pop(); +// System.out.println("[UIPaint] " + announcement.toString() + " at " + w.toString()); + + announcement.run(); + + } + + int imageId = frameBuffer.getImageId(); + getDriver().drawImage(getWidgetX(), getWidgetY(), getWidgetWidth(), getWidgetHeight(), 1.0f, imageId); + + } + + @Override + public void release() { + if(frameBuffer != null) { + frameBuffer.release(); + frameBuffer = null; + } + + } + + private interface Announcement extends Runnable { + } + + private class SizeChangeAnnouncement implements Announcement { + @Override + public void run() { + // Make a bigger box to include the widget. To support float size and position + // of widget! + int realWidth = (int) Math.ceil(getWidgetWidth()); + int realHeight = (int) Math.ceil(getWidgetHeight()); + if (frameBuffer == null) + frameBuffer = getDriver().createFrameBuffer(realWidth, realHeight); + else + frameBuffer.resize(realWidth, realHeight); + + } + + @Override + public String toString() { + return "SizeChangeAnnouncement []"; + } + + } + + private class RepaintAnnouncement implements Announcement { + + float x, y, width, height; + + public RepaintAnnouncement(float x, float y, float width, float height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + @Override + public void run() { + GraphicDriver driver = getDriver(); + + driver.pushFrameBuffer(frameBuffer); + driver.begin(frameBuffer.getWidth(), frameBuffer.getHeight()); + + driver.scissorPush(x, y, width, height); + driver.clearColor(0, 0, 0, 0); + widgetRealPaint(x, y, width, height); + driver.scissorReset(); + + driver.finish(); + driver.popFrameBuffer(); + + } + + @Override + public String toString() { + return "RepaintAnnouncement [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getEnclosingInstance().hashCode(); + result = prime * result + Objects.hash(height, width, x, y); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RepaintAnnouncement other = (RepaintAnnouncement) obj; + if (!getEnclosingInstance().equals(other.getEnclosingInstance())) + return false; + return Float.floatToIntBits(height) == Float.floatToIntBits(other.height) + && Float.floatToIntBits(width) == Float.floatToIntBits(other.width) + && Float.floatToIntBits(x) == Float.floatToIntBits(other.x) + && Float.floatToIntBits(y) == Float.floatToIntBits(other.y); + } + + private BufferedPaintManager getEnclosingInstance() { + return BufferedPaintManager.this; + } + + } + +} diff --git a/src/main/java/xueli/gui/ImmediatePaintManager.java b/src/main/java/xueli/gui/ImmediatePaintManager.java new file mode 100644 index 00000000..ff2fcff9 --- /dev/null +++ b/src/main/java/xueli/gui/ImmediatePaintManager.java @@ -0,0 +1,45 @@ +package xueli.gui; + +import org.lwjgl.utils.vector.Matrix3f; +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2f; +import xueli.gui.driver.GraphicDriver; + +// Every frame it just draws all +public class ImmediatePaintManager extends PaintManager { + + private final GraphicDriver driver; + + public ImmediatePaintManager(WidgetAccess widget, GraphicDriver driver) { + super(widget, driver); + this.driver = driver; + + // Can't put in parent class initialization because "announcements" can be null there! + this.announceSizeChange(); + this.announceRepaint(0, 0, getWidgetWidth(), getWidgetHeight()); + + } + + @Override + public void announceSizeChange() { + } + + @Override + public void announceRepaint(float x, float y, float width, float height) { + } + + @Override + public void doPaint() { + this.driver.pushMatrix(Matrix3f.translate(new Vector2f(getWidgetX(), getWidgetY()), new Matrix3f(), null)); + this.driver.scissorPush(0, 0, getWidgetWidth(), getWidgetHeight()); + this.widgetRealPaint(0, 0, getWidgetWidth(), getWidgetHeight()); + this.driver.scissorPop(); + this.driver.popMatrix(); + + } + + @Override + public void release() { + } + +} diff --git a/src/main/java/xueli/gui/MyUIFrameworkInfo.java b/src/main/java/xueli/gui/MyUIFrameworkInfo.java new file mode 100644 index 00000000..6804ca49 --- /dev/null +++ b/src/main/java/xueli/gui/MyUIFrameworkInfo.java @@ -0,0 +1,11 @@ +package xueli.gui; + +public class MyUIFrameworkInfo { + + // How to make a name for my framework? + public static final String FRAMEWORK_NAME = "xueli"; + + private MyUIFrameworkInfo() { + } + +} diff --git a/src/main/java/xueli/gui/PaintManager.java b/src/main/java/xueli/gui/PaintManager.java new file mode 100644 index 00000000..6fe4895e --- /dev/null +++ b/src/main/java/xueli/gui/PaintManager.java @@ -0,0 +1,66 @@ +package xueli.gui; + +import java.lang.ref.WeakReference; + +import xueli.gui.driver.GraphicDriver; + +public abstract class PaintManager { + + private final WidgetAccess widget; + + private final GraphicDriver driver; + + public PaintManager(WidgetAccess widget, GraphicDriver driver) { + this.widget = widget; + this.driver = driver; + + } + + public abstract void announceSizeChange(); + + public abstract void announceRepaint(float x, float y, float width, float height); + + protected abstract void doPaint(); + + protected abstract void release(); + + public SizeHint measure() { + return this.widget.measure(); + } + + protected void widgetRealPaint(float x, float y, float width, float height) { + this.widget.doPaint(x, y, width, height); + } + + protected float getWidgetWidth() { + return this.widget.getWidth(); + } + + protected float getWidgetHeight() { + return this.widget.getHeight(); + } + + protected float getWidgetX() { + return this.widget.getX(); + } + + protected float getWidgetY() { + return this.widget.getY(); + } + + public GraphicDriver getDriver() { + return driver; + } + + public static interface WidgetAccess { + + public SizeHint measure(); + public void doPaint(float x, float y, float width, float height); + public float getX(); + public float getY(); + public float getWidth(); + public float getHeight(); + + } + +} diff --git a/src/main/java/xueli/gui/PaintMaster.java b/src/main/java/xueli/gui/PaintMaster.java new file mode 100644 index 00000000..f07ba84d --- /dev/null +++ b/src/main/java/xueli/gui/PaintMaster.java @@ -0,0 +1,117 @@ +package xueli.gui; + +import xueli.gui.driver.GraphicDriver; +import xueli.utils.MyWeakHashMap; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +public class PaintMaster { + + private final GraphicDriver driver; + + private final ReferenceQueue referenceQueue = new ReferenceQueue<>(); + private final MyWeakHashMap painters = new MyWeakHashMap<>(referenceQueue, PaintManager::release); + + private final WeakHashMap immediateModeMap = new WeakHashMap<>(); + + public PaintMaster(GraphicDriver driver) { + this.driver = driver; + + } + + public PaintManager getPaintManager(Widget widget) { + return this.getPaintManager(widget, false); + } + + public PaintManager getPaintManager(Widget widget, boolean immediate) { + PaintManager manager = this.painters.get(widget); + if(manager == null) { + return this.createAndStorePaintManager(widget, immediate); + } + + if((immediate && manager instanceof BufferedPaintManager) + || (!immediate && manager instanceof ImmediatePaintManager)) { + manager.release(); + } else { + return manager; + } + return this.createAndStorePaintManager(widget, immediate); + } + + private PaintManager createAndStorePaintManager(Widget widget, boolean immediate) { + PaintManager newManager = immediate ? + new ImmediatePaintManager(new MyWidgetAccess(new WeakReference<>(widget)), driver) + : new BufferedPaintManager(new MyWidgetAccess(new WeakReference<>(widget)), driver); + newManager.announceRepaint(0, 0, widget.getWidth(), widget.getHeight()); + this.painters.put(widget, newManager); + return newManager; + } + + public void drawWidget(Widget widget) { + this.getPaintManager(widget, this.isUseImmediateMode(widget)).doPaint(); + } + + public void drawWidget(Widget widget, GraphicDriver newDriver) { + this.getPaintManager(widget, this.isUseImmediateMode(widget)).doPaint(); + } + + public void setUseImmediateMode(Widget widget, boolean use) { + immediateModeMap.put(widget, use); + } + + public boolean isUseImmediateMode(Widget widget) { + Boolean objVal = immediateModeMap.get(widget); + return objVal == null ? false : objVal.booleanValue(); + } + + public void tick() { + } + + public GraphicDriver getDriver() { + return driver; + } + + private class MyWidgetAccess implements PaintManager.WidgetAccess { + + private final WeakReference widget; + + public MyWidgetAccess(WeakReference widget) { + this.widget = widget; + } + + @Override + public SizeHint measure() { + var widget = this.widget.get(); + return widget.getSkin().measure(widget, driver); + } + + @Override + public void doPaint(float x, float y, float width, float height) { + var widget = this.widget.get(); + widget.getSkin().paint(widget, x, y, width, height, PaintMaster.this); + } + + @Override + public float getX() { + return this.widget.get().getX(); + } + + @Override + public float getY() { + return this.widget.get().getY(); + } + + @Override + public float getWidth() { + return this.widget.get().getWidth(); + } + + @Override + public float getHeight() { + return this.widget.get().getHeight(); + } + } + +} diff --git a/src/main/java/xueli/gui/SizeHint.java b/src/main/java/xueli/gui/SizeHint.java new file mode 100644 index 00000000..47a2c6cd --- /dev/null +++ b/src/main/java/xueli/gui/SizeHint.java @@ -0,0 +1,14 @@ +package xueli.gui; + +// I've glanced at sources of QT as well :} +public record SizeHint(int type, int width, int height) { + + public static final int POLICY_FIXED = 0; + public static final int POLICY_MINIMUM = 1; + public static final int POLICY_MAXIMUM = 2; + public static final int POLICY_PREFERED = 3; + public static final int POLICY_EXPANDING = 4; + public static final int POLICY_MINIMUM_EXPANDING = 5; + public static final int POLICY_IGNORED = 6; + +} diff --git a/src/main/java/xueli/gui/SkinTheme.java b/src/main/java/xueli/gui/SkinTheme.java new file mode 100644 index 00000000..a40221e1 --- /dev/null +++ b/src/main/java/xueli/gui/SkinTheme.java @@ -0,0 +1,23 @@ +package xueli.gui; + +import java.util.HashMap; + +import xueli.gui.driver.GraphicDriver; + +public class SkinTheme { + + private final HashMap, WidgetSkin> skinRegistry = new HashMap<>(); + + protected void registerSkin(Class clazz, WidgetSkin skin) { + this.skinRegistry.put(clazz, skin); + } + + public WidgetSkin getSkin(Class clazz) { + return skinRegistry.get(clazz); + } + + public void install(GraphicDriver driver) {} + + public void uninstall(GraphicDriver driver) {} + +} diff --git a/src/main/java/xueli/gui/UIContext.java b/src/main/java/xueli/gui/UIContext.java new file mode 100644 index 00000000..342d6d04 --- /dev/null +++ b/src/main/java/xueli/gui/UIContext.java @@ -0,0 +1,141 @@ +package xueli.gui; + +import java.util.LinkedList; + +import xueli.game2.display.GameDisplay; +import xueli.game2.display.event.CursorPositionEvent; +import xueli.game2.display.event.WindowKeyEvent; +import xueli.game2.display.event.WindowMouseButtonEvent; +import xueli.game2.display.event.WindowSizedEvent; +import xueli.gui.driver.GraphicDriver; + +public class UIContext { + +// private static final Logger LOGGER = new Logger(); + + // TODO: re-design Graphic Driver or create a new interface to adapt to different display + // Maybe next time Windows API is used directly (never + private final GameDisplay display; + private final GraphicDriver driver; + + private final PaintMaster paintManager; + private final LinkedList eventQueue = new LinkedList<>(); // Not thread safe + + private final WidgetGroup root; + + public UIContext(GraphicDriver driver, GameDisplay display) { + this.display = display; + this.driver = driver; + this.paintManager = new PaintMaster(driver); + this.registerEventListener(); + + this.root = new WidgetGroup(this); +// this.root.setUseImmediateMode(true); + this.onEventbusSizedEvent(new WindowSizedEvent(getDisplayWidth(), getDisplayHeight())); + + } + + public void postEvent(UIEvent event) { + this.tryMergeWithLastEvent(event); + } + + private void tryMergeWithLastEvent(UIEvent next) { + UIEvent last = eventQueue.peek(); + if(last == null) { + eventQueue.add(next); + return; + } + + if(last.type() == UIEvent.EVENT_CURSOR_POSITION && next.type() == UIEvent.EVENT_CURSOR_POSITION) { + eventQueue.poll(); + eventQueue.add(next); + } else { + eventQueue.add(next); + } + } + + public void tick() { + this.processEvent(); + this.paintManager.tick(); + + this.driver.begin(getDisplayWidth(), getDisplayHeight()); + this.paintManager.drawWidget(this.root); + this.driver.finish(); + + } + + public void release() { + this.unregisterEventListener(); + + } + + private void registerEventListener() { + // TODO: no more register here + // maybe the driver is responsible for this + display.eventbus.register(WindowKeyEvent.class, this::onEventbusKeyEvent); + display.eventbus.register(WindowSizedEvent.class, this::onEventbusSizedEvent); // TODO: Merge Size Event + display.eventbus.register(WindowMouseButtonEvent.class, this::onEventbusMouseButtonEvent); + display.eventbus.register(CursorPositionEvent.class, this::onEventbusCursorPositionEvent); + + } + + private void unregisterEventListener() { + display.eventbus.unregister(WindowKeyEvent.class, this::onEventbusKeyEvent); + display.eventbus.unregister(WindowSizedEvent.class, this::onEventbusSizedEvent); + display.eventbus.unregister(WindowMouseButtonEvent.class, this::onEventbusMouseButtonEvent); + display.eventbus.unregister(CursorPositionEvent.class, this::onEventbusCursorPositionEvent); + + } + + private void onEventbusKeyEvent(WindowKeyEvent e) { + this.postEvent(new UIEvent(UIEvent.EVENT_KEY, e)); + } + + private void onEventbusSizedEvent(WindowSizedEvent e) { +// this.postEvent(new UIEvent(UIEvent.EVENT_WINDOW_SIZED, e)); + + this.root.setBounds(0, 0, e.width(), e.height()); + + } + + private void onEventbusMouseButtonEvent(WindowMouseButtonEvent e) { + this.postEvent(new UIEvent(UIEvent.EVENT_MOUSE_INPUT, e)); + } + + private void onEventbusCursorPositionEvent(CursorPositionEvent e) { + this.postEvent(new UIEvent(UIEvent.EVENT_CURSOR_POSITION, e)); + } + + private void processEvent() { + UIEvent e; + while ((e = this.eventQueue.poll()) != null) { +// LOGGER.info("[UIEvent] " + e.toString()); + root.dispatchEvent(e); + } + } + + public void setUseImmediateMode(Widget widget, boolean use) { + // TODO + } + + public PaintMaster getPaintMaster() { + return paintManager; + } + + public WidgetGroup getRootWidget() { + return root; + } + + public int getDisplayWidth() { + return this.display.getWidth(); + } + + public int getDisplayHeight() { + return this.display.getHeight(); + } + + public GraphicDriver getDriver() { + return driver; + } + +} diff --git a/src/main/java/xueli/gui/UIEvent.java b/src/main/java/xueli/gui/UIEvent.java new file mode 100644 index 00000000..90bed5d7 --- /dev/null +++ b/src/main/java/xueli/gui/UIEvent.java @@ -0,0 +1,10 @@ +package xueli.gui; + +public record UIEvent(int type, Object data) { + + public static final int EVENT_KEY = 0; +// public static final int EVENT_WINDOW_SIZED = 1; + public static final int EVENT_MOUSE_INPUT = 2; + public static final int EVENT_CURSOR_POSITION = 3; + +} diff --git a/src/main/java/xueli/gui/Widget.java b/src/main/java/xueli/gui/Widget.java new file mode 100644 index 00000000..1026594b --- /dev/null +++ b/src/main/java/xueli/gui/Widget.java @@ -0,0 +1,166 @@ +package xueli.gui; + +import java.beans.PropertyChangeEvent; +import java.lang.ref.WeakReference; + +import org.lwjgl.utils.vector.Vector2d; +import org.lwjgl.utils.vector.Vector2f; + +import xueli.gui.skin.DefaultSkin; + +/** + *

The controller of widget, containing position, size and other components. It stores and manages its model and view, receives all outside invokes and events and publish all necessary interface.

+ * + *

Designed for MVC mode: https://baijiahao.baidu.com/s?id=1705333225423245283

+ * + * @author Xueli + */ +public class Widget extends WidgetBean { + +// private static final Logger LOGGER = new Logger(); + + public static final String PROPERTY_POSITION = "pos"; + public static final String PROPERTY_SIZE = "size"; + + protected Widget parent = null; + + private float x = 0.0f, y = 0.0f; // Relative to the parent + private float width = 0.0f, height = 0.0f; + + private final WidgetUI ui; + + public Widget(UIContext ctx) { + super(ctx); + this.registerPropertyChange(); + this.ui = new WidgetUI(new WeakReference<>(this), ctx); + + setSkin(DefaultSkin.SHARED_INSTANCE.getSkin(getClass())); + + } + + private void registerPropertyChange() { + this.registerPropertyChangeListener(PROPERTY_SIZE, this::onPropertySizeChange); + this.registerPropertyChangeListener(PROPERTY_POSITION, this::onPropertyPositionChange); + + } + + private void onPropertySizeChange(PropertyChangeEvent event) { + var painter = ctx.getPaintMaster().getPaintManager(this); + painter.announceSizeChange(); + this.announceRepaint(); + } + + private void onPropertyPositionChange(PropertyChangeEvent event) { + if (this.parent == null) + return; // it is the root node indeed! + this.parent.announceRepaint(); + } + + // Set the bounds. If this is a view group, then the layout code can be added to + // property change listener. + public void setBounds(float x, float y, float width, float height) { + this.setPosition(x, y); + this.setDimension(width, height); + + } + + public void setPosition(float x, float y) { + boolean moved = this.x != x || this.y != y; + Vector2f oldPos = new Vector2f(this.x, this.y); + this.x = x; + this.y = y; + + if (moved) { + this.firePropertyChange(PROPERTY_POSITION, oldPos, new Vector2d(x, y)); + + } + + } + + public void setDimension(float width, float height) { + boolean resized = this.width != width || this.height != height; + Vector2f oldSize = new Vector2f(this.width, this.height); + this.width = width; + this.height = height; + + if (resized) { + this.firePropertyChange(PROPERTY_SIZE, oldSize, new Vector2d(width, height)); + } + + } + + // "Repaint" should happen when an area of widget should be updated + // So if only the position of the widget is changed, the parent should be + // updated + protected void announceRepaint(float x, float y, float width, float height) { + this.ui.announceRepaint(x, y, width, height); + + } + + protected void announceRepaint() { + this.ui.announceRepaint(); + } + + // Should only be called from Skin or test! +// public SizeHint measure() { +// return this.ui.measure(); +// } + + // Should only be called from Skin or test! +// public void doPaint() { +// this.ui.doPaint(); +// } + + protected void dispatchEvent(UIEvent event) { +// switch(event.type()) { +// case UIEvent.EVENT_KEY -> { +// +// } +// case UIEvent.EVENT_WINDOW_SIZED -> { +// +// } +// case UIEvent.EVENT_MOUSE_INPUT -> { +// +// } +// case UIEvent.EVENT_CURSOR_POSITION -> { +// +// } +// default -> { +// LOGGER.warning("Unknown event: " + event.toString()); +// } +// } + } + + public void setSkin(WidgetSkin skin) { + this.ui.setSkin(skin); + } + + public final float getX() { + return x; + } + + public final float getY() { + return y; + } + + public final float getWidth() { + return width; + } + + public final float getHeight() { + return height; + } + + public WidgetSkin getSkin() { + return this.ui.getSkin(); + } + + public void setUseImmediateMode(boolean useImmediateMode) { + this.ui.setUseImmediateMode(useImmediateMode); + } + + public boolean isUseImmediateMode() { + return this.ui.getUseImmediateMode(); + } + +} diff --git a/src/main/java/xueli/gui/WidgetBean.java b/src/main/java/xueli/gui/WidgetBean.java new file mode 100644 index 00000000..63c1d8ba --- /dev/null +++ b/src/main/java/xueli/gui/WidgetBean.java @@ -0,0 +1,72 @@ +package xueli.gui; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Objects; + +import javax.swing.text.SimpleAttributeSet; + +public class WidgetBean { + + protected final UIContext ctx; + + private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); + private final SimpleAttributeSet attributes = new SimpleAttributeSet(); + + public WidgetBean(UIContext ctx) { + this.ctx = ctx; + } + + public void registerPropertyChangeListener(PropertyChangeListener listener) { + this.changeSupport.addPropertyChangeListener(listener); + } + + public void unregisterPropertyChangeListener(PropertyChangeListener listener) { + this.changeSupport.removePropertyChangeListener(listener); + } + + public void registerPropertyChangeListener(String name, PropertyChangeListener listener) { + this.changeSupport.addPropertyChangeListener(name, listener); + } + + public void unregisterPropertyChangeListener(String name, PropertyChangeListener listener) { + this.changeSupport.removePropertyChangeListener(name, listener); + } + + public void firePropertyChange(String name, Object oldValue, Object newValue) { + this.changeSupport.firePropertyChange(name, oldValue, newValue); + } + + public final boolean hasAttribute(Object name) { + return this.attributes.isDefined(name); + } + + public final Object getAttribute(Object name) { + return this.attributes.getAttribute(name); + } + + // Return the old value if contains + public final Object putAttribute(Object name, Object value) { + Object oldValue = null; + if(hasAttribute(name)) + oldValue = getAttribute(name); + + if(Objects.equals(value, oldValue)) + return oldValue; + + this.attributes.addAttribute(name, value); + this.firePropertyChange(getAttributePropertyName(name), oldValue, value); + return oldValue; + } + + public final void removeAttribute(Object name) { + this.attributes.removeAttribute(name); + } + + // So please use objects that have difference "toString" result! + public static final String getAttributePropertyName(Object name) { + // Actually this start prefix can be change to anything :} + return "P:" + name.toString(); + } + +} diff --git a/src/main/java/xueli/gui/WidgetGroup.java b/src/main/java/xueli/gui/WidgetGroup.java new file mode 100644 index 00000000..7d57e42b --- /dev/null +++ b/src/main/java/xueli/gui/WidgetGroup.java @@ -0,0 +1,99 @@ +package xueli.gui; + +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; + +import xueli.gui.layout.AbsoluteLayout; +import xueli.gui.layout.LayoutManager; +import xueli.gui.skin.DefaultWidgetGroupSkin; + +public class WidgetGroup extends Widget { + +// private static final Logger LOGGER = new Logger(); + +// public static final Identifier IDENTIFIER = new Identifier(MyUIFrameworkInfo.FRAMEWORK_NAME, "widget_group"); + + public static final String PROPERTY_LAYOUT_MANAGER = "layout"; + public static final String PROPERTY_WIDGET_ADD = "child_add"; + public static final String PROPERTY_WIDGET_REMOVE = "child_remove"; + + private final ArrayList widgets = new ArrayList<>(); + private LayoutManager layoutManager = new AbsoluteLayout(); + + public WidgetGroup(UIContext ctx) { + super(ctx); + this.registerPropertyListener(); + + } + + private void registerPropertyListener() { + this.registerPropertyChangeListener(PROPERTY_LAYOUT_MANAGER, this::onPropertyLayoutManagerChange); + this.registerPropertyChangeListener(PROPERTY_WIDGET_ADD, this::onPropertyChildrenAdd); + this.registerPropertyChangeListener(PROPERTY_WIDGET_REMOVE, this::onPropertyChildrenRemove); + + } + + private void onPropertyLayoutManagerChange(PropertyChangeEvent e) { + this.announceLayout(); + } + + private void onPropertyChildrenAdd(PropertyChangeEvent e) { + this.layoutManager.addWidget(parent, this); + this.announceLayout(); + + } + + private void onPropertyChildrenRemove(PropertyChangeEvent e) { + this.layoutManager.removeWidget(parent, this); + this.announceLayout(); + + } + + public void add(Widget widget) { + if(widget.parent != null) { + throw new IllegalStateException("Add it the second time? " + widget.toString() + " in " + this.toString()); + } + + widget.parent = this; + this.widgets.add(widget); + this.firePropertyChange(PROPERTY_WIDGET_ADD, null, widget); + + } + + public void remove(Widget widget) { + widget.parent = null; + this.widgets.remove(widget); + this.firePropertyChange(PROPERTY_WIDGET_REMOVE, null, widget); + + } + + public void announceLayout() { + this.layoutManager.doLayout(this); + + } + + public Widget getWidgetFromIndex(int i) { + return this.widgets.get(i); + } + + public int getWidgetCount() { + return this.widgets.size(); + } + + public void setSkin(DefaultWidgetGroupSkin skin) { + super.setSkin(skin); + } + + public DefaultWidgetGroupSkin getSkin() { + return (DefaultWidgetGroupSkin) super.getSkin(); + } + + public void setLayoutManager(LayoutManager layoutManager) { + this.layoutManager = layoutManager; + } + + public LayoutManager getLayoutManager() { + return layoutManager; + } + +} diff --git a/src/main/java/xueli/gui/WidgetSkin.java b/src/main/java/xueli/gui/WidgetSkin.java new file mode 100644 index 00000000..6360c224 --- /dev/null +++ b/src/main/java/xueli/gui/WidgetSkin.java @@ -0,0 +1,18 @@ +package xueli.gui; + +import xueli.gui.driver.GraphicDriver; + +// So I admit that I glance at the source of JavaFX :} +// The skin only affects the widget itself, so it shouldn't be considered when laying out parent widget +public interface WidgetSkin { + + public void install(Widget widget); + + public SizeHint measure(Widget widget, GraphicDriver graphics); + + // Paint an area of this widget + public void paint(Widget widget, float x, float y, float width, float height, PaintMaster paintMaster); + + public void uninstall(Widget widget); + +} diff --git a/src/main/java/xueli/gui/WidgetUI.java b/src/main/java/xueli/gui/WidgetUI.java new file mode 100644 index 00000000..dbc12c43 --- /dev/null +++ b/src/main/java/xueli/gui/WidgetUI.java @@ -0,0 +1,94 @@ +package xueli.gui; + +import java.beans.PropertyChangeEvent; +import java.lang.ref.WeakReference; + +/** + * The view of widget, only managing things concerning painting, publishing interfaces that have something to do with UI, for example, deciding which item is chosen when user clicking somewhere in a list widget. + * + * @author LoveliZeeiam + */ +class WidgetUI { + + public static final String PROPERTY_SKIN = "skin"; + + // Using Weak Reference here to help GC + private final WeakReference controller; + private final UIContext context; + + private WidgetSkin skin; + private boolean useImmediateMode = false; + + public WidgetUI(WeakReference controller, UIContext context) { + this.controller = controller; + this.context = context; + this.registerChangeListener(); + + } + + private void registerChangeListener() { + getController().registerPropertyChangeListener(PROPERTY_SKIN, this::onPropertySkinChange); + + } + + public void setUseImmediateMode(boolean useImmediateMode) { + this.useImmediateMode = useImmediateMode; + } + + public void setSkin(WidgetSkin skin) { + if(skin == this.skin) return; + + Widget controller = getController(); + + var oldSkin = this.skin; + if (oldSkin != null) { + oldSkin.uninstall(controller); + } + this.skin = skin; + if (skin != null) { + skin.install(controller); + } + controller.firePropertyChange(PROPERTY_SKIN, oldSkin, skin); + + } + + private void onPropertySkinChange(PropertyChangeEvent event) { + this.announceRepaint(); + } + + public void announceRepaint() { + this.announceRepaint(0, 0, getController().getWidth(), getController().getHeight()); + } + + public void announceRepaint(float x, float y, float width, float height) { + // Maybe should be processed inside every widget + // if(x > this.width || y > this.height) return; + var painter = this.context.getPaintMaster().getPaintManager(getController(), useImmediateMode); + painter.announceRepaint(x, y, width, height); + + } + + public SizeHint measure() { + var painter = this.context.getPaintMaster().getPaintManager(getController(), useImmediateMode); + return painter.measure(); + } + + public void doPaint() { + var painter = this.context.getPaintMaster().getPaintManager(getController(), useImmediateMode); + painter.doPaint(); + } + + // Shouldn't be null because UI and Widget have the same lifetime + private Widget getController() { + return controller.get(); + } + + public boolean getUseImmediateMode() { + return useImmediateMode; + } + + public WidgetSkin getSkin() { + return skin; + } + +} diff --git a/src/main/java/xueli/gui/WindowSizeProvider.java b/src/main/java/xueli/gui/WindowSizeProvider.java new file mode 100644 index 00000000..7b432153 --- /dev/null +++ b/src/main/java/xueli/gui/WindowSizeProvider.java @@ -0,0 +1,9 @@ +package xueli.gui; + +public interface WindowSizeProvider { + + public int getWidth(); + + public int getHeight(); + +} diff --git a/src/main/java/xueli/gui/driver/FrameBuffer.java b/src/main/java/xueli/gui/driver/FrameBuffer.java new file mode 100644 index 00000000..5f55e6f9 --- /dev/null +++ b/src/main/java/xueli/gui/driver/FrameBuffer.java @@ -0,0 +1,17 @@ +package xueli.gui.driver; + +public interface FrameBuffer { + + public GraphicDriver getGraphicDriver(); + + public int getImageId(); + + public void resize(int width, int height); + + public void release(); + + public int getWidth(); + + public int getHeight(); + +} diff --git a/src/main/java/xueli/gui/driver/GraphicDriver.java b/src/main/java/xueli/gui/driver/GraphicDriver.java new file mode 100644 index 00000000..e15d3bae --- /dev/null +++ b/src/main/java/xueli/gui/driver/GraphicDriver.java @@ -0,0 +1,74 @@ +package xueli.gui.driver; + +import java.awt.Color; +import java.io.IOException; + +import org.lwjgl.utils.vector.Matrix2f; +import org.lwjgl.utils.vector.Matrix3f; +import xueli.game2.resource.Resource; + +public interface GraphicDriver { + + public void clearColor(float r, float g, float b, float a); + + public int registerImage(Resource res) throws IOException; + + public int registerFont(String name, Resource res) throws IOException; + + public void setTextLetterSpacing(float s); + + public float drawFont(float x, float y, float size, String str, int fontId, FontAlign... aligns); + + public void drawFilledRect(float x, float y, float width, float height, FillType type); + + public void drawFilledCircle(float x, float y, float radius, FillType type); + + public void setTexturedPaint(float x, float y, float width, float height, float angle, float alpha, int imageId); + + public void setColor(Color color); + + public void fill(FillType type); + + public void scissorPush(float x, float y, float width, float height); + + public void scissorPop(); + + public void scissorReset(); + + public float measureTextWidth(float size, String text, int fontId); + + public FrameBuffer createFrameBuffer(int width, int height); + + public void pushFrameBuffer(FrameBuffer buffer); + + public void popFrameBuffer(); + + public void pushMatrix(Matrix3f matrix); + + public void popMatrix(); + + public void begin(int width, int height); + + public void finish(); + + default public void drawImage(float x, float y, float width, float height, float alpha, int imageId) { + this.setTexturedPaint(x, y, width, height, 0, alpha, imageId); + this.drawFilledRect(x, y, width, height, FillType.PAINT); + + } + + default public void drawImageCircle(float x, float y, float radius, int imageId, float angle, float alpha) { + this.setTexturedPaint(x - radius, y - radius, radius * 2, radius * 2, angle, alpha, imageId); + this.drawFilledCircle(x, y, radius, FillType.PAINT); + + } + + public static enum FontAlign { + LEFT, CENTER, RIGHT, TOP, MIDDLE, BOTTOM, BASE_LINE; + } + + public static enum FillType { + COLOR, PAINT; + } + +} diff --git a/src/main/java/xueli/gui/layout/AbsoluteLayout.java b/src/main/java/xueli/gui/layout/AbsoluteLayout.java new file mode 100644 index 00000000..2a16748c --- /dev/null +++ b/src/main/java/xueli/gui/layout/AbsoluteLayout.java @@ -0,0 +1,27 @@ +package xueli.gui.layout; + +import xueli.gui.SizeHint; +import xueli.gui.Widget; +import xueli.gui.WidgetGroup; + +// The best layout is "manual" layout (wrong +public class AbsoluteLayout implements LayoutManager { + + @Override + public void addWidget(Widget widget, WidgetGroup parent) { + } + + @Override + public void removeWidget(Widget widget, WidgetGroup parent) { + } + + @Override + public SizeHint measure(WidgetGroup parent) { + return new SizeHint(SizeHint.POLICY_IGNORED, 0, 0); + } + + @Override + public void doLayout(WidgetGroup parent) { + } + +} diff --git a/src/main/java/xueli/gui/layout/LayoutManager.java b/src/main/java/xueli/gui/layout/LayoutManager.java new file mode 100644 index 00000000..e2b4beeb --- /dev/null +++ b/src/main/java/xueli/gui/layout/LayoutManager.java @@ -0,0 +1,17 @@ +package xueli.gui.layout; + +import xueli.gui.SizeHint; +import xueli.gui.Widget; +import xueli.gui.WidgetGroup; + +public interface LayoutManager { + + public void addWidget(Widget widget, WidgetGroup parent); + + public void removeWidget(Widget widget, WidgetGroup parent); + + public SizeHint measure(WidgetGroup parent); + + public void doLayout(WidgetGroup parent); + +} diff --git a/src/main/java/xueli/gui/package-info.java b/src/main/java/xueli/gui/package-info.java new file mode 100644 index 00000000..4761d18d --- /dev/null +++ b/src/main/java/xueli/gui/package-info.java @@ -0,0 +1,15 @@ +/** + * Make a Gui framework by myself because my game engine need this. + * + *

Here some principles are defined explicitly:

+ *
    + *
  • 1. Every widgets should have a interface that play the role of Controller, and the controller can only be an interface to change the model of a controller.
  • + *
  • 2. The painting API should be hidden from the external access, the user can only create a widget at a manager and get its controller. Requests of changing skin should be submitted to the manager.
  • + *
+ * + *

Not all widgets are provided. Only widgets used are provided.

+ * + * To avoid code being removed, a new package "gui2" is used. + * + */ +package xueli.gui; \ No newline at end of file diff --git a/src/main/java/xueli/gui/skin/DefaultRectangleSkin.java b/src/main/java/xueli/gui/skin/DefaultRectangleSkin.java new file mode 100644 index 00000000..4ac5cd96 --- /dev/null +++ b/src/main/java/xueli/gui/skin/DefaultRectangleSkin.java @@ -0,0 +1,39 @@ +package xueli.gui.skin; + +import xueli.gui.PaintMaster; +import xueli.gui.SizeHint; +import xueli.gui.Widget; +import xueli.gui.WidgetSkin; +import xueli.gui.driver.GraphicDriver; +import xueli.gui.driver.GraphicDriver.FillType; +import xueli.gui.widget.Rectangle; + +public class DefaultRectangleSkin implements WidgetSkin { + + public DefaultRectangleSkin() { + } + + @Override + public void install(Widget widget) { + } + + @Override + public SizeHint measure(Widget widget, GraphicDriver graphics) { + return new SizeHint(SizeHint.POLICY_IGNORED, 0, 0); + } + + @Override + public void paint(Widget widget, float x, float y, float width, float height, PaintMaster paintMaster) { + var driver = paintMaster.getDriver(); + + Rectangle w = (Rectangle) widget; + driver.setColor(w.getColor()); + driver.drawFilledRect(x, y, width, height, FillType.COLOR); + + } + + @Override + public void uninstall(Widget widget) { + } + +} diff --git a/src/main/java/xueli/gui/skin/DefaultSkin.java b/src/main/java/xueli/gui/skin/DefaultSkin.java new file mode 100644 index 00000000..eaea3c65 --- /dev/null +++ b/src/main/java/xueli/gui/skin/DefaultSkin.java @@ -0,0 +1,28 @@ +package xueli.gui.skin; + +import xueli.gui.SkinTheme; +import xueli.gui.WidgetGroup; +import xueli.gui.driver.GraphicDriver; +import xueli.gui.widget.Rectangle; + +public class DefaultSkin extends SkinTheme { + + public static final SkinTheme SHARED_INSTANCE = new DefaultSkin(); + + public DefaultSkin() { + registerSkin(WidgetGroup.class, new DefaultWidgetGroupSkin()); + registerSkin(Rectangle.class, new DefaultRectangleSkin()); + + } + + @Override + public void install(GraphicDriver driver) { + + } + + @Override + public void uninstall(GraphicDriver driver) { + + } + +} diff --git a/src/main/java/xueli/gui/skin/DefaultWidgetGroupSkin.java b/src/main/java/xueli/gui/skin/DefaultWidgetGroupSkin.java new file mode 100644 index 00000000..2869f788 --- /dev/null +++ b/src/main/java/xueli/gui/skin/DefaultWidgetGroupSkin.java @@ -0,0 +1,39 @@ +package xueli.gui.skin; + +import xueli.gui.*; +import xueli.gui.driver.GraphicDriver; + +public class DefaultWidgetGroupSkin implements WidgetSkin { + + @Override + public void install(Widget widget) { +// WidgetGroup controller = (WidgetGroup) widget; + + } + + // The name of this method is from Android! + @Override + public SizeHint measure(Widget widget, GraphicDriver graphics) { + WidgetGroup controller = (WidgetGroup) widget; + return controller.getLayoutManager().measure(controller); + } + + @Override + public void paint(Widget widget, float x, float y, float width, float height, PaintMaster paintMaster) { + WidgetGroup controller = (WidgetGroup) widget; + + int count = controller.getWidgetCount(); + for(int i = 0; i < count; i++) { + Widget child = controller.getWidgetFromIndex(i); + paintMaster.drawWidget(child); + } + + } + + @Override + public void uninstall(Widget widget) { +// WidgetGroup controller = (WidgetGroup) widget; + + } + +} diff --git a/src/main/java/xueli/gui/widget/Rectangle.java b/src/main/java/xueli/gui/widget/Rectangle.java new file mode 100644 index 00000000..93d163e3 --- /dev/null +++ b/src/main/java/xueli/gui/widget/Rectangle.java @@ -0,0 +1,46 @@ +package xueli.gui.widget; + +import java.awt.Color; + +import xueli.gui.UIContext; +import xueli.gui.Widget; + +public class Rectangle extends Widget { + +// public static final Identifier IDENTIFIER = new Identifier(MyUIFrameworkInfo.FRAMEWORK_NAME, "rectangle"); + + public static final String PROPERTY_COLOR = "color"; + + private Color color; + + public Rectangle(UIContext ctx) { + this(Color.white, ctx); + + } + + public Rectangle(Color color, UIContext ctx) { + super(ctx); + + setUseImmediateMode(true); + this.color = color; + + this.registerChangeListener(); + + } + + private void registerChangeListener() { + this.registerPropertyChangeListener(e -> this.announceRepaint()); + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + Color oldColor = this.color; + this.color = color; + this.firePropertyChange(PROPERTY_COLOR, oldColor, color); + + } + +} diff --git a/src/main/java/xueli/guiwindows/package-info.java b/src/main/java/xueli/guiwindows/package-info.java new file mode 100644 index 00000000..1d274f7c --- /dev/null +++ b/src/main/java/xueli/guiwindows/package-info.java @@ -0,0 +1,5 @@ +/** + * Now LovelyZeeiam wants to use total Windows API to draw GUI! + * But he can't find drawing methods in JNA. + */ +package xueli.guiwindows; \ No newline at end of file diff --git a/src/main/java/xueli/mcremake/client/BlockIconGenerator.java b/src/main/java/xueli/mcremake/client/BlockIconGenerator.java new file mode 100644 index 00000000..c1857378 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/BlockIconGenerator.java @@ -0,0 +1,100 @@ +package xueli.mcremake.client; + +import java.util.HashMap; +import java.util.function.BiConsumer; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2i; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.math.MatrixHelper; +import xueli.game2.renderer.legacy.FrameBuffer; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.client.renderer.world.ChunkRenderType; +import xueli.mcremake.client.renderer.world.block.BlockRenderTypes; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.registry.GameRegistry; +import xueli.registry.Identifier; + +public class BlockIconGenerator implements ResourceHolder { + + public static final int FRAME_BUFFER_SIZE = 256; + + private static final Matrix4f viewMatrix, projMatrix; + + static { + projMatrix = MatrixHelper.ortho(-0.9f, 0.9f, -0.9f, 0.9f, 0.01f, 1000000.0f); + viewMatrix = MatrixHelper.lookAt(new Vector3f(4 + (float) (Math.random() * 2.0f - 1.0f) * 0.4f, + 3.5f + (float) (Math.random() * 2.0f - 1.0f) * 0.4f, 4 + (float) (Math.random() * 2.0f - 1.0f) * 0.4f), + new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0, -1, 0)); + } + + private final CraftGameClient ctx; + + private final HashMap icons = new HashMap<>(); + + public BlockIconGenerator(CraftGameClient ctx) { + this.ctx = ctx; + + } + + @Override + public void reload() { + this.clear(); + this.genBlockIcons(); + } + + private void clear() { + icons.values().forEach(b -> { + b.delete(); + }); + icons.clear(); + } + + private void genBlockIcons() { + BlockRenderTypes renderTypes = new BlockRenderTypes(ctx); + + for (ChunkRenderType type : renderTypes.values()) { + type.applyMatrix("viewMatrix", viewMatrix); + type.applyMatrix("projMatrix", projMatrix); + } + + GameRegistry.BUILTIN_BLOCK_REGISTRY.getAllContainTag(GameRegistry.TAG_GENERIC_BLOCK).forEach(name -> { + BlockType block = GameRegistry.BUILTIN_BLOCK_REGISTRY.getByName(name); + ChunkRenderBuildManager manager = new ChunkRenderBuildManager(new Vector2i(0, 0)) { + @Override + public T getRenderType(Class clazz) { + return renderTypes.get(clazz); + } + }; + block.renderer().renderIcon(manager); + manager.flip(); + + FrameBuffer frameBuffer = new FrameBuffer(FRAME_BUFFER_SIZE, FRAME_BUFFER_SIZE); + frameBuffer.bind(); + for (ChunkRenderType type : renderTypes.values()) { + type.render(); + type.clear(); + } + frameBuffer.unbind(); +// frameBuffer.save("./" + name.location() + ".png"); + + icons.put(name, frameBuffer); + + }); + + } + + public Texture getTextureId(Identifier loc) { + FrameBuffer frame; + return (frame = icons.get(loc)) == null ? null + : new Texture(frame.getTextureId(), FRAME_BUFFER_SIZE, FRAME_BUFFER_SIZE); + } + + public void ForEach(BiConsumer c) { + icons.forEach((n, f) -> c.accept(n, new Texture(f.getTextureId(), FRAME_BUFFER_SIZE, FRAME_BUFFER_SIZE))); + } + +} diff --git a/src/main/java/xueli/mcremake/client/CraftGameClient.java b/src/main/java/xueli/mcremake/client/CraftGameClient.java new file mode 100644 index 00000000..242f7571 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/CraftGameClient.java @@ -0,0 +1,117 @@ +package xueli.mcremake.client; + +import java.util.UUID; + +import org.lwjgl.glfw.GLFW; + +import xueli.game2.display.GameDisplay; +import xueli.game2.ecs.ResourceListImpl; +import xueli.game2.resource.ResourceHolder; +import xueli.mcremake.client.renderer.item.ItemRenderMaster; +import xueli.mcremake.client.renderer.world.block.BlockRenderTypes; +import xueli.mcremake.client.systems.GameRenderSystem; +import xueli.mcremake.client.systems.ItemTypeSelectSystem; +import xueli.mcremake.client.systems.KeyBindingUpdateSystem; +import xueli.mcremake.client.systems.PlayerUpdateSystem; +import xueli.mcremake.core.world.WorldDimension; +import xueli.mcremake.network.ServerPlayerInfo; +import xueli.mcremake.registry.GameRegistry; +import xueli.mcremake.registry.TerrainTextureAtlas; +import xueli.mcremake.registry.item.ItemRenderTypes; + +/** + * This is the main class of game client.
+ *
+ * SOME PRINCIPLES:
+ * 1. The render resources should be added to "renderResources" so that it can + * be reloaded.
+ * 2. Item system and block system follow the same rules: a render type which + * you can have access to every resource, initialize everything that render + * needs; a "vertex gatherer" separated by each type of blocks or items so that + * every item can choose its render type.
+ * + * TODOs:
+ * 1. When it comes to infinity world we just use ticket-like mechanism (each + * time we iterate the chunks and spread the ticket and load it until it got + * under zero) but at first I should have the chunk generator done. 2. Also set + * 2 state of a chunk, "LOADING", "DONE", determining whether the chunk is ready + * to go 3. Separate BlockInfo and BlockRenderer, and in WorldRenderer class, we + * just attach the certain block renderer to the BlockInfo + * + */ +public class CraftGameClient extends GameDisplay { + + public static final ServerPlayerInfo PLAYER_INFO = new ServerPlayerInfo("LovelyZeeiam", + UUID.fromString("a5538060-b314-4cb0-90cd-ead6c59f16a7")); + + public final GameState state = new GameState(); + final ResourceListImpl renderResources = new ResourceListImpl<>(); + final ResourceListImpl systems = new ResourceListImpl<>(); + + public CraftGameClient() { + super(1280, 720, "Minecraft Classic Forever"); + + } + + @Override + protected void renderInit() { + GameRegistry.callForClazzLoad(); + + state.worldDirect = new WorldDimension(this); + state.world = new ListenableBufferedWorldAccessible(state.worldDirect, eventbus); + + this.renderResources.add(new TerrainTextureAtlas(this)); + this.renderResources.add(new BlockIconGenerator(this)); + this.renderResources.add(new WorldRenderer(new BlockRenderTypes(this), this)); + this.renderResources.add(new ItemRenderMaster(new ItemRenderTypes(renderResources), this)); + + this.systems.add(new KeyBindingUpdateSystem()); + this.systems.add(new PlayerUpdateSystem()); + this.systems.add(new ItemTypeSelectSystem()); + this.systems.add(new GameRenderSystem()); + + this.systems.values().forEach(o -> o.start(this)); + + state.worldDirect.init(); + + this.resourceManager.addResourceHolder(() -> { + this.renderResources.values().forEach(ResourceHolder::reload); + }); + + } + + @Override + protected void render() { + this.display.setMouseGrabbed(true); + + for (int i = 0; i < this.timer.getNumShouldTick(); i++) { + this.systems.values().forEach(o -> o.tick(this)); + this.state.tickCount++; + } + this.state.partialTick = this.timer.getRemainProgress(); + + state.world.flush(); + + this.systems.values().forEach(o -> o.update(this)); + + if (this.state.keyBindings.isPressed(GLFW.GLFW_KEY_ESCAPE)) { + this.announceClose(); + } + + } + + @Override + protected void renderRelease() { + this.renderResources.values().forEach(o -> { + if (o instanceof IGameSystem system) { + system.release(this); + } + }); + + } + + public T getRenderResources(Class clazz) { + return this.renderResources.get(clazz); + } + +} diff --git a/src/main/java/xueli/mcremake/client/CraftGameMain.java b/src/main/java/xueli/mcremake/client/CraftGameMain.java new file mode 100644 index 00000000..e1b0918b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/CraftGameMain.java @@ -0,0 +1,13 @@ +package xueli.mcremake.client; + +import org.lwjgl.system.Library; + +public class CraftGameMain { + + public static void main(String[] args) { + Library.initialize(); + new CraftGameClient().run(); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/GameState.java b/src/main/java/xueli/mcremake/client/GameState.java new file mode 100644 index 00000000..efd47c8b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/GameState.java @@ -0,0 +1,23 @@ +package xueli.mcremake.client; + +import xueli.game2.input.KeyBindings; +import xueli.mcremake.client.state.ClientPlayerState; +import xueli.mcremake.core.item.ItemType; +import xueli.mcremake.core.world.WorldDimension; +import xueli.mcremake.registry.GameRegistry; + +// TODO: maybe each type of game state can also be a component! +public class GameState { + + public int tickCount = 0; + public double partialTick = 0.0; + + public final ClientPlayerState player = new ClientPlayerState(); + public ItemType selectedItemType = GameRegistry.ITEM_BLOCK_DIRT; + + WorldDimension worldDirect; + public ListenableBufferedWorldAccessible world; + + public final KeyBindings keyBindings = new KeyBindings(); + +} diff --git a/src/main/java/xueli/mcremake/client/IGameSystem.java b/src/main/java/xueli/mcremake/client/IGameSystem.java new file mode 100644 index 00000000..a9177099 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/IGameSystem.java @@ -0,0 +1,17 @@ +package xueli.mcremake.client; + +public interface IGameSystem { + + default public void start(CraftGameClient ctx) { + }; + + default public void tick(CraftGameClient ctx) { + }; + + default public void update(CraftGameClient ctx) { + }; + + default public void release(CraftGameClient ctx) { + }; + +} diff --git a/src/main/java/xueli/mcremake/client/ListenableBufferedWorldAccessible.java b/src/main/java/xueli/mcremake/client/ListenableBufferedWorldAccessible.java new file mode 100644 index 00000000..6a96c34e --- /dev/null +++ b/src/main/java/xueli/mcremake/client/ListenableBufferedWorldAccessible.java @@ -0,0 +1,61 @@ +package xueli.mcremake.client; + +import java.util.HashMap; +import java.util.function.Consumer; + +import org.lwjgl.utils.vector.Vector3i; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.client.events.ModifyBlockEvent; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.world.BufferedWorldAccessible; +import xueli.mcremake.core.world.WorldAccessible; +import xueli.utils.events.EventBus; + +public class ListenableBufferedWorldAccessible extends BufferedWorldAccessible { + + private final EventBus bus; + + private HashMap blockChangeList = new HashMap<>(); + + public ListenableBufferedWorldAccessible(WorldAccessible world, EventBus eventBus) { + super(world); + this.bus = eventBus; + + } + + public BlockType getBlockImmediate(int x, int y, int z) { + BlockType changed; + if ((changed = blockChangeList.get(new Vector3i(x, y, z))) != null) + return changed; + return super.getBlock(x, y, z); + } + + @Override + public void setBlock(int x, int y, int z, BlockType block) { + blockChangeList.put(new Vector3i(x, y, z), block); + commandBuffer.add(() -> { + world.setBlock(x, y, z, block); + bus.post(new ModifyBlockEvent(x, y, z)); + }); + + } + + @Override + public void modifyBlockTag(int x, int y, int z, Consumer c) { + commandBuffer.add(() -> { + world.modifyBlockTag(x, y, z, c); + bus.post(new ModifyBlockEvent(x, y, z)); + }); + + } + + @Override + public void flush() { + blockChangeList.clear(); + super.flush(); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/WorldRenderer.java b/src/main/java/xueli/mcremake/client/WorldRenderer.java new file mode 100644 index 00000000..da237fda --- /dev/null +++ b/src/main/java/xueli/mcremake/client/WorldRenderer.java @@ -0,0 +1,176 @@ +package xueli.mcremake.client; + +import java.util.TreeSet; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2i; + +import xueli.game2.camera3d.BoundCamera; +import xueli.game2.camera3d.ICamera; +import xueli.game2.display.Display; +import xueli.game2.ecs.ResourceListGeneric; +import xueli.game2.math.Frustum; +import xueli.game2.math.MatrixHelper; +import xueli.game2.renderer.legacy.RenderBuffer; +import xueli.game2.resource.ResourceHolder; +import xueli.mcremake.client.events.ModifyBlockEvent; +import xueli.mcremake.client.events.NewChunkEvent; +import xueli.mcremake.client.events.UnloadChunkEvent; +import xueli.mcremake.client.renderer.world.ChunkRebuiltTask; +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.client.renderer.world.ChunkRenderType; +import xueli.mcremake.core.world.Chunk; +import xueli.mcremake.core.world.WorldDimension; + +/** + * Sort the vertex: First store the vertex, then calculate its indices + */ +public class WorldRenderer implements ResourceHolder, AutoCloseable { + + private final CraftGameClient ctx; + + private final WorldDimension world; + + private final TreeSet chunkRebuiltList = new TreeSet<>(); + private final TreeSet chunkRemoveList = new TreeSet<>(); + + private final ResourceListGeneric renderTypes; + + private BoundCamera camera = new BoundCamera(null); + private Matrix4f viewMatrix, projMatrix; + + public WorldRenderer(ResourceListGeneric renderTypes, CraftGameClient ctx) { + this.ctx = ctx; + this.world = ctx.state.worldDirect; + + ctx.eventbus.register(NewChunkEvent.class, this::onCreateNewChunk); + ctx.eventbus.register(ModifyBlockEvent.class, this::onModifyBlock); + ctx.eventbus.register(UnloadChunkEvent.class, this::onRemoveChunk); + + this.renderTypes = renderTypes; + + } + + @Override + public void reload() { + } + + public void setCamera(ICamera camera) { + this.camera.setCamera(camera); + } + + private void onCreateNewChunk(NewChunkEvent event) { + chunkRebuiltList.add(new Vector2i(event.x(), event.z())); + + } + + private void onModifyBlock(ModifyBlockEvent event) { + Vector2i inChunkPos = new Vector2i(); + Vector2i chunkPos = Chunk.toChunkPos(event.x(), event.z(), inChunkPos); + chunkRebuiltList.add(chunkPos); + + if (inChunkPos.x == 0) { + chunkRebuiltList.add(new Vector2i(chunkPos.x - 1, chunkPos.y)); + } else if (inChunkPos.x == Chunk.CHUNK_SIZE - 1) { + chunkRebuiltList.add(new Vector2i(chunkPos.x + 1, chunkPos.y)); + } + + if (inChunkPos.y == 0) { + chunkRebuiltList.add(new Vector2i(chunkPos.x, chunkPos.y - 1)); + } else if (inChunkPos.y == Chunk.CHUNK_SIZE - 1) { + chunkRebuiltList.add(new Vector2i(chunkPos.x, chunkPos.y + 1)); + } + + } + + private void onRemoveChunk(UnloadChunkEvent event) { + chunkRemoveList.add(new Vector2i(event.x(), event.z())); + } + + private void doTasks() { + Vector2i v; + v = chunkRebuiltList.pollFirst(); + if (v != null) { + for (ChunkRenderType type : renderTypes.values()) { + RenderBuffer buf = type.getRenderBuffer(v); + buf.clear(); + } + + Chunk chunk = world.getChunk(v.x, v.y); + if (chunk != null) { + final Vector2i fv = v; + // Will flash if async, why? +// CompletableFuture.supplyAsync(() -> { + ChunkRenderBuildManager manager = new ChunkRenderBuildManager(fv) { + @Override + public T getRenderType(Class clazz) { + return renderTypes.get(clazz); + } + }; + new ChunkRebuiltTask(fv.x, fv.y, chunk, world, manager).run(); +// return manager; +// }, ctx.getAsyncExecutor()).thenAcceptAsync(manager -> { + manager.flip(); +// }, ctx.getMainThreadExecutor()); + } + } + + v = chunkRemoveList.pollFirst(); + if (v != null) { + for (ChunkRenderType type : renderTypes.values()) { + type.releaseRenderBuffer(v); + } + } + + } + + public void render() { +// for(int i = 0; i < 10; i++) { + this.doTasks(); +// } + + Display display = ctx.getDisplay(); + + this.viewMatrix = camera.getCameraMatrix(); + this.projMatrix = MatrixHelper.perspective(display.getWidth(), display.getHeight(), 110.0f, 0.01f, 99999.9f); // Really + // shouldn't + // make + // "99999" + // larger, + // or + // in + // frustum + // culling + // it + // will + // throw + // an + // NAN + Frustum frustum = new Frustum(projMatrix, viewMatrix); // New allocation seems like a much costing time, so we + // should make something like object pool + + for (ChunkRenderType type : renderTypes.values()) { + type.applyMatrix("viewMatrix", viewMatrix); + type.applyMatrix("projMatrix", projMatrix); + type.cullRender(frustum); + } + + } + + @Override + public void close() throws Exception { + for (Object type : renderTypes.values()) { + ((ChunkRenderType) type).release(); + } + + } + + public Matrix4f getViewMatrix() { + return viewMatrix; + } + + public Matrix4f getProjMatrix() { + return projMatrix; + } + +} diff --git a/src/main/java/xueli/mcremake/client/events/ModifyBlockEvent.java b/src/main/java/xueli/mcremake/client/events/ModifyBlockEvent.java new file mode 100644 index 00000000..3ced9ca3 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/events/ModifyBlockEvent.java @@ -0,0 +1,4 @@ +package xueli.mcremake.client.events; + +public record ModifyBlockEvent(int x, int y, int z) { +} \ No newline at end of file diff --git a/src/main/java/xueli/mcremake/client/events/NewChunkEvent.java b/src/main/java/xueli/mcremake/client/events/NewChunkEvent.java new file mode 100644 index 00000000..05419a7b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/events/NewChunkEvent.java @@ -0,0 +1,4 @@ +package xueli.mcremake.client.events; + +public record NewChunkEvent(int x, int z) { +} \ No newline at end of file diff --git a/src/main/java/xueli/mcremake/client/events/UnloadChunkEvent.java b/src/main/java/xueli/mcremake/client/events/UnloadChunkEvent.java new file mode 100644 index 00000000..640f58d1 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/events/UnloadChunkEvent.java @@ -0,0 +1,4 @@ +package xueli.mcremake.client.events; + +public record UnloadChunkEvent(int x, int z) { +} \ No newline at end of file diff --git a/src/main/java/xueli/mcremake/client/gui/universal/UniversalBackgroundRenderer.java b/src/main/java/xueli/mcremake/client/gui/universal/UniversalBackgroundRenderer.java new file mode 100644 index 00000000..76d07c83 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/gui/universal/UniversalBackgroundRenderer.java @@ -0,0 +1,88 @@ +package xueli.mcremake.client.gui.universal; + +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.renderer.legacy.BackRenderBuffer; +import xueli.game2.renderer.legacy.RenderBuffer; +import xueli.game2.resource.ReloadableResourceTicket; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.renderer.gui.MyRenderBuffer2D; +import xueli.mcremake.client.renderer.gui.RenderTypeTexture2D; +import xueli.registry.Identifier; + +/** + * Sorry but we have to give up this for some time + */ +@Deprecated +public class UniversalBackgroundRenderer implements ResourceHolder { + + public static final Identifier UNIVERSAL_BACKGROUND_RESOURCE_LOCATION = new Identifier("minecraft", + "gui/background.png"); + public static final int BG_SIZE = 128; + public static final Vector3f BG_COLOR = new Vector3f(0.3f, 0.3f, 0.3f); + + private ReloadableResourceTicket texUniversalBgId; + + private final CraftGameClient ctx; + + private final RenderTypeTexture2D renderType = new RenderTypeTexture2D(); + private RenderBuffer renderBuffer; + + public UniversalBackgroundRenderer(CraftGameClient ctx) { + this.ctx = ctx; + + } + + private BackRenderBuffer backBuf; + + public void init() { + this.texUniversalBgId = ctx.textureResource.register(UNIVERSAL_BACKGROUND_RESOURCE_LOCATION, false); + this.reload(); + + } + + public void draw(float x, float y, float width, float height) { + float u1 = width / BG_SIZE; + float v1 = height / BG_SIZE; + + Vector2f leftBottom = new Vector2f(0, 0); + Vector2f rightBottom = new Vector2f(u1, 0); + Vector2f leftTop = new Vector2f(0, v1); + Vector2f rightTop = new Vector2f(u1, v1); + + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_VERTEX, new Vector2f(x, y), new Vector2f(x + width, y), + new Vector2f(x, y + height)); + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_UV, leftTop, rightTop, leftBottom); + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_COLOR, BG_COLOR, BG_COLOR, BG_COLOR); + + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_VERTEX, new Vector2f(x + width, y + height), + new Vector2f(x + width, y), new Vector2f(x, y + height)); + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_UV, rightBottom, rightTop, leftBottom); + backBuf.applyToBuffer(MyRenderBuffer2D.ATTR_COLOR, BG_COLOR, BG_COLOR, BG_COLOR); + + } + + public void tick() { + backBuf.flip(); + + renderType.setDisplayDimension(ctx.getWidth(), ctx.getHeight()); + renderType.render(); + + backBuf = renderBuffer.createBackBuffer(); + + } + + @Override + public void reload() { + if (this.renderBuffer != null) { + this.renderBuffer.release(); + } + renderBuffer = renderType.getRenderBuffer(this.texUniversalBgId.get().id()); + backBuf = renderBuffer.createBackBuffer(); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/player/AttackButtonHandler.java b/src/main/java/xueli/mcremake/client/player/AttackButtonHandler.java new file mode 100644 index 00000000..79e3b127 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/player/AttackButtonHandler.java @@ -0,0 +1,53 @@ +package xueli.mcremake.client.player; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.utils.vector.Vector3i; + +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.core.entity.PickResult; + +public class AttackButtonHandler extends FunctionalKeyHandler { + + public AttackButtonHandler(CraftGameClient ctx) { + super(ctx, ctx.state.keyBindings.getKeyBinding(GLFW.GLFW_MOUSE_BUTTON_LEFT)); + + } + + private int blockBreakCooldown = 0; + + @Override + protected void functionStart(CraftGameClient ctx) { + this.doAttack(ctx); + } + + @Override + protected void functionContinue(CraftGameClient ctx) { + // TODO: In creative mode, when the player is faster, its break cooldown should + // be faster. So is it when placing blocks. + // TODO: When we can aim at an entity, the block break cooldown should not be + // updated. + blockBreakCooldown += ctx.timer.getNumShouldTick(); // TODO: should use tick + if (blockBreakCooldown > 6) { + blockBreakCooldown = 0; + this.doAttack(ctx); + } + + } + + private void doAttack(CraftGameClient ctx) { + PickResult pick = ctx.state.player.pickResult; + if (pick != null) { + Vector3i pickBlock = pick.blockPos(); + ctx.state.world.setBlock(pickBlock.x, pickBlock.y, pickBlock.z, null); + + } + + } + + @Override + protected void functionEnd(CraftGameClient ctx) { + blockBreakCooldown = 0; + + } + +} diff --git a/src/main/java/xueli/mcremake/client/player/FunctionalKeyHandler.java b/src/main/java/xueli/mcremake/client/player/FunctionalKeyHandler.java new file mode 100644 index 00000000..2769daa7 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/player/FunctionalKeyHandler.java @@ -0,0 +1,49 @@ +package xueli.mcremake.client.player; + +import xueli.game2.input.KeyBindings.KeyBinding; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.IGameSystem; + +public abstract class FunctionalKeyHandler implements IGameSystem { + + protected final CraftGameClient ctx; + private KeyBinding keyBinding; + + public FunctionalKeyHandler(CraftGameClient ctx, KeyBinding keyBinding) { + this.ctx = ctx; + this.keyBinding = keyBinding; + } + + public void setKeyBinding(KeyBinding keyBinding) { + this.keyBinding = keyBinding; + } + + private boolean lastTimePressed = false; + + /** + * @return Whether this method need another invoke + */ + public void tick(CraftGameClient ctx) { + while (keyBinding.consumeClick()) { + this.functionStart(ctx); + return; + } + + boolean thisTimePressed = keyBinding.isPressed(); + if (thisTimePressed) { + this.functionContinue(ctx); + } else if (this.lastTimePressed) { + this.functionEnd(ctx); + } + + this.lastTimePressed = thisTimePressed; + + } + + protected abstract void functionStart(CraftGameClient ctx); + + protected abstract void functionContinue(CraftGameClient ctx); + + protected abstract void functionEnd(CraftGameClient ctx); + +} diff --git a/src/main/java/xueli/mcremake/client/player/UseButtonHandler.java b/src/main/java/xueli/mcremake/client/player/UseButtonHandler.java new file mode 100644 index 00000000..99bfa9e3 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/player/UseButtonHandler.java @@ -0,0 +1,54 @@ +package xueli.mcremake.client.player; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.utils.vector.Vector3i; + +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.core.entity.PickResult; +import xueli.mcremake.registry.GameRegistry; +import xueli.registry.Identifier; + +public class UseButtonHandler extends FunctionalKeyHandler { + + public UseButtonHandler(CraftGameClient ctx) { + super(ctx, ctx.state.keyBindings.getKeyBinding(GLFW.GLFW_MOUSE_BUTTON_RIGHT)); + } + + private int blockPlaceCooldown = 0; + + @Override + protected void functionStart(CraftGameClient ctx) { + this.doUse(ctx); + } + + @Override + protected void functionContinue(CraftGameClient ctx) { + blockPlaceCooldown += ctx.timer.getNumShouldTick(); + if (blockPlaceCooldown > 6) { + blockPlaceCooldown = 0; + this.doUse(ctx); + } + + } + + private void doUse(CraftGameClient ctx) { + PickResult pick = ctx.state.player.pickResult; + if (pick != null) { + Vector3i pickBlock = pick.placePos(); + Identifier itemBlock = GameRegistry.BUILTIN_ITEM_BLOCK_MAP_REGISTRY + .getByName(ctx.state.selectedItemType.namespace()); + if (itemBlock != null) { + ctx.state.world.setBlock(pickBlock.x, pickBlock.y, pickBlock.z, + GameRegistry.BUILTIN_BLOCK_REGISTRY.getByName(itemBlock)); + } + } + + } + + @Override + protected void functionEnd(CraftGameClient ctx) { + blockPlaceCooldown = 0; + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/gui/MyRenderBuffer2D.java b/src/main/java/xueli/mcremake/client/renderer/gui/MyRenderBuffer2D.java new file mode 100644 index 00000000..2d2fa120 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/gui/MyRenderBuffer2D.java @@ -0,0 +1,23 @@ +package xueli.mcremake.client.renderer.gui; + +import xueli.game2.renderer.legacy.VertexAttributeRenderBuffer; +import xueli.game2.renderer.legacy.VertexType; +import xueli.game2.renderer.legacy.shape.ShapeType; + +public class MyRenderBuffer2D extends VertexAttributeRenderBuffer { + + public static final int ATTR_VERTEX = 0; + public static final int ATTR_UV = 1; + public static final int ATTR_COLOR = 2; + + public MyRenderBuffer2D() { + super(ShapeType.TRIANGLES); + this.attr.bind(() -> { + this.attr.addAttributeBuffer(ATTR_VERTEX, 2, VertexType.FLOAT); + this.attr.addAttributeBuffer(ATTR_UV, 2, VertexType.FLOAT); + this.attr.addAttributeBuffer(ATTR_COLOR, 3, VertexType.FLOAT); + }); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/gui/RenderTypeTexture2D.java b/src/main/java/xueli/mcremake/client/renderer/gui/RenderTypeTexture2D.java new file mode 100644 index 00000000..6199d7df --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/gui/RenderTypeTexture2D.java @@ -0,0 +1,98 @@ +package xueli.mcremake.client.renderer.gui; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL13.GL_TEXTURE0; +import static org.lwjgl.opengl.GL13.glActiveTexture; + +import java.util.function.Predicate; + +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2f; + +import xueli.game2.renderer.legacy.RenderType; +import xueli.game2.resource.submanager.render.shader.Shader; + +public class RenderTypeTexture2D extends RenderType { + + private static final String VERT_SHADER_CODE = """ + #version 330 + + layout (location = 0) in vec2 pos; + layout (location = 1) in vec2 texPos; + layout (location = 2) in vec3 color; + + uniform vec2 displayDimension; + + out vec2 otexPos; + out vec3 ocolor; + + void main(){ + vec2 normalPos = vec2(mix(-1.0, 1.0, pos.x / displayDimension.x), mix(1.0, -1.0, pos.y / displayDimension.y)); + gl_Position = vec4(normalPos, 0.0, 1.0); + otexPos = texPos; + ocolor = color; + + } + """; + + private static final String FRAG_SHADER_CODE = """ + #version 330 + + in vec2 otexPos; + in vec3 ocolor; + + uniform sampler2D texSampler; + + out vec4 out_color; + + void main(){ + vec4 ambient = texture(texSampler, otexPos); + if(ambient.w == 0) discard; + + out_color = ambient * vec4(ocolor, 1.0); + + } + + """; + + private Shader shader; + + public RenderTypeTexture2D() { + super(i -> new MyRenderBuffer2D()); + this.shader = Shader.compile(VERT_SHADER_CODE, FRAG_SHADER_CODE); + + } + + @Override + public void render(Predicate selector) { + this.shader.bind(); + buffers.forEach((i, a) -> { + if (selector.test(i)) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, i); + a.render(); + glBindTexture(GL_TEXTURE_2D, 0); + } + }); + this.shader.unbind(); + } + + @Override + public void applyMatrix(String name, Matrix4f matrix) { + } + + public void setDisplayDimension(float width, float height) { + this.shader.bind(); + shader.setUniformVector2f(shader.getUnifromLocation("displayDimension"), new Vector2f(width, height)); + this.shader.unbind(); + + } + + @Override + public void doRelease() { + this.shader.release(); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderManager.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderManager.java new file mode 100644 index 00000000..d739bb3f --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderManager.java @@ -0,0 +1,7 @@ +package xueli.mcremake.client.renderer.item; + +public interface ItemRenderManager { + + public abstract T getRenderType(Class clazz); + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderMaster.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderMaster.java new file mode 100644 index 00000000..d6e94e44 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderMaster.java @@ -0,0 +1,41 @@ +package xueli.mcremake.client.renderer.item; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.ecs.ResourceListGeneric; +import xueli.game2.renderer.ui.NanoGui; +import xueli.game2.resource.ResourceHolder; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.core.item.ItemType; + +public class ItemRenderMaster implements ResourceHolder, ItemRenderManager { + + @SuppressWarnings("unused") + private final CraftGameClient ctx; + private final ResourceListGeneric renderTypes; + + public ItemRenderMaster(ResourceListGeneric renderTypes, CraftGameClient ctx) { + this.ctx = ctx; + this.renderTypes = renderTypes; + + } + + public void renderUI(ItemType item, CompoundMap tags, float x, float y, float width, float height, NanoGui gui) { + ItemVertexGatherer renderer = item.renderer(); + if (renderer != null) { + renderer.renderUI(tags, x, y, width, height, this, gui); + } + + } + + @Override + public T getRenderType(Class clazz) { + return renderTypes.get(clazz); + } + + @Override + public void reload() { + renderTypes.values().forEach(ItemRenderType::reload); + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderTarget.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderTarget.java new file mode 100644 index 00000000..15fb6aab --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderTarget.java @@ -0,0 +1,13 @@ +package xueli.mcremake.client.renderer.item; + +public interface ItemRenderTarget { + + public void bind(); + + public void unbind(); + + public int getWidth(); + + public int getHeight(); + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderType.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderType.java new file mode 100644 index 00000000..89350fc2 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemRenderType.java @@ -0,0 +1,11 @@ +package xueli.mcremake.client.renderer.item; + +import xueli.game2.resource.ResourceHolder; + +public interface ItemRenderType extends ResourceHolder { + +// public void render(CompoundMap tags, float x, float y, float width, float height, ItemRenderManager manager); + + public void release(); + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemStack.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemStack.java new file mode 100644 index 00000000..b1e595b9 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemStack.java @@ -0,0 +1,39 @@ +package xueli.mcremake.client.renderer.item; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.core.item.ItemType; + +public class ItemStack { + + private final ItemType type; + private int count; + private final CompoundMap tag = new CompoundMap(); + + public ItemStack(ItemType type, int count) { + this.type = type; + this.count = count; + } + + public ItemType getType() { + return type; + } + + public CompoundMap getTag() { + return tag; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public String toString() { + return "ItemStack [type=" + type + ", count=" + count + ", tag=" + tag + "]"; + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/item/ItemVertexGatherer.java b/src/main/java/xueli/mcremake/client/renderer/item/ItemVertexGatherer.java new file mode 100644 index 00000000..a65e89d9 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/item/ItemVertexGatherer.java @@ -0,0 +1,12 @@ +package xueli.mcremake.client.renderer.item; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.renderer.ui.NanoGui; + +public interface ItemVertexGatherer { + + public void renderUI(CompoundMap tags, float x, float y, float width, float height, ItemRenderManager manager, + NanoGui gui); + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/ChunkRebuiltTask.java b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRebuiltTask.java new file mode 100644 index 00000000..49b95b0c --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRebuiltTask.java @@ -0,0 +1,49 @@ +package xueli.mcremake.client.renderer.world; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.client.renderer.world.block.BlockVertexGatherer; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.world.Chunk; +import xueli.mcremake.core.world.WorldAccessible; + +public class ChunkRebuiltTask { + + private final int x, z; + private final WorldAccessible world; + private final Chunk chunk; + private final ChunkRenderBuildManager manager; + + public ChunkRebuiltTask(int x, int z, Chunk chunk, WorldAccessible world, ChunkRenderBuildManager manager) { + this.x = x; + this.z = z; + this.chunk = chunk; + this.world = world; + this.manager = manager; + + } + + public void run() { + for (int i = 0; i < Chunk.CHUNK_SIZE; i++) { + for (int j = 0; j < Chunk.CHUNK_SIZE; j++) { + for (int k = 0; k < Chunk.SUB_CHUNK_LAYER_COUNT; k++) { + int maxHeight = chunk.getMaxHeight(i, j, k); + for (int l = 0; l <= maxHeight; l++) { + int realHeight = k * Chunk.SUB_CHUNK_HEIGHT + l; + BlockType block = chunk.getBlock(i, realHeight, j); + if (block == null) + continue; + CompoundMap tag = chunk.getBlockTag(i, realHeight, j); + BlockVertexGatherer renderer = block.renderer(); + if (renderer == null) + continue; + renderer.render(this.x * Chunk.CHUNK_SIZE + i, realHeight, this.z * Chunk.CHUNK_SIZE + j, tag, + world, manager); + } + } + } + } + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderBuildManager.java b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderBuildManager.java new file mode 100644 index 00000000..ed13b7a4 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderBuildManager.java @@ -0,0 +1,31 @@ +package xueli.mcremake.client.renderer.world; + +import java.util.HashMap; + +import org.lwjgl.utils.vector.Vector2i; + +import xueli.game2.renderer.legacy.BackRenderBuffer; + +public abstract class ChunkRenderBuildManager { + + private final Vector2i chunkPos; + private final HashMap, BackRenderBuffer> backBuffers = new HashMap<>(); + + public ChunkRenderBuildManager(Vector2i chunkPos) { + this.chunkPos = chunkPos; + } + + public BackRenderBuffer getRenderBuffer(Class clazz) { + return backBuffers.computeIfAbsent(clazz, c -> { + ChunkRenderType type = this.getRenderType(clazz); + return type.getRenderBuffer(chunkPos).createBackBuffer(); + }); + } + + public abstract T getRenderType(Class clazz); + + public void flip() { + backBuffers.values().forEach(BackRenderBuffer::flip); + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderType.java b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderType.java new file mode 100644 index 00000000..a7f87a64 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/ChunkRenderType.java @@ -0,0 +1,29 @@ +package xueli.mcremake.client.renderer.world; + +import java.util.function.Function; + +import org.lwjgl.utils.vector.Vector2i; + +import xueli.game2.math.Frustum; +import xueli.game2.renderer.legacy.RenderBuffer; +import xueli.game2.renderer.legacy.RenderType; +import xueli.mcremake.core.world.Chunk; + +public abstract class ChunkRenderType extends RenderType { + + public ChunkRenderType() { + super(v -> new MyRenderBuffer()); + } + + public ChunkRenderType(Function bufferSupplier) { + super(bufferSupplier); + } + + public void cullRender(Frustum frustum) { + this.render(v -> { + return frustum.isCubeInFrustum(v.x * 16, 0, v.y * 16, (v.x + 1) * 16, Chunk.CHUNK_HEIGHT, (v.y + 1) * 16); + }); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBuffer.java b/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBuffer.java new file mode 100644 index 00000000..32cf6cac --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBuffer.java @@ -0,0 +1,22 @@ +package xueli.mcremake.client.renderer.world; + +import xueli.game2.renderer.legacy.VertexAttributeRenderBuffer; +import xueli.game2.renderer.legacy.VertexType; +import xueli.game2.renderer.legacy.shape.ShapeType; + +public class MyRenderBuffer extends VertexAttributeRenderBuffer { + + public static final int ATTR_VERTEX = 0; + public static final int UV_VERTEX = 1; + public static final int COLOR_VERTEX = 2; + + public MyRenderBuffer() { + super(ShapeType.TRIANGLES); + this.attr.bind(() -> { + this.attr.addAttributeBuffer(ATTR_VERTEX, 3, VertexType.FLOAT); + this.attr.addAttributeBuffer(UV_VERTEX, 2, VertexType.FLOAT); + this.attr.addAttributeBuffer(COLOR_VERTEX, 3, VertexType.FLOAT); + }); + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBufferSortedDistance.java b/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBufferSortedDistance.java new file mode 100644 index 00000000..c7ff90b6 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/MyRenderBufferSortedDistance.java @@ -0,0 +1,130 @@ +package xueli.mcremake.client.renderer.world; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import xueli.game2.Vector; +import xueli.game2.renderer.legacy.buffer.ElementBuffer; +import xueli.utils.buffer.LotsOfByteBuffer; + +public class MyRenderBufferSortedDistance extends MyRenderBuffer { + + private final ElementBuffer ebo = new ElementBuffer(); + private LotsOfByteBuffer elementBuffer; + + @Override + public void setVertexCount(int count) { + super.setVertexCount(count); + + if (elementBuffer != null) { + elementBuffer.release(); + } + elementBuffer = new LotsOfByteBuffer(count * Integer.BYTES); + ebo.updateBuffer(elementBuffer); + + } + + public void renderSorted(Vector camVector) { +// int vertCount = this.getVertCount(); +// AttributeBuffer vertBuffer = this.attr.getAttributeBuffer(ATTR_VERTEX); +// var latestVertBuffer = vertBuffer.getLatestBuffer(); +// if(latestVertBuffer != null) { +// FloatBuffer vertRawBuf = latestVertBuffer.asFloatBuffer(); +// +// elementBuffer.setReadWrite(false); +// elementBuffer.clear(); +// SortTask.genSortedElementBuffer(vertRawBuf, vertCount, elementBuffer); +// elementBuffer.setReadWrite(true); +// ebo.updateBuffer(elementBuffer); +// +// ebo.tick(); +// this.attr.renderElement(ebo, vertCount); +// } + this.render(); + + } + + @Override + public void release() { + ebo.release(); + super.release(); + + } + +} + +class SortTask { + + public static void genSortedElementBuffer(FloatBuffer vertRawBuf, int vertCount, LotsOfByteBuffer elementBuffer) { + int triangleCount = vertCount / 3; + + for (int i = 0; i < vertCount; i++) { + elementBuffer.putInt(i); + } + ArrayList selectedList = new ArrayList<>(); + for (int i = 0; i < triangleCount; i++) { + selectedList.add(i); + writeElementBuffer(i, elementBuffer); + } + sortElementBuffer(vertRawBuf, selectedList, elementBuffer); + + } + +// public static void dumpElementBuffer(LotsOfByteBuffer buf) { +// IntBuffer raw = buf.getBuffer().asIntBuffer(); +// System.out.print("["); +// for(int i = 0; i < raw.limit(); i++) { +// System.out.print(raw.get(i)); +// System.out.print(", "); +// } +// System.out.println("]"); +// +// } + + private static void sortElementBuffer(FloatBuffer vertRawBuf, List selected, + LotsOfByteBuffer elementBuffer) { + if (selected.size() < 1) + return; + if (selected.size() == 1) { + writeElementBuffer(selected.get(0), elementBuffer); + return; + } + + double middle = getDistance(vertRawBuf, selected.get(0)); + ArrayList left = new ArrayList<>(); + ArrayList right = new ArrayList<>(); + + for (int i = 1; i < selected.size(); i++) { + double val = getDistance(vertRawBuf, selected.get(i)); + if (val < middle) { + left.add(selected.get(i)); + } else { + right.add(selected.get(i)); + } + } + + sortElementBuffer(vertRawBuf, left, elementBuffer); + writeElementBuffer(selected.get(0), elementBuffer); + sortElementBuffer(vertRawBuf, right, elementBuffer); + + } + + private static void writeElementBuffer(int i, LotsOfByteBuffer buf) { + buf.putInt(i * 3); + buf.putInt(i * 3 + 1); + buf.putInt(i * 3 + 2); + + } + + private static double getDistance(FloatBuffer vertRawBuf, int offset) { + double d1 = Math.sqrt(Math.pow(vertRawBuf.get(offset * 9), 2) + Math.pow(vertRawBuf.get(offset * 9 + 1), 2) + + Math.pow(vertRawBuf.get(offset * 9 + 2), 2)); + double d2 = Math.sqrt(Math.pow(vertRawBuf.get(offset * 9 + 3), 2) + Math.pow(vertRawBuf.get(offset * 9 + 4), 2) + + Math.pow(vertRawBuf.get(offset * 9 + 5), 2)); + double d3 = Math.sqrt(Math.pow(vertRawBuf.get(offset * 9 + 6), 2) + Math.pow(vertRawBuf.get(offset * 9 + 7), 2) + + Math.pow(vertRawBuf.get(offset * 9 + 8), 2)); + return d1 + d2 + d3; + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeAlpha.java b/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeAlpha.java new file mode 100644 index 00000000..4ef2ee8b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeAlpha.java @@ -0,0 +1,41 @@ +package xueli.mcremake.client.renderer.world; + +import java.util.function.Predicate; + +import org.lwjgl.opengl.GL30; +import org.lwjgl.utils.vector.Vector2i; + +import xueli.mcremake.client.CraftGameClient; + +public class RenderTypeAlpha extends RenderTypeSolid { + + public RenderTypeAlpha(CraftGameClient ctx) { + super(v -> new MyRenderBufferSortedDistance(), ctx); + } + + @Override + public void render(Predicate selector) { + GL30.glEnable(GL30.GL_BLEND); + GL30.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + GL30.glEnable(GL30.GL_DEPTH_TEST); // TODO: Duplicate source! How to simplify them? + GL30.glEnable(GL30.GL_CULL_FACE); + this.shader.bind(); + this.texture.bind(); + + ctx.checkGLError("Pre-render alpha layer"); + buffers.forEach((t, b) -> { + if (selector.test(t)) { + ((MyRenderBufferSortedDistance) b).renderSorted(ctx.state.player.positionOnRender); + } + }); + ctx.checkGLError("Post-render alpha layer"); + + this.texture.unbind(); + this.shader.unbind(); + GL30.glDisable(GL30.GL_DEPTH_TEST); + GL30.glDisable(GL30.GL_CULL_FACE); + GL30.glDisable(GL30.GL_BLEND); + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeSolid.java b/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeSolid.java new file mode 100644 index 00000000..4491928b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/RenderTypeSolid.java @@ -0,0 +1,111 @@ +package xueli.mcremake.client.renderer.world; + +import java.util.function.Function; +import java.util.function.Predicate; + +import org.lwjgl.opengl.GL30; +import org.lwjgl.utils.vector.Matrix4f; +import org.lwjgl.utils.vector.Vector2i; + +import xueli.game2.renderer.legacy.RenderBuffer; +import xueli.game2.resource.submanager.render.shader.Shader; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.registry.TerrainTextureAtlas; + +public class RenderTypeSolid extends ChunkRenderType { + + private static final String VERT_SHADER_CODE = """ + #version 330 + + layout (location = 0) in vec3 pos; + layout (location = 1) in vec2 texPos; + layout (location = 2) in vec3 color; + + uniform mat4 projMatrix; + uniform mat4 viewMatrix; + + out vec2 otexPos; + out vec3 ocolor; + + void main(){ + vec4 posCam = viewMatrix * vec4(pos, 1.0); + gl_Position = projMatrix * posCam; + otexPos = texPos; + ocolor = color; + + } + """; + + private static final String FRAG_SHADER_CODE = """ + #version 330 + + in vec2 otexPos; + in vec3 ocolor; + + uniform sampler2D texSampler; + + out vec4 out_color; + + void main(){ + vec4 ambient = texture(texSampler, otexPos); + if(ambient.w == 0) discard; + + out_color = ambient * vec4(ocolor, 1.0); + + } + + """; + + protected final CraftGameClient ctx; + protected final Shader shader; + protected final TerrainTextureAtlas texture; + + public RenderTypeSolid(CraftGameClient ctx) { + this(v -> new MyRenderBuffer(), ctx); + } + + public RenderTypeSolid(Function bufferSupplier, CraftGameClient ctx) { + super(bufferSupplier); + this.ctx = ctx; + this.shader = Shader.compile(VERT_SHADER_CODE, FRAG_SHADER_CODE); + this.texture = ctx.getRenderResources(TerrainTextureAtlas.class); + + } + + @Override + public void render(Predicate selector) { + GL30.glEnable(GL30.GL_DEPTH_TEST); + GL30.glEnable(GL30.GL_CULL_FACE); + this.shader.bind(); + this.texture.bind(); + super.render(selector); + this.texture.unbind(); + this.shader.unbind(); + GL30.glDisable(GL30.GL_DEPTH_TEST); + GL30.glDisable(GL30.GL_CULL_FACE); + + } + + @Override + public void applyMatrix(String name, Matrix4f matrix) { + if (this.shader.isBound()) { + shader.setUniformMatrix(shader.getUnifromLocation(name), matrix); + } else { + shader.bind(); + shader.setUniformMatrix(shader.getUnifromLocation(name), matrix); + shader.unbind(); + } + + } + + @Override + public void doRelease() { + this.shader.release(); + + } + + public TerrainTextureAtlas getTexture() { + return texture; + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRenderTypes.java b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRenderTypes.java new file mode 100644 index 00000000..9366cc2b --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRenderTypes.java @@ -0,0 +1,16 @@ +package xueli.mcremake.client.renderer.world.block; + +import xueli.game2.ecs.ResourceListGeneric; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.renderer.world.ChunkRenderType; +import xueli.mcremake.client.renderer.world.RenderTypeAlpha; +import xueli.mcremake.client.renderer.world.RenderTypeSolid; + +public class BlockRenderTypes extends ResourceListGeneric { + + public BlockRenderTypes(CraftGameClient ctx) { + this.add(new RenderTypeSolid(ctx)); + this.add(new RenderTypeAlpha(ctx)); + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererLiquid.java b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererLiquid.java new file mode 100644 index 00000000..375b0ca6 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererLiquid.java @@ -0,0 +1,122 @@ +package xueli.mcremake.client.renderer.world.block; + +import org.lwjgl.utils.vector.Vector3f; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.renderer.legacy.BackRenderBuffer; +import xueli.game2.resource.submanager.render.texture.atlas.AtlasResourceHolder; +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.client.renderer.world.RenderTypeAlpha; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.world.WorldAccessible; +import xueli.mcremake.registry.TerrainTextureAtlas; + +public class BlockRendererLiquid implements BlockVertexGatherer { + + private final int x, y; + + public BlockRendererLiquid(int xInAtlas, int yInAtlas) { + this.x = xInAtlas; + this.y = yInAtlas; + } + + @Override + public void render(int x, int y, int z, CompoundMap tag, WorldAccessible world, ChunkRenderBuildManager manager) { + BackRenderBuffer buffer = manager.getRenderBuffer(RenderTypeAlpha.class); + TerrainTextureAtlas texture = manager.getRenderType(RenderTypeAlpha.class).getTexture(); + AtlasResourceHolder uvVertex = texture.getUVVertex(this.x, this.y); + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y - 1, z))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x + 1, y, z), new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + // Indicate which plane faces the player by figure out its wrap order + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y + 1, z))) { + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z), new Vector3f(x, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.leftBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x - 1, y, z))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z + 1), + new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x + 1, y, z))) { + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y, z - 1))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y + 1, z), new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.leftTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y, z + 1))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftTop(), uvVertex.rightBottom(), uvVertex.rightTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + } + + private boolean shouldRenderFaceOnThisBlock(BlockType block) { + return block == null; + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSideTopBottom.java b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSideTopBottom.java new file mode 100644 index 00000000..79122a56 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSideTopBottom.java @@ -0,0 +1,128 @@ +package xueli.mcremake.client.renderer.world.block; + +import org.lwjgl.utils.vector.Vector3f; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.renderer.legacy.BackRenderBuffer; +import xueli.game2.resource.submanager.render.texture.atlas.AtlasResourceHolder; +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.client.renderer.world.RenderTypeSolid; +import xueli.mcremake.core.world.WorldAccessible; +import xueli.mcremake.registry.TerrainTextureAtlas; + +public class BlockRendererSideTopBottom implements BlockVertexGatherer { + + private final int sideX, sideY; + private final int topX, topY; + private final int bottomX, bottomY; + + public BlockRendererSideTopBottom(int sideX, int sideY, int topX, int topY, int bottomX, int bottomY) { + this.sideX = sideX; + this.sideY = sideY; + this.topX = topX; + this.topY = topY; + this.bottomX = bottomX; + this.bottomY = bottomY; + } + + @Override + public void render(int x, int y, int z, CompoundMap tag, WorldAccessible world, ChunkRenderBuildManager manager) { + BackRenderBuffer buffer = manager.getRenderBuffer(RenderTypeSolid.class); + TerrainTextureAtlas texture = manager.getRenderType(RenderTypeSolid.class).getTexture(); + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x, y - 1, z))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.bottomX, this.bottomY); + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x + 1, y, z), new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + // Indicate which plane faces the player by figure out its wrap order + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + } + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x, y + 1, z))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.topX, this.topY); + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z), new Vector3f(x, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.leftBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + } + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x - 1, y, z))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.sideX, this.sideY); + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z + 1), + new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x + 1, y, z))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.sideX, this.sideY); + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x, y, z - 1))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.sideX, this.sideY); + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y + 1, z), new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.leftTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || BlockRendererSolid.shouldRenderFaceOnThisBlock(world.getBlock(x, y, z + 1))) { + AtlasResourceHolder uvVertex = texture.getUVVertex(this.sideX, this.sideY); + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftTop(), uvVertex.rightBottom(), uvVertex.rightTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSolid.java b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSolid.java new file mode 100644 index 00000000..8bb033b8 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockRendererSolid.java @@ -0,0 +1,124 @@ +package xueli.mcremake.client.renderer.world.block; + +import org.lwjgl.utils.vector.Vector3f; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.renderer.legacy.BackRenderBuffer; +import xueli.game2.resource.submanager.render.texture.atlas.AtlasResourceHolder; +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.client.renderer.world.RenderTypeSolid; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.world.WorldAccessible; +import xueli.mcremake.registry.GameRegistry; +import xueli.mcremake.registry.TerrainTextureAtlas; + +public class BlockRendererSolid implements BlockVertexGatherer { + + private final int x, y; + + public BlockRendererSolid(int xInAtlas, int yInAtlas) { + this.x = xInAtlas; + this.y = yInAtlas; + } + + @Override + public void render(int x, int y, int z, CompoundMap tag, WorldAccessible world, ChunkRenderBuildManager manager) { + BackRenderBuffer buffer = manager.getRenderBuffer(RenderTypeSolid.class); + TerrainTextureAtlas texture = manager.getRenderType(RenderTypeSolid.class).getTexture(); + AtlasResourceHolder uvVertex = texture.getUVVertex(this.x, this.y); + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y - 1, z))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x + 1, y, z), new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + // Indicate which plane faces the player by figure out its wrap order + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.5f, 0.5f, 0.5f), new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(0.5f, 0.5f, 0.5f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y + 1, z))) { + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z), new Vector3f(x, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.leftBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y + 1, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(1, 1, 1), new Vector3f(1, 1, 1), new Vector3f(1, 1, 1)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x - 1, y, z))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x, y + 1, z + 1), + new Vector3f(x, y + 1, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x + 1, y, z))) { + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z + 1)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x + 1, y, z + 1), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightTop(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y, z - 1))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z), new Vector3f(x, y + 1, z), new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightBottom(), uvVertex.rightTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z), new Vector3f(x + 1, y + 1, z), + new Vector3f(x + 1, y, z)); + buffer.applyToBuffer(1, uvVertex.rightTop(), uvVertex.leftTop(), uvVertex.leftBottom()); + buffer.applyToBuffer(2, new Vector3f(0.7f, 0.7f, 0.7f), new Vector3f(0.7f, 0.7f, 0.7f), + new Vector3f(0.7f, 0.7f, 0.7f)); + + } + + if (world == null || shouldRenderFaceOnThisBlock(world.getBlock(x, y, z + 1))) { + buffer.applyToBuffer(0, new Vector3f(x, y, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftBottom(), uvVertex.rightBottom(), uvVertex.leftTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + buffer.applyToBuffer(0, new Vector3f(x, y + 1, z + 1), new Vector3f(x + 1, y, z + 1), + new Vector3f(x + 1, y + 1, z + 1)); + buffer.applyToBuffer(1, uvVertex.leftTop(), uvVertex.rightBottom(), uvVertex.rightTop()); + buffer.applyToBuffer(2, new Vector3f(0.8f, 0.8f, 0.8f), new Vector3f(0.8f, 0.8f, 0.8f), + new Vector3f(0.8f, 0.8f, 0.8f)); + + } + + } + + public static boolean shouldRenderFaceOnThisBlock(BlockType block) { + return block == null || !GameRegistry.BUILTIN_BLOCK_REGISTRY.getTags(block.namespace()) + .contains(GameRegistry.TAG_GENERIC_BLOCK); + } + +} diff --git a/src/main/java/xueli/mcremake/client/renderer/world/block/BlockVertexGatherer.java b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockVertexGatherer.java new file mode 100644 index 00000000..17353689 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/renderer/world/block/BlockVertexGatherer.java @@ -0,0 +1,16 @@ +package xueli.mcremake.client.renderer.world.block; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.client.renderer.world.ChunkRenderBuildManager; +import xueli.mcremake.core.world.WorldAccessible; + +public interface BlockVertexGatherer { + + public void render(int x, int y, int z, CompoundMap tag, WorldAccessible world, ChunkRenderBuildManager manager); + + default public void renderIcon(ChunkRenderBuildManager manager) { + this.render(0, 0, 0, null, null, manager); + } + +} diff --git a/src/main/java/xueli/mcremake/client/state/ClientPlayerState.java b/src/main/java/xueli/mcremake/client/state/ClientPlayerState.java new file mode 100644 index 00000000..36c84edc --- /dev/null +++ b/src/main/java/xueli/mcremake/client/state/ClientPlayerState.java @@ -0,0 +1,20 @@ +package xueli.mcremake.client.state; + +import org.lwjgl.utils.vector.Vector3d; + +import xueli.game2.Vector; +import xueli.mcremake.core.entity.PickResult; + +public class ClientPlayerState { + + public Vector3d lastTickPosition = new Vector3d(); + + public Vector3d position = new Vector3d(0, 100, 0); + public double rotX = 0.0, rotY = 0.0; + public Vector3d velocity = new Vector3d(0, 0, 0); + + public PickResult pickResult; + + public Vector positionOnRender = new Vector(); + +} diff --git a/src/main/java/xueli/mcremake/client/systems/GameRenderSystem.java b/src/main/java/xueli/mcremake/client/systems/GameRenderSystem.java new file mode 100644 index 00000000..7b90fd13 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/systems/GameRenderSystem.java @@ -0,0 +1,51 @@ +package xueli.mcremake.client.systems; + +import org.lwjgl.opengl.GL30; + +import xueli.game2.Vector; +import xueli.game2.camera3d.MovableCamera; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.IGameSystem; +import xueli.mcremake.client.WorldRenderer; + +public class GameRenderSystem implements IGameSystem { + + @Override + public void start(CraftGameClient ctx) { + + } + + @Override + public void update(CraftGameClient ctx) { + GL30.glClearColor(0.7f, 0.7f, 1.0f, 1.0f); + this.renderWorld(ctx); + this.renderHud(ctx); + + } + + private void renderWorld(CraftGameClient ctx) { + Vector renderPos = ctx.state.player.positionOnRender; + + WorldRenderer renderer = ctx.getRenderResources(WorldRenderer.class); + renderer.setCamera(new MovableCamera(renderPos)); + renderer.render(); + + } + + private void renderHud(CraftGameClient ctx) { +// Gui gui = ctx.getGuiManager(); +// gui.begin(ctx.getWidth(), ctx.getHeight()); +// if(ctx.state.selectedItemType != null) { +// ItemRenderMaster renderer = ctx.getRenderResources(ItemRenderMaster.class); +// renderer.renderUI(ctx.state.selectedItemType, null, ctx.getWidth() - 128, 0, 128, 128, gui); +// } +// gui.finish(); + + } + + @Override + public void release(CraftGameClient ctx) { + + } + +} diff --git a/src/main/java/xueli/mcremake/client/systems/ItemTypeSelectSystem.java b/src/main/java/xueli/mcremake/client/systems/ItemTypeSelectSystem.java new file mode 100644 index 00000000..db03e867 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/systems/ItemTypeSelectSystem.java @@ -0,0 +1,33 @@ +package xueli.mcremake.client.systems; + +import org.lwjgl.glfw.GLFW; + +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.IGameSystem; +import xueli.mcremake.registry.GameRegistry; + +public class ItemTypeSelectSystem implements IGameSystem { + + @Override + public void update(CraftGameClient ctx) { + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_1)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_DIRT; + } + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_2)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_GRASS; + } + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_3)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_STONE; + } + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_4)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_BEDROCK; + } + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_5)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_SAND; + } + if (ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_6)) { + ctx.state.selectedItemType = GameRegistry.ITEM_BLOCK_GRAVEL; + } + } + +} diff --git a/src/main/java/xueli/mcremake/client/systems/KeyBindingUpdateSystem.java b/src/main/java/xueli/mcremake/client/systems/KeyBindingUpdateSystem.java new file mode 100644 index 00000000..ebb6cf34 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/systems/KeyBindingUpdateSystem.java @@ -0,0 +1,44 @@ +package xueli.mcremake.client.systems; + +import xueli.game2.display.event.WindowKeyEvent; +import xueli.game2.display.event.WindowMouseButtonEvent; +import xueli.game2.input.DefaultKeyListener; +import xueli.game2.input.DefaultMouseListener; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.IGameSystem; + +public class KeyBindingUpdateSystem implements IGameSystem { + + private DefaultKeyListener keyListener; + private DefaultMouseListener mouseListener; + + @Override + public void start(CraftGameClient ctx) { + keyListener = new DefaultKeyListener(ctx.state.keyBindings); + mouseListener = new DefaultMouseListener(ctx.state.keyBindings); + + ctx.eventbus.register(WindowKeyEvent.class, this::processKeyEvent); + ctx.eventbus.register(WindowMouseButtonEvent.class, this::processMouseButtonEvent); + + } + + private void processKeyEvent(WindowKeyEvent event) { + keyListener.onKey(event.key(), event.scancode(), event.action(), event.mods()); + } + + private void processMouseButtonEvent(WindowMouseButtonEvent event) { + mouseListener.onMouseButton(event.button(), event.action(), event.mods()); + } + + @Override + public void update(CraftGameClient ctx) { + + } + + @Override + public void release(CraftGameClient ctx) { + ctx.eventbus.unregister(WindowKeyEvent.class, this::processKeyEvent); + ctx.eventbus.unregister(WindowMouseButtonEvent.class, this::processMouseButtonEvent); + } + +} diff --git a/src/main/java/xueli/mcremake/client/systems/PlayerUpdateSystem.java b/src/main/java/xueli/mcremake/client/systems/PlayerUpdateSystem.java new file mode 100644 index 00000000..ca707c91 --- /dev/null +++ b/src/main/java/xueli/mcremake/client/systems/PlayerUpdateSystem.java @@ -0,0 +1,175 @@ +package xueli.mcremake.client.systems; + +import org.lwjgl.glfw.GLFW; +import org.lwjgl.utils.vector.Vector3d; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.Vector; +import xueli.game2.display.Display; +import xueli.game2.math.TriFuncMap; +import xueli.game2.phys.aabb.AABB; +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.IGameSystem; +import xueli.mcremake.client.player.AttackButtonHandler; +import xueli.mcremake.client.player.UseButtonHandler; +import xueli.mcremake.core.entity.CollideResult; +import xueli.mcremake.core.entity.EntityCollider; +import xueli.mcremake.core.entity.PickCollider; +import xueli.mcremake.core.entity.VirtualKeyboard; + +public class PlayerUpdateSystem implements IGameSystem { + + private static final AABB PLAYER_COLLISION_BOX = new AABB(new Vector3d(-0.4, -1.5, -0.4), + new Vector3d(0.4, 0.2, 0.4)); + private static final double MAX_REACH_DISTANCE = 6.0; + + private final VirtualKeyboard keyboard = new VirtualKeyboard(); + // private boolean firstTick = true; + + private EntityCollider collider; + + private AttackButtonHandler attackHandler; + private UseButtonHandler useHandler; + + @Override + public void start(CraftGameClient ctx) { + this.collider = new EntityCollider(PLAYER_COLLISION_BOX, ctx.state.world); + this.attackHandler = new AttackButtonHandler(ctx); + this.useHandler = new UseButtonHandler(ctx); + + } + + @Override + public void update(CraftGameClient ctx) { + this.updateKeyboardState(ctx); + this.updateRotation(ctx); + this.updateRenderPosition(ctx); + this.updatePickResult(ctx); + this.tickMouseButton(ctx); + + } + + private void updateKeyboardState(CraftGameClient ctx) { + keyboard.forward = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_W); + keyboard.backward = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_S); + keyboard.leftward = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_A); + keyboard.rightward = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_D); + keyboard.wantDash = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_R); + keyboard.wantFly = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_SPACE); + keyboard.wantSneak = ctx.state.keyBindings.isPressed(GLFW.GLFW_KEY_LEFT_SHIFT); + + } + + private void updateRotation(CraftGameClient ctx) { + Display display = ctx.getDisplay(); + ctx.state.player.rotX -= display.getCursorDY() * 0.1; // TODO: Into key binding or something like that for the + // "abstract" layer + ctx.state.player.rotY += display.getCursorDX() * 0.1; + ctx.state.player.rotX = Math.min(ctx.state.player.rotX, 89.0); // TODO: Why isn't the view doing smooth + // rotation? + ctx.state.player.rotX = Math.max(ctx.state.player.rotX, -89.0); + // System.out.println(ctx.state.player.rotX + ", " + ctx.state.player.rotY); + + } + + private void updateRenderPosition(CraftGameClient ctx) { + Vector positionOnRender = ctx.state.player.positionOnRender; + Vector3d lastTickPosition = ctx.state.player.lastTickPosition; + Vector3d recentTickPosition = ctx.state.player.position; + positionOnRender.x = lastTickPosition.x + (recentTickPosition.x - lastTickPosition.x) * ctx.state.partialTick; + positionOnRender.y = lastTickPosition.y + (recentTickPosition.y - lastTickPosition.y) * ctx.state.partialTick; + positionOnRender.z = lastTickPosition.z + (recentTickPosition.z - lastTickPosition.z) * ctx.state.partialTick; + positionOnRender.rotX = ctx.state.player.rotX; + positionOnRender.rotY = ctx.state.player.rotY; + + } + + private void updatePickResult(CraftGameClient ctx) { + Vector renderPos = ctx.state.player.positionOnRender; + ctx.state.player.pickResult = PickCollider.pick(renderPos, MAX_REACH_DISTANCE, ctx.state.world); + + } + + private void tickMouseButton(CraftGameClient ctx) { + attackHandler.tick(ctx); + useHandler.tick(ctx); + + } + + @Override + public void tick(CraftGameClient ctx) { + this.movePlayer(ctx); + + // firstTick = false; + + } + + private void movePlayer(CraftGameClient ctx) { + Vector3d position = ctx.state.player.position; + Vector3d velocity = ctx.state.player.velocity; + + ctx.state.player.lastTickPosition.x = position.x; + ctx.state.player.lastTickPosition.y = position.y; + ctx.state.player.lastTickPosition.z = position.z; + + Vector3f acceleration = new Vector3f(); + float speed = 0.3f; + + if (keyboard.wantDash) { + speed *= 7.0f; + } + + if (keyboard.forward) { + acceleration.x -= speed * (float) TriFuncMap.sin(-ctx.state.player.rotY); + acceleration.z -= speed * (float) TriFuncMap.cos(-ctx.state.player.rotY); + } else if (keyboard.backward) { + acceleration.x += speed * (float) TriFuncMap.sin(-ctx.state.player.rotY); + acceleration.z += speed * (float) TriFuncMap.cos(-ctx.state.player.rotY); + } + + if (keyboard.leftward) { + acceleration.x -= speed * (float) TriFuncMap.sin(-ctx.state.player.rotY + 90); + acceleration.z -= speed * (float) TriFuncMap.cos(-ctx.state.player.rotY + 90); + } else if (keyboard.rightward) { + acceleration.x += speed * (float) TriFuncMap.sin(-ctx.state.player.rotY + 90); + acceleration.z += speed * (float) TriFuncMap.cos(-ctx.state.player.rotY + 90); + } + + if (keyboard.wantFly) { + acceleration.y += speed * 1.4f; + } else if (keyboard.wantSneak) { + acceleration.y -= speed * 1.4f; + } + + velocity.x += acceleration.x; + velocity.y += acceleration.y; + velocity.z += acceleration.z; + + velocity.x *= 0.5; + velocity.z *= 0.5; + velocity.y *= 0.6; + + Vector3d delta = new Vector3d(velocity.x, velocity.y, velocity.z); + CollideResult result = collider.collide(new Vector3d(position.x, position.y, position.z), delta, delta); + position.x += delta.x; + position.y += delta.y; + position.z += delta.z; + // System.out.println(delta); + + if (result.xCollide() != CollideResult.NO_COLLIDE) { + velocity.x = 0; + } + if (result.yCollide() != CollideResult.NO_COLLIDE) { + velocity.y = 0; + } + if (result.zCollide() != CollideResult.NO_COLLIDE) { + velocity.z = 0; + } + System.out.println(result); + + // keyboard.forward = keyboard.backward = keyboard.leftward = keyboard.rightward + // = keyboard.wantDash = keyboard.wantFly = keyboard.wantSneak = false; + + } + +} diff --git a/src/main/java/xueli/mcremake/core/block/BlockCollidable.java b/src/main/java/xueli/mcremake/core/block/BlockCollidable.java new file mode 100644 index 00000000..a7fc9a02 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/block/BlockCollidable.java @@ -0,0 +1,25 @@ +package xueli.mcremake.core.block; + +import java.util.List; + +import xueli.game2.phys.aabb.AABB; +import xueli.game2.phys.aabb.NameableAABB; +import xueli.mcremake.core.world.WorldAccessible; + +public interface BlockCollidable { + + public static final BlockCollidable NONE = new BlockCollidable() { + @Override + public void getCollisionAABBs(int x, int y, int z, WorldAccessible world, List aabbs) { + } + + @Override + public void getPickTestAABBs(int x, int y, int z, WorldAccessible world, List aabbs) { + } + }; + + public void getCollisionAABBs(int x, int y, int z, WorldAccessible world, List aabbs); + + public void getPickTestAABBs(int x, int y, int z, WorldAccessible world, List aabbs); + +} diff --git a/src/main/java/xueli/mcremake/core/block/BlockType.java b/src/main/java/xueli/mcremake/core/block/BlockType.java new file mode 100644 index 00000000..7036c168 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/block/BlockType.java @@ -0,0 +1,8 @@ +package xueli.mcremake.core.block; + +import xueli.mcremake.client.renderer.world.block.BlockVertexGatherer; +import xueli.registry.Identifier; + +public record BlockType(Identifier namespace, String name, BlockVertexGatherer renderer, + BlockCollidable collidable) { +} diff --git a/src/main/java/xueli/mcremake/core/entity/CollideResult.java b/src/main/java/xueli/mcremake/core/entity/CollideResult.java new file mode 100644 index 00000000..939a3cef --- /dev/null +++ b/src/main/java/xueli/mcremake/core/entity/CollideResult.java @@ -0,0 +1,17 @@ +package xueli.mcremake.core.entity; + +public record CollideResult(int xCollide, int yCollide, int zCollide) { + + public static final int NO_COLLIDE = 0; + public static final int MINUS_COLLIDE = 1; + public static final int PLUS_COLLIDE = 2; + + public static int calcCollide(double result, double origin) { + if (result == origin) + return NO_COLLIDE; + if (result < origin) + return MINUS_COLLIDE; + return PLUS_COLLIDE; + } + +} diff --git a/src/main/java/xueli/mcremake/core/entity/EntityCollider.java b/src/main/java/xueli/mcremake/core/entity/EntityCollider.java new file mode 100644 index 00000000..a87475c8 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/entity/EntityCollider.java @@ -0,0 +1,145 @@ +package xueli.mcremake.core.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.utils.vector.Vector3d; +import org.lwjgl.utils.vector.Vector3i; + +import xueli.game2.phys.aabb.AABB; +import xueli.mcremake.core.block.BlockCollidable; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.world.WorldAccessible; + +public class EntityCollider { + + private static final double EPS = 1.0e-5d; + + private final WorldAccessible world; + // Relative to camera + private final AABB relativeBox; + + public EntityCollider(AABB relativeEntityBox, WorldAccessible world) { + this.world = world; + this.relativeBox = relativeEntityBox; + + } + + public CollideResult collide(Vector3d original, Vector3d delta, Vector3d target) { + ArrayList aabbs = new ArrayList<>(); + AABB entityBox = this.relativeBox.add(original); + addAllCollisionBox(this.world, entityBox.expand(delta), aabbs); + + double detY = delta.y; + for (int i = 0; i < aabbs.size(); i++) { + AABB box = aabbs.get(i); + detY = this.collideClipY(entityBox, box, detY); + } + entityBox = entityBox.add(new Vector3d(0, detY, 0)); + + double detX = delta.x; + for (int i = 0; i < aabbs.size(); i++) { + AABB box = aabbs.get(i); + detX = this.collideClipX(entityBox, box, detX); + } + entityBox = entityBox.add(new Vector3d(detX, 0, 0)); + + double detZ = delta.z; + for (int i = 0; i < aabbs.size(); i++) { + AABB box = aabbs.get(i); + detZ = this.collideClipZ(entityBox, box, detZ); + } +// entityBox = entityBox.add(new Vector3d(0, 0, detZ)); + + target.x = detX; + target.y = detY; + target.z = detZ; + return new CollideResult(CollideResult.calcCollide(detX, delta.x), // TODO: All returned 0? strange + CollideResult.calcCollide(detY, delta.y), CollideResult.calcCollide(detZ, delta.z)); + } + + private double collideClipX(AABB movingBox, AABB toCollideBox, double movingDistance) { + Vector3d m0 = movingBox.getVmin(); + Vector3d m1 = movingBox.getVmax(); + Vector3d c0 = toCollideBox.getVmin(); + Vector3d c1 = toCollideBox.getVmax(); + + if (m1.y <= c0.y || m0.y >= c1.y) + return movingDistance; + if (m1.z <= c0.z || m0.z >= c1.z) + return movingDistance; + + if (movingDistance > 0.0F && m1.x <= c0.x) { + movingDistance = Math.min(movingDistance, c0.x - m1.x - EPS); + } + + if (movingDistance < 0.0F && m0.x >= c1.x) { + movingDistance = Math.max(movingDistance, c1.x - m0.x + EPS); + } + + return movingDistance; + } + + private double collideClipY(AABB movingBox, AABB toCollideBox, double movingDistance) { + Vector3d m0 = movingBox.getVmin(); + Vector3d m1 = movingBox.getVmax(); + Vector3d c0 = toCollideBox.getVmin(); + Vector3d c1 = toCollideBox.getVmax(); + + if (m1.x <= c0.x || m0.x >= c1.x) + return movingDistance; + if (m1.z <= c0.z || m0.z >= c1.z) + return movingDistance; + + if (movingDistance > 0.0F && m1.y <= c0.y) { + movingDistance = Math.min(movingDistance, c0.y - m1.y - EPS); + } + + if (movingDistance < 0.0F && m0.y >= c1.y) { + movingDistance = Math.max(movingDistance, c1.y - m0.y + EPS); + } + + return movingDistance; + } + + private double collideClipZ(AABB movingBox, AABB toCollideBox, double movingDistance) { + Vector3d m0 = movingBox.getVmin(); + Vector3d m1 = movingBox.getVmax(); + Vector3d c0 = toCollideBox.getVmin(); + Vector3d c1 = toCollideBox.getVmax(); + + if (m1.x <= c0.x || m0.x >= c1.x) + return movingDistance; + if (m1.y <= c0.y || m0.y >= c1.y) + return movingDistance; + + if (movingDistance > 0.0F && m1.z <= c0.z) { + movingDistance = Math.min(movingDistance, c0.z - m1.z - EPS); + } + + if (movingDistance < 0.0F && m0.z >= c1.z) { + movingDistance = Math.max(movingDistance, c1.z - m0.z + EPS); + } + + return movingDistance; + } + + public static void addAllCollisionBox(WorldAccessible world, AABB box, List list) { + Vector3i blockPosLittle = new Vector3i(box.getVmin()); + Vector3i blockPosBig = new Vector3i(box.getVmax()); + for (int x = blockPosLittle.x; x <= blockPosBig.x; x++) { + for (int y = blockPosLittle.y; y <= blockPosBig.y; y++) { + for (int z = blockPosLittle.z; z <= blockPosBig.z; z++) { + BlockType block = world.getBlock(x, y, z); + if (block == null) + continue; + BlockCollidable collidable = block.collidable(); + if (collidable == null) + continue; + collidable.getCollisionAABBs(x, y, z, world, list); + } + } + } + } + +} diff --git a/src/main/java/xueli/mcremake/core/entity/PickCollider.java b/src/main/java/xueli/mcremake/core/entity/PickCollider.java new file mode 100644 index 00000000..a546f857 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/entity/PickCollider.java @@ -0,0 +1,177 @@ +package xueli.mcremake.core.entity; + +import java.util.ArrayList; + +import org.lwjgl.utils.vector.Vector2d; +import org.lwjgl.utils.vector.Vector3d; +import org.lwjgl.utils.vector.Vector3i; + +import xueli.game2.Vector; +import xueli.game2.math.TriFuncMap; +import xueli.game2.phys.aabb.BoxFace; +import xueli.game2.phys.aabb.NameableAABB; +import xueli.mcremake.client.ListenableBufferedWorldAccessible; +import xueli.mcremake.core.block.BlockCollidable; +import xueli.mcremake.core.block.BlockType; + +public class PickCollider { + + public static PickResult pick(Vector camera, double maxReachDistance, ListenableBufferedWorldAccessible world) { + Vector3d direction = new Vector3d(TriFuncMap.sin(camera.rotY), TriFuncMap.tan(camera.rotX), + -TriFuncMap.cos(camera.rotY)); + direction.normalize(); + + // If the line goes down, it is impossible to interact with the bottom. + // Conversely, the line going up can't interact with the top. The thesis is the + // same with the other 2 axis. + boolean[] needFaceTest = { true, true, true, true, true, true }; + if (direction.y < 0) { + needFaceTest[2] = false; + } else if (direction.y > 0) { + needFaceTest[3] = false; + } else { + needFaceTest[2] = needFaceTest[3] = false; + } + + if (direction.x < 0) { + needFaceTest[0] = false; + } else if (direction.x > 0) { + needFaceTest[1] = false; + } else { + needFaceTest[0] = needFaceTest[1] = false; + } + + if (direction.z < 0) { + needFaceTest[4] = false; + } else if (direction.z > 0) { + needFaceTest[5] = false; + } else { + needFaceTest[4] = needFaceTest[5] = false; + } + + for (double i = 0; i < maxReachDistance; i += 0.1) { + double rayEndX = camera.x + i * direction.x; + double rayEndY = camera.y + i * direction.y; + double rayEndZ = camera.z + i * direction.z; + + Vector3i blockPos = new Vector3i((int) Math.floor(rayEndX), (int) Math.floor(rayEndY), + (int) Math.floor(rayEndZ)); + BlockType block = world.getBlockImmediate(blockPos.x, blockPos.y, blockPos.z); + BlockCollidable collidable; + if (block != null && ((collidable = block.collidable()) != null)) { + ArrayList blockBoundingBoxes = new ArrayList<>(); + collidable.getPickTestAABBs(blockPos.x, blockPos.y, blockPos.z, world, blockBoundingBoxes); + + double finalT = maxReachDistance; + PickResult result = null; + + for (int j = 0; j < blockBoundingBoxes.size(); j++) { + NameableAABB aabb = blockBoundingBoxes.get(j); + Vector3d b0 = aabb.getVmin(); + Vector3d b1 = aabb.getVmax(); + + // Here we have a math question: + // Now that there is a line, which expression is: + // [ x = x0 + t * dx + // { y = y0 + t * dy + // [ z = z0 + t * dz + // And here is a box, which has 12 faces, + // We have to interact the line with planes extended from the faces + + // For face x = xb0, + // t = (xb0 - x0) / dx + // If t is greater than maxReachDistance, we ignore it + // Or we calculate y and z to check if they are in a range + // At last we choose the least "t" as our collision result + + if (needFaceTest[0]) { + double t = (b0.x - camera.x) / direction.x; + if (t >= 0 && t < finalT) { + double thisY = camera.y + t * direction.y; + double thisZ = camera.z + t * direction.z; + if (thisY > b0.y && thisY < b1.y && thisZ > b0.z && thisZ < b1.z) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.X_MINUS, + new Vector2d((thisY - b0.y) / (b1.y - b0.y), (thisZ - b0.z) / (b1.z - b0.z))); + } + } + } + + if (needFaceTest[1]) { + double t = (b1.x - camera.x) / direction.x; + if (t >= 0 && t < finalT) { + double thisY = camera.y + t * direction.y; + double thisZ = camera.z + t * direction.z; + if (thisY > b0.y && thisY < b1.y && thisZ > b0.z && thisZ < b1.z) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.X_PLUS, + new Vector2d((thisY - b0.y) / (b1.y - b0.y), (thisZ - b0.z) / (b1.z - b0.z))); + } + } + } + + if (needFaceTest[2]) { + double t = (b0.y - camera.y) / direction.y; + if (t >= 0 && t <= finalT) { + double thisX = camera.x + t * direction.x; + double thisZ = camera.z + t * direction.z; + if (thisX > b0.x && thisX < b1.x && thisZ > b0.z && thisZ < b1.z) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.Y_MINUS, + new Vector2d((thisX - b0.x) / (b1.x - b0.x), (thisZ - b0.z) / (b1.z - b0.z))); + } + } + } + + if (needFaceTest[3]) { + double t = (b1.y - camera.y) / direction.y; + if (t >= 0 && t <= finalT) { + double thisX = camera.x + t * direction.x; + double thisZ = camera.z + t * direction.z; + if (thisX > b0.x && thisX < b1.x && thisZ > b0.z && thisZ < b1.z) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.Y_PLUS, + new Vector2d((thisX - b0.x) / (b1.x - b0.x), (thisZ - b0.z) / (b1.z - b0.z))); + } + } + } + + if (needFaceTest[4]) { + double t = (b0.z - camera.z) / direction.z; + if (t >= 0 && t <= finalT) { + double thisX = camera.x + t * direction.x; + double thisY = camera.y + t * direction.y; + if (thisX > b0.x && thisX < b1.x && thisY > b0.y && thisY < b1.y) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.Z_MINUS, + new Vector2d((thisX - b0.x) / (b1.x - b0.x), (thisY - b0.y) / (b1.y - b0.y))); + } + } + } + + if (needFaceTest[5]) { + double t = (b1.z - camera.z) / direction.z; + if (t >= 0 && t <= finalT) { + double thisX = camera.x + t * direction.x; + double thisY = camera.y + t * direction.y; + if (thisX > b0.x && thisX < b1.x && thisY > b0.y && thisY < b1.y) { + finalT = t; + result = new PickResult(blockPos, aabb, BoxFace.Z_PLUS, + new Vector2d((thisX - b0.x) / (b1.x - b0.x), (thisY - b0.y) / (b1.y - b0.y))); + } + } + } + + } + + if (result != null) + return result; + + } + + } + + return null; + } + +} diff --git a/src/main/java/xueli/mcremake/core/entity/PickResult.java b/src/main/java/xueli/mcremake/core/entity/PickResult.java new file mode 100644 index 00000000..d22fa142 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/entity/PickResult.java @@ -0,0 +1,16 @@ +package xueli.mcremake.core.entity; + +import org.lwjgl.utils.vector.Vector2d; +import org.lwjgl.utils.vector.Vector3i; + +import xueli.game2.phys.aabb.BoxFace; +import xueli.game2.phys.aabb.NameableAABB; + +// Make it simple first +public record PickResult(Vector3i blockPos, NameableAABB collisionBox, BoxFace face, Vector2d relativeCollidePos) { + + public Vector3i placePos() { + return Vector3i.add(blockPos, face.getFaceToVector()); + } + +} diff --git a/src/main/java/xueli/mcremake/core/entity/VirtualKeyboard.java b/src/main/java/xueli/mcremake/core/entity/VirtualKeyboard.java new file mode 100644 index 00000000..479f3f06 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/entity/VirtualKeyboard.java @@ -0,0 +1,11 @@ +package xueli.mcremake.core.entity; + +public class VirtualKeyboard { + + public boolean forward = false, backward = false; + public boolean leftward = false, rightward = false; + public boolean wantDash = false, wantFly = false, wantSneak = false; + +// public int wantUseLeftButton = 0, wantUseRightButton = 0; + +} diff --git a/src/main/java/xueli/mcremake/core/item/ItemType.java b/src/main/java/xueli/mcremake/core/item/ItemType.java new file mode 100644 index 00000000..2af42375 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/item/ItemType.java @@ -0,0 +1,7 @@ +package xueli.mcremake.core.item; + +import xueli.mcremake.client.renderer.item.ItemVertexGatherer; +import xueli.registry.Identifier; + +public record ItemType(Identifier namespace, String name, ItemVertexGatherer renderer) { +} diff --git a/src/main/java/xueli/mcremake/core/world/BufferedWorldAccessible.java b/src/main/java/xueli/mcremake/core/world/BufferedWorldAccessible.java new file mode 100644 index 00000000..08a919a8 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/BufferedWorldAccessible.java @@ -0,0 +1,48 @@ +package xueli.mcremake.core.world; + +import java.util.ArrayList; +import java.util.function.Consumer; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.core.block.BlockType; + +public class BufferedWorldAccessible implements WorldAccessible { + + protected final WorldAccessible world; + + public BufferedWorldAccessible(WorldAccessible world) { + this.world = world; + } + + @Override + public BlockType getBlock(int x, int y, int z) { + return world.getBlock(x, y, z); + } + + @Override + public CompoundMap getBlockTag(int x, int y, int z) { + return world.getBlockTag(x, y, z); + } + + protected final ArrayList commandBuffer = new ArrayList<>(); + + @Override + public void setBlock(int x, int y, int z, BlockType block) { + commandBuffer.add(() -> world.setBlock(x, y, z, block)); + } + + @Override + public void modifyBlockTag(int x, int y, int z, Consumer c) { + commandBuffer.add(() -> { + world.modifyBlockTag(x, y, z, c); + }); + } + + public void flush() { + commandBuffer.forEach(Runnable::run); + commandBuffer.clear(); + + } + +} diff --git a/src/main/java/xueli/mcremake/core/world/Chunk.java b/src/main/java/xueli/mcremake/core/world/Chunk.java new file mode 100644 index 00000000..d01bae93 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/Chunk.java @@ -0,0 +1,138 @@ +package xueli.mcremake.core.world; + +import java.util.HashMap; +import java.util.function.Consumer; + +import org.lwjgl.utils.vector.Vector2i; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.Tag; + +import xueli.mcremake.core.block.BlockType; + +public class Chunk implements WorldAccessible { + + public static final int CHUNK_SIZE = 16; + public static final int SUB_CHUNK_HEIGHT = 16; + public static final int SUB_CHUNK_LAYER_COUNT = 8; + public static final int CHUNK_HEIGHT = SUB_CHUNK_HEIGHT * SUB_CHUNK_LAYER_COUNT; + + final ChunkGrid[] grids = new ChunkGrid[SUB_CHUNK_LAYER_COUNT]; + + private final HashMap techniqueTags = new HashMap<>(); + private final CompoundMap gamingChunkTag = new CompoundMap(); + + public Chunk() { + for (int i = 0; i < SUB_CHUNK_LAYER_COUNT; i++) { + grids[i] = new ChunkGrid(); + } + + } + + public WorldAccessible createAccessible() { + return this; + } + + @Override + public BlockType getBlock(int x, int y, int z) { + if (y < 0 || y >= Chunk.CHUNK_HEIGHT) + return null; + int ySub = y / SUB_CHUNK_HEIGHT; +// System.out.println(ySub + ", " + y + ", " + x + ", " + z); + return grids[ySub].grid[x][z][y % SUB_CHUNK_HEIGHT]; + } + + @Override + public CompoundMap getBlockTag(int x, int y, int z) { + if (y < 0 || y >= Chunk.CHUNK_HEIGHT) + return null; + int ySub = y / SUB_CHUNK_HEIGHT; + return grids[ySub].tagGrid[x][z][y % SUB_CHUNK_HEIGHT]; + } + + @Override + public void setBlock(int x, int y, int z, BlockType block) { + if (y < 0 || y >= Chunk.CHUNK_HEIGHT) + return; + int ySub = y / SUB_CHUNK_HEIGHT; + int yInSub = y % SUB_CHUNK_HEIGHT; + ChunkGrid grid = grids[ySub]; + grid.grid[x][z][yInSub] = block; + + if (block == null) { + grid.tagGrid[x][z][yInSub] = null; + if (y == grid.heightMap[x][z]) { + int i = --grid.heightMap[x][z]; + for (; i >= 0 && grid.grid[x][z][i] == null; i = --grid.heightMap[x][z]) + ; + } + } else { + grid.heightMap[x][z] = Math.max(grid.heightMap[x][z], yInSub); + } + + } + + public void setBlockImmediate(int x, int y, int z, BlockType block) { + int ySub = y / SUB_CHUNK_HEIGHT; + int yInSub = y % SUB_CHUNK_HEIGHT; + ChunkGrid grid = grids[ySub]; + grid.grid[x][z][yInSub] = block; + } + + public void recalcHeightMap() { + for (int i = 0; i < SUB_CHUNK_LAYER_COUNT; i++) { + ChunkGrid grid = grids[i]; + for (int x = 0; x < CHUNK_SIZE; x++) { + for (int z = 0; z < CHUNK_SIZE; z++) { + int h = grid.heightMap[x][z] = SUB_CHUNK_HEIGHT - 1; + for (; h >= 0 && grid.grid[x][z][h] == null; h = --grid.heightMap[x][z]) + ; + } + } + } + } + + @Override + public void modifyBlockTag(int x, int y, int z, Consumer c) { + if (y < 0 || y >= Chunk.CHUNK_HEIGHT) + return; + int ySub = y / SUB_CHUNK_HEIGHT; + CompoundMap map = grids[ySub].tagGrid[x][z][y % SUB_CHUNK_HEIGHT]; + if (map == null) { + map = grids[ySub].tagGrid[x][z][y % SUB_CHUNK_HEIGHT] = new CompoundMap(); + } + c.accept(map); + } + + public int getMaxHeight(int x, int z, int ySub) { + return grids[ySub].heightMap[x][z]; + } + + public void addTag(Tag tag) { + this.gamingChunkTag.put(tag); + } + + public Tag getTag(String key) { + return this.gamingChunkTag.get(key); + } + + void setTechniqueTags(String key, int val) { + this.techniqueTags.put(key, val); + } + + int getTechniqueTags(String key) { + return this.techniqueTags.get(key); + } + + public static Vector2i toChunkPos(int blockX, int blockZ) { + return new Vector2i(blockX >> 4, blockZ >> 4); + } + + public static Vector2i toChunkPos(int blockX, int blockZ, Vector2i inChunkPosDist) { + Vector2i chunkPos = toChunkPos(blockX, blockZ); + inChunkPosDist.x = blockX - (chunkPos.x << 4); + inChunkPosDist.y = blockZ - (chunkPos.y << 4); + return chunkPos; + } + +} diff --git a/src/main/java/xueli/mcremake/core/world/ChunkGrid.java b/src/main/java/xueli/mcremake/core/world/ChunkGrid.java new file mode 100644 index 00000000..6f05f045 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/ChunkGrid.java @@ -0,0 +1,13 @@ +package xueli.mcremake.core.world; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.core.block.BlockType; + +class ChunkGrid { + + public final BlockType[][][] grid = new BlockType[Chunk.CHUNK_SIZE][Chunk.CHUNK_SIZE][Chunk.SUB_CHUNK_HEIGHT]; + public final int[][] heightMap = new int[Chunk.CHUNK_SIZE][Chunk.CHUNK_SIZE]; + public final CompoundMap[][][] tagGrid = new CompoundMap[Chunk.CHUNK_SIZE][Chunk.CHUNK_SIZE][Chunk.SUB_CHUNK_HEIGHT]; + +} diff --git a/src/main/java/xueli/mcremake/core/world/ChunkProvider.java b/src/main/java/xueli/mcremake/core/world/ChunkProvider.java new file mode 100644 index 00000000..fe475b62 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/ChunkProvider.java @@ -0,0 +1,10 @@ +package xueli.mcremake.core.world; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +public interface ChunkProvider { + + public CompletableFuture getChunk(int x, int z, ExecutorService executor); + +} diff --git a/src/main/java/xueli/mcremake/core/world/WorldAccessible.java b/src/main/java/xueli/mcremake/core/world/WorldAccessible.java new file mode 100644 index 00000000..5e6ef4c1 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/WorldAccessible.java @@ -0,0 +1,19 @@ +package xueli.mcremake.core.world; + +import java.util.function.Consumer; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.core.block.BlockType; + +public interface WorldAccessible { + + public BlockType getBlock(int x, int y, int z); + + public CompoundMap getBlockTag(int x, int y, int z); + + public void setBlock(int x, int y, int z, BlockType block); + + public void modifyBlockTag(int x, int y, int z, Consumer c); + +} diff --git a/src/main/java/xueli/mcremake/core/world/WorldDimension.java b/src/main/java/xueli/mcremake/core/world/WorldDimension.java new file mode 100644 index 00000000..701955d6 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/WorldDimension.java @@ -0,0 +1,159 @@ +package xueli.mcremake.core.world; + +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.lwjgl.utils.vector.Vector2i; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.mcremake.client.CraftGameClient; +import xueli.mcremake.client.events.NewChunkEvent; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.level.worldgen.LandOrRiverLayer; +import xueli.mcremake.registry.GameRegistry; + +public class WorldDimension implements WorldAccessible { + + private final CraftGameClient ctx; + private final ConcurrentHashMap chunkMap = new ConcurrentHashMap<>(); + + public WorldDimension(CraftGameClient ctx) { + this.ctx = ctx; + + } + + public void init() { // Maybe it should be a startup component +// RandomChunkProvider chunkGenerator = new RandomChunkProvider(666); + +// for (int i = -8; i < 8; i++) { +// for (int j = -8; j < 8; j++) { +// final Vector2i chunkPos = new Vector2i(i, j); +// chunkGenerator.getChunk(j, i, ctx.getAsyncExecutor()) +// .thenAcceptAsync(chunk -> { +// chunkMap.put(chunkPos, chunk); +// ctx.eventbus.post(new NewChunkEvent(chunkPos.x, chunkPos.y)); +// }, ctx.getMainThreadExecutor()) +// .exceptionally(e -> { +// e.printStackTrace(); +// return null; +// }); +// } +// } + + Random random = new Random(666); + int[][] landRiverLayer = new int[64][64]; + for (int x = 0; x < 64; x++) { + for (int y = 0; y < 64; y++) { + landRiverLayer[x][y] = random.nextInt(10) > 5 ? 1 : 0; + } + } + landRiverLayer = LandOrRiverLayer.scaleAndMerge(landRiverLayer, 64, 64); + landRiverLayer = LandOrRiverLayer.scaleAndMerge(landRiverLayer, 128, 128); + LandOrRiverLayer.merge(landRiverLayer, 256, 256); + LandOrRiverLayer.merge(landRiverLayer, 256, 256); + LandOrRiverLayer.merge(landRiverLayer, 256, 256); + LandOrRiverLayer.merge(landRiverLayer, 256, 256); + + for (int x = 0; x < 256; x++) { + for (int y = 0; y < 256; y++) { + landRiverLayer[x][y] *= 5; + } + } + + for (int i = 5; i > 0; i--) { + for (int x = 0; x < 256; x++) { + for (int y = 0; y < 256; y++) { + if (landRiverLayer[x][y] == 0 + && LandOrRiverLayer.findValue(landRiverLayer, 256, 256, x, y, i) > 0) { + landRiverLayer[x][y] = i - 1; + } + } + } + } + + final int[][] finalLayer = landRiverLayer; + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + final Vector2i chunkPos = new Vector2i(i, j); + CompletableFuture.supplyAsync(() -> { + Chunk chunk = new Chunk(); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int height = finalLayer[chunkPos.x * 16 + x][chunkPos.y * 16 + z]; + chunk.setBlockImmediate(x, 70 + height, z, + height <= 2 ? GameRegistry.BLOCK_DIRT : GameRegistry.BLOCK_GRASS); + for (int y = 0; y < height + 70; y++) { + chunk.setBlockImmediate(x, y, z, GameRegistry.BLOCK_DIRT); + } + for (int y = 73; y > 70; y--) { + if (chunk.getBlock(x, y, z) == null) + chunk.setBlockImmediate(x, y, z, GameRegistry.BLOCK_WATER); + } + + } + } + + chunk.recalcHeightMap(); + return chunk; + }, ctx.getAsyncExecutor()).thenAcceptAsync(chunk -> { + chunkMap.put(chunkPos, chunk); + ctx.eventbus.post(new NewChunkEvent(chunkPos.x, chunkPos.y)); + }, ctx.getMainThreadExecutor()).exceptionally(e -> { + e.printStackTrace(); + return null; + }); + + } + } + + } + + public WorldAccessible createAccessible() { + return this; + } + + @Override + public BlockType getBlock(int x, int y, int z) { + Vector2i chunkPos = Chunk.toChunkPos(x, z); + Chunk chunk = chunkMap.get(chunkPos); + return chunk == null ? null + : chunk.getBlock(x - (chunkPos.x * Chunk.CHUNK_SIZE), y, z - (chunkPos.y * Chunk.CHUNK_SIZE)); + } + + @Override + public CompoundMap getBlockTag(int x, int y, int z) { + Vector2i chunkPos = Chunk.toChunkPos(x, z); + Chunk chunk = chunkMap.get(chunkPos); + return chunk == null ? null + : chunk.getBlockTag(x - (chunkPos.x * Chunk.CHUNK_SIZE), y, z - (chunkPos.y * Chunk.CHUNK_SIZE)); + } + + @Override + public void setBlock(int x, int y, int z, BlockType block) { + Vector2i chunkPos = Chunk.toChunkPos(x, z); + Chunk chunk = chunkMap.get(chunkPos); + if (chunk != null) { + chunk.setBlock(x - (chunkPos.x * Chunk.CHUNK_SIZE), y, z - (chunkPos.y * Chunk.CHUNK_SIZE), block); + } + + } + + @Override + public void modifyBlockTag(int x, int y, int z, Consumer c) { + Vector2i chunkPos = Chunk.toChunkPos(x, z); + Chunk chunk = chunkMap.get(chunkPos); + if (chunk == null) + return; + chunk.modifyBlockTag(x - (chunkPos.x * Chunk.CHUNK_SIZE), y, z - (chunkPos.y * Chunk.CHUNK_SIZE), c); + + } + + public Chunk getChunk(int x, int z) { + return chunkMap.get(new Vector2i(x, z)); + } + +} diff --git a/src/main/java/xueli/mcremake/core/world/biome/BiomeType.java b/src/main/java/xueli/mcremake/core/world/biome/BiomeType.java new file mode 100644 index 00000000..9014bd00 --- /dev/null +++ b/src/main/java/xueli/mcremake/core/world/biome/BiomeType.java @@ -0,0 +1,6 @@ +package xueli.mcremake.core.world.biome; + +import xueli.registry.Identifier; + +public record BiomeType(Identifier namespace, String name) { +} diff --git a/src/main/java/xueli/mcremake/level/LevelInfo.java b/src/main/java/xueli/mcremake/level/LevelInfo.java new file mode 100644 index 00000000..4a17d3b5 --- /dev/null +++ b/src/main/java/xueli/mcremake/level/LevelInfo.java @@ -0,0 +1,67 @@ +package xueli.mcremake.level; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteOrder; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.Tag; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.flowpowered.nbt.stream.NBTOutputStream; + +public class LevelInfo implements AutoCloseable { + + private final File file; + + private final int version; + private final CompoundMap map; + + public LevelInfo(File file) throws Exception { + this.file = file; + + try (DataInputStream in = new DataInputStream(new FileInputStream(file)); + NBTInputStream nbtIn = new NBTInputStream(in, false, ByteOrder.LITTLE_ENDIAN)) { + version = in.readInt(); + in.readInt(); + CompoundTag tag = (CompoundTag) nbtIn.readTag(); + this.map = tag.getValue(); + } + + } + + public Tag get(String tag) { + return map.get(tag); + } + + public void write(Tag tag) { + map.put(tag); + } + + @Override + public void close() throws Exception { + DataOutputStream out = new DataOutputStream(new FileOutputStream(file)); + out.writeInt(version); + + ByteArrayOutputStream nbtSectionOut = new ByteArrayOutputStream(); + NBTOutputStream nbtOut = new NBTOutputStream(nbtSectionOut, false, ByteOrder.LITTLE_ENDIAN); + nbtOut.writeTag(new CompoundTag("", map)); + nbtOut.close(); + + out.writeInt(nbtSectionOut.size()); + out.write(nbtSectionOut.toByteArray()); + + out.close(); + + } + + @Override + public String toString() { + return new CompoundTag("", map).toString(); + } + +} diff --git a/src/main/java/xueli/mcremake/level/LevelReader.java b/src/main/java/xueli/mcremake/level/LevelReader.java new file mode 100644 index 00000000..98949f28 --- /dev/null +++ b/src/main/java/xueli/mcremake/level/LevelReader.java @@ -0,0 +1,14 @@ +package xueli.mcremake.level; + +import java.io.File; + +public class LevelReader { + + private LevelReader() { + } + + public static LevelInfo readLevelInfo(File levelFolder) throws Exception { + return new LevelInfo(new File(levelFolder, "level.dat")); + } + +} diff --git a/src/main/java/xueli/mcremake/level/worldgen/LandOrRiverLayer.java b/src/main/java/xueli/mcremake/level/worldgen/LandOrRiverLayer.java new file mode 100644 index 00000000..ec8a3a77 --- /dev/null +++ b/src/main/java/xueli/mcremake/level/worldgen/LandOrRiverLayer.java @@ -0,0 +1,183 @@ +package xueli.mcremake.level.worldgen; + +import java.util.HashMap; +import java.util.Random; + +import org.lwjgl.utils.vector.Vector2i; + +import xueli.mcremake.core.world.Chunk; +import xueli.mcremake.registry.GameRegistry; +import xueli.utils.collection.CollectionHelper; + +public class LandOrRiverLayer { + + private static final int LAYER_MAP_INITIAL_SIZE = 8; + private static final int SCALE_TIMES = 5; + private static final int CHUNK_POS_TO_LAYER_OFFSET = 4; + private static final int LAYER_TO_CHUNK_COUNT = 16; + + private final long seed; + + private final HashMap layerMap = new HashMap<>(); + + public LandOrRiverLayer(long seed) { + this.seed = seed; + + } + + public void fillLandOrRiver(int x, int z, Chunk chunk) { + int[][] chunkMap = getLayerMap(x, z); +// System.out.println(CollectionHelper.toString(chunkMap)); + + for (int i = 0; i < Chunk.CHUNK_SIZE; i++) { + for (int j = 0; j < Chunk.CHUNK_SIZE; j++) { + chunk.setBlockImmediate(i, 70, j, + chunkMap[i][j] == 0 ? GameRegistry.BLOCK_STONE : GameRegistry.BLOCK_DIRT); + } + } + + } + + private int[][] getLayerMap(int x, int z) { + synchronized (this.layerMap) { + int[][] map = layerMap.get(new Vector2i(x, z)); + if (map != null) + return map; + this.genLayerMap(x >> CHUNK_POS_TO_LAYER_OFFSET, z >> CHUNK_POS_TO_LAYER_OFFSET); + return layerMap.get(new Vector2i(x, z)); + } + } + + private void genLayerMap(int offsetX, int offsetZ) { + Random random = new Random(this.seed); + + int size = LAYER_MAP_INITIAL_SIZE; + int[][] layerMap = new int[size][size]; + // First: Spread random spot + for (int x = 0; x < LAYER_MAP_INITIAL_SIZE; x++) { + for (int y = 0; y < LAYER_MAP_INITIAL_SIZE; y++) { + layerMap[x][y] = random.nextInt(10) * 6 / 10; + } + } + + // Second: Scale for times + for (int i = 0; i < SCALE_TIMES; i++) { + layerMap = scaleAndMerge(layerMap, size, size); + size *= 2; + } +// this.merge(layerMap, size, size); +// this.merge(layerMap, size, size); + + System.out.println(CollectionHelper.toString(layerMap)); + + // Last: copy data and store + // I know there are memory costs but I will fix it la-la-la-later! + for (int x = 0; x < LAYER_TO_CHUNK_COUNT; x++) { + for (int z = 0; z < LAYER_TO_CHUNK_COUNT; z++) { + int[][] chunkMap = new int[Chunk.CHUNK_SIZE][Chunk.CHUNK_SIZE]; + for (int x1 = 0; x1 < Chunk.CHUNK_SIZE; x1++) { + for (int z1 = 0; z1 < Chunk.CHUNK_SIZE; z1++) { + chunkMap[x1][z1] = layerMap[x * LAYER_TO_CHUNK_COUNT + x1][z * LAYER_TO_CHUNK_COUNT + z1]; +// System.out.println((x * LAYER_TO_CHUNK_COUNT + x1) + ", " + (z * LAYER_TO_CHUNK_COUNT + z1)); + + } +// System.out.println(x * LAYER_TO_CHUNK_COUNT + x1); + } + this.layerMap.put(new Vector2i((offsetX << CHUNK_POS_TO_LAYER_OFFSET) + x, + (offsetZ << CHUNK_POS_TO_LAYER_OFFSET) + z), chunkMap); +// System.out.println(new Vector2i((offsetX << CHUNK_POS_TO_LAYER_OFFSET) + x, (offsetZ << CHUNK_POS_TO_LAYER_OFFSET) + z)); + } + } +// System.out.println("===="); + + } + + public static int[][] scaleAndMerge(int[][] original, int xSize, int ySize) { + int newXSize = xSize << 1; + int newYSize = ySize << 1; + + int[][] dest = new int[newXSize][newYSize]; + for (int x = 0; x < xSize; x++) { + for (int y = 0; y < ySize; y++) { + dest[x * 2][y * 2] = original[x][y]; + dest[x * 2 + 1][y * 2] = original[x][y]; + dest[x * 2][y * 2 + 1] = original[x][y]; + dest[x * 2 + 1][y * 2 + 1] = original[x][y]; +// System.out.print(original[x][y] + " "); + } +// System.out.println(); + } +// System.out.println("=="); + merge(dest, newXSize, newYSize); +// System.out.println(CollectionHelper.toString(dest)); + return dest; + } + + public static void merge(int[][] dest, int xSize, int ySize) { + int[][] scores = new int[xSize][ySize]; + for (int x = 0; x < xSize; x++) { + for (int y = 0; y < ySize; y++) { + scores[x][y] = calcScore(dest, xSize, ySize, x, y); + } + } + + for (int x = 0; x < xSize; x++) { + for (int y = 0; y < ySize; y++) { + if (scores[x][y] > 4) + dest[x][y] = 1; + if (scores[x][y] < 4) + dest[x][y] = 0; + } + } + + } + + public static int calcScore(int[][] dest, int xSize, int ySize, int x, int y) { + int score = 0; + if (x != 0) { + score += dest[x - 1][y]; + if (y != 0) + score += dest[x - 1][y - 1]; + if (y != ySize - 1) + score += dest[x - 1][y + 1]; + } + if (x != xSize - 1) { + score += dest[x + 1][y]; + if (y != 0) + score += dest[x + 1][y - 1]; + if (y != ySize - 1) + score += dest[x + 1][y + 1]; + } + if (y != 0) + score += dest[x][y - 1]; + if (y != ySize - 1) + score += dest[x][y + 1]; + + return score; + } + + public static int findValue(int[][] dest, int xSize, int ySize, int x, int y, int val) { + int score = 0; + if (x != 0) { + score += dest[x - 1][y] == val ? 1 : 0; + if (y != 0) + score += dest[x - 1][y - 1] == val ? 1 : 0; + if (y != ySize - 1) + score += dest[x - 1][y + 1] == val ? 1 : 0; + } + if (x != xSize - 1) { + score += dest[x + 1][y] == val ? 1 : 0; + if (y != 0) + score += dest[x + 1][y - 1] == val ? 1 : 0; + if (y != ySize - 1) + score += dest[x + 1][y + 1] == val ? 1 : 0; + } + if (y != 0) + score += dest[x][y - 1] == val ? 1 : 0; + if (y != ySize - 1) + score += dest[x][y + 1] == val ? 1 : 0; + + return score; + } + +} diff --git a/src/main/java/xueli/mcremake/level/worldgen/RandomChunkProvider.java b/src/main/java/xueli/mcremake/level/worldgen/RandomChunkProvider.java new file mode 100644 index 00000000..697828cf --- /dev/null +++ b/src/main/java/xueli/mcremake/level/worldgen/RandomChunkProvider.java @@ -0,0 +1,32 @@ +package xueli.mcremake.level.worldgen; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +import xueli.mcremake.core.world.Chunk; +import xueli.mcremake.core.world.ChunkProvider; + +public class RandomChunkProvider implements ChunkProvider { + + public RandomChunkProvider(long seed) { + this.init(seed); + + } + + private void init(long seed) { + + } + + @Override + public CompletableFuture getChunk(int x, int z, ExecutorService executor) { + return CompletableFuture.supplyAsync(() -> { + Chunk chunk = new Chunk(); + + // TODO + + chunk.recalcHeightMap(); + return chunk; + }, executor); + } + +} diff --git a/src/main/java/xueli/mcremake/network/PacketSourceSide.java b/src/main/java/xueli/mcremake/network/PacketSourceSide.java new file mode 100644 index 00000000..b45e229a --- /dev/null +++ b/src/main/java/xueli/mcremake/network/PacketSourceSide.java @@ -0,0 +1,22 @@ +package xueli.mcremake.network; + +import xueli.game2.network.Protocol; +import xueli.mcremake.network.protocol.C00HelloPacket; + +public enum PacketSourceSide { + + FROM_SERVER(new Protocol()), + + FROM_CLIENT(new Protocol().register(0x00, C00HelloPacket.class, C00HelloPacket::new)); + + private Protocol protocol; + + PacketSourceSide(Protocol p) { + this.protocol = p; + } + + public Protocol getProtocol() { + return protocol; + } + +} diff --git a/src/main/java/xueli/mcremake/network/ServerPlayerInfo.java b/src/main/java/xueli/mcremake/network/ServerPlayerInfo.java new file mode 100644 index 00000000..8f6e8b37 --- /dev/null +++ b/src/main/java/xueli/mcremake/network/ServerPlayerInfo.java @@ -0,0 +1,6 @@ +package xueli.mcremake.network; + +import java.util.UUID; + +public record ServerPlayerInfo(String name, UUID uuid) { +} diff --git a/src/main/java/xueli/mcremake/network/protocol/C00HelloPacket.java b/src/main/java/xueli/mcremake/network/protocol/C00HelloPacket.java new file mode 100644 index 00000000..1a55f05e --- /dev/null +++ b/src/main/java/xueli/mcremake/network/protocol/C00HelloPacket.java @@ -0,0 +1,44 @@ +package xueli.mcremake.network.protocol; + +import java.io.IOException; +import java.util.UUID; + +import xueli.game2.network.Packet; +import xueli.game2.network.PrimitiveCodec; +import xueli.game2.network.Readable; +import xueli.game2.network.Writable; + +public class C00HelloPacket extends Packet { + + private String name; + private UUID uuid; + + public C00HelloPacket(Readable buf) { + super(buf); + } + + public C00HelloPacket(String name, UUID uuid) { + this.name = name; + this.uuid = uuid; + } + + @Override + public void read(Readable buf) throws IOException { + this.name = PrimitiveCodec.STRING.read(buf); + this.uuid = PrimitiveCodec.UUID.read(buf); + + } + + @Override + public void write(Writable buf) throws IOException { + PrimitiveCodec.STRING.write(name, buf); + PrimitiveCodec.UUID.write(uuid, buf); + + } + + @Override + public String toString() { + return "C00HelloPacket [name=" + name + ", uuid=" + uuid + "]"; + } + +} diff --git a/src/main/java/xueli/mcremake/network/protocol/S00PlayPacket.java b/src/main/java/xueli/mcremake/network/protocol/S00PlayPacket.java new file mode 100644 index 00000000..85773216 --- /dev/null +++ b/src/main/java/xueli/mcremake/network/protocol/S00PlayPacket.java @@ -0,0 +1,39 @@ +package xueli.mcremake.network.protocol; + +import java.io.IOException; + +import xueli.game2.network.Packet; +import xueli.game2.network.Readable; +import xueli.game2.network.Writable; + +public class S00PlayPacket extends Packet { + + private float x, y, z; + + public S00PlayPacket(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public S00PlayPacket(Readable buf) { + super(buf); + } + + @Override + public void read(Readable buf) throws IOException { + this.x = buf.readFloat(); + this.y = buf.readFloat(); + this.z = buf.readFloat(); + + } + + @Override + public void write(Writable buf) throws IOException { + buf.writeFloat(this.x); + buf.writeFloat(this.y); + buf.writeFloat(this.z); + + } + +} diff --git a/src/main/java/xueli/mcremake/registry/GameRegistry.java b/src/main/java/xueli/mcremake/registry/GameRegistry.java new file mode 100644 index 00000000..97dc5752 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/GameRegistry.java @@ -0,0 +1,168 @@ +package xueli.mcremake.registry; + +import xueli.mcremake.client.renderer.item.ItemVertexGatherer; +import xueli.mcremake.client.renderer.world.block.BlockRendererLiquid; +import xueli.mcremake.client.renderer.world.block.BlockRendererSideTopBottom; +import xueli.mcremake.client.renderer.world.block.BlockRendererSolid; +import xueli.mcremake.client.renderer.world.block.BlockVertexGatherer; +import xueli.mcremake.core.block.BlockCollidable; +import xueli.mcremake.core.block.BlockType; +import xueli.mcremake.core.item.ItemType; +import xueli.mcremake.core.world.biome.BiomeType; +import xueli.mcremake.registry.block.BlockCollidableSolid; +import xueli.mcremake.registry.item.ItemRendererRegularBlock; +import xueli.registry.Identifier; +import xueli.registry.Registry; +import xueli.registry.RegistryImplement; +import xueli.registry.WritableRegistry; + +public final class GameRegistry { + + // Include this tag to make block item use default renderer + public static final Identifier TAG_GENERIC_BLOCK = new Identifier("generic_blocks"); + public static final Identifier TAG_GENERIC_LIQUID = new Identifier("generic_liquid"); + + public static final Registry BUILTIN_BLOCK_REGISTRY; + public static final BlockType BLOCK_STONE; + public static final BlockType BLOCK_DIRT; + public static final BlockType BLOCK_GRASS; + public static final BlockType BLOCK_BEDROCK; + public static final BlockType BLOCK_SAND; + public static final BlockType BLOCK_GRAVEL; + public static final BlockType BLOCK_WATER; +// public static final BlockType BLOCK_SAND_STONE; + + static { + RegistryImplement writable = new RegistryImplement<>(); + BLOCK_STONE = registerBlockType(writable, new Identifier("stone"), "Stone", + new BlockRendererSolid(1, 0), new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_DIRT = registerBlockType(writable, new Identifier("dirt"), "dirt", new BlockRendererSolid(2, 0), + new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_GRASS = registerBlockType(writable, new Identifier("grass_block"), "Grass Block", + new BlockRendererSideTopBottom(3, 0, 1, 7, 2, 0), new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_BEDROCK = registerBlockType(writable, new Identifier("bedrock"), "bedrock", + new BlockRendererSolid(1, 1), new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_SAND = registerBlockType(writable, new Identifier("sand"), "sand", new BlockRendererSolid(2, 1), + new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_GRAVEL = registerBlockType(writable, new Identifier("gravel"), "gravel", + new BlockRendererSolid(3, 1), new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + BLOCK_WATER = registerBlockType(writable, new Identifier("water"), "water", + new BlockRendererLiquid(15, 12), BlockCollidable.NONE, TAG_GENERIC_LIQUID); + // BLOCK_SAND_STONE = registerBlockType(writable, new + // ResourceIdentifier("sandstone"), "sandstone", BlockListener.NONE, new + // BlockRendererSolid(1, 1), new BlockCollidableSolid(), TAG_GENERIC_BLOCK); + + BUILTIN_BLOCK_REGISTRY = writable.freeze(); + } + + public static final Registry BUILTIN_ITEM_REGISTRY; + public static final Registry BUILTIN_ITEM_BLOCK_MAP_REGISTRY; + public static final ItemType ITEM_BLOCK_STONE; + public static final ItemType ITEM_BLOCK_DIRT; + public static final ItemType ITEM_BLOCK_GRASS; + public static final ItemType ITEM_BLOCK_BEDROCK; + public static final ItemType ITEM_BLOCK_SAND; + public static final ItemType ITEM_BLOCK_GRAVEL; + + static { + RegistryImplement writable = new RegistryImplement<>(); + RegistryImplement mapWritable = new RegistryImplement<>(); + + ITEM_BLOCK_STONE = registerItemFromBlock(writable, mapWritable, BLOCK_STONE, + new ItemRendererRegularBlock(BLOCK_STONE)); + ITEM_BLOCK_DIRT = registerItemFromBlock(writable, mapWritable, BLOCK_DIRT, + new ItemRendererRegularBlock(BLOCK_DIRT)); + ITEM_BLOCK_GRASS = registerItemFromBlock(writable, mapWritable, BLOCK_GRASS, + new ItemRendererRegularBlock(BLOCK_GRASS)); + ITEM_BLOCK_BEDROCK = registerItemFromBlock(writable, mapWritable, BLOCK_BEDROCK, + new ItemRendererRegularBlock(BLOCK_BEDROCK)); + ITEM_BLOCK_SAND = registerItemFromBlock(writable, mapWritable, BLOCK_SAND, + new ItemRendererRegularBlock(BLOCK_SAND)); + ITEM_BLOCK_GRAVEL = registerItemFromBlock(writable, mapWritable, BLOCK_GRAVEL, + new ItemRendererRegularBlock(BLOCK_GRAVEL)); + + BUILTIN_ITEM_BLOCK_MAP_REGISTRY = mapWritable.freeze(); + BUILTIN_ITEM_REGISTRY = writable.freeze(); + + } + + public static final Registry BUILTIN_BIOME_REGISTRY; + public static final BiomeType BIOME_FOREST; + public static final BiomeType BIOME_SEASONAL_FOREST; + public static final BiomeType BIOME_RAIN_FOREST; + public static final BiomeType BIOME_TAIGA_FOREST; + public static final BiomeType BIOME_SWAMPLAND; + public static final BiomeType BIOME_SAVANNA; + public static final BiomeType BIOME_SHRUBLAND; + public static final BiomeType BIOME_DESERT; + public static final BiomeType BIOME_ICE_DESERT; + public static final BiomeType BIOME_PLAINS; + public static final BiomeType BIOME_TUNDRA; + + static { + RegistryImplement writable = new RegistryImplement<>(); + BIOME_RAIN_FOREST = registerBiomeType(writable, new Identifier("rainforest"), "Rainforest"); + BIOME_SWAMPLAND = registerBiomeType(writable, new Identifier("swampland"), "Swampland"); + BIOME_SEASONAL_FOREST = registerBiomeType(writable, new Identifier("seasonal_forest"), + "Seasonal Forest"); // Yes, inside the game there is a biome called Seasonal Forest + BIOME_FOREST = registerBiomeType(writable, new Identifier("forest"), "Forest"); + BIOME_ICE_DESERT = registerBiomeType(writable, new Identifier("ice_desert"), "Ice Desert"); // But + // inside + // the game + // it seems + // to extend + // the + // FlatBiome + // class and + // it really + // like + // Builder + // in Java + BIOME_SAVANNA = registerBiomeType(writable, new Identifier("savanna"), "Savanna"); + BIOME_SHRUBLAND = registerBiomeType(writable, new Identifier("shrubland"), "Shrubland"); + BIOME_TAIGA_FOREST = registerBiomeType(writable, new Identifier("taiga"), "Taiga"); + BIOME_DESERT = registerBiomeType(writable, new Identifier("desert"), "Desert"); + BIOME_PLAINS = registerBiomeType(writable, new Identifier("plains"), "Plains"); + BIOME_TUNDRA = registerBiomeType(writable, new Identifier("tundra"), "Tundra"); + + BUILTIN_BIOME_REGISTRY = writable.freeze(); + } + + public static BlockType registerBlockType(WritableRegistry registry, Identifier identify, + String name, BlockVertexGatherer renderer, BlockCollidable collidable, Identifier... tags) { + BlockType type = new BlockType(identify, name, renderer, collidable); + registry.register(identify, type); + registry.addTag(identify, tags); + return type; + } + + private static ItemType registerItemFromBlock(WritableRegistry registry, + WritableRegistry itemBlockMapRegistry, BlockType block, ItemVertexGatherer renderer, + Identifier... tags) { + ItemType type = new ItemType(block.namespace(), block.name(), renderer); + registry.register(block.namespace(), type); + registry.addTag(block.namespace(), tags); + itemBlockMapRegistry.register(block.namespace(), block.namespace()); + return type; + } + + public static ItemType registerItemType(WritableRegistry registry, Identifier identify, + String name, ItemVertexGatherer renderer, Identifier... tags) { + ItemType type = new ItemType(identify, name, renderer); + registry.register(identify, type); + registry.addTag(identify, tags); + return type; + } + + private static BiomeType registerBiomeType(WritableRegistry registry, Identifier identify, + String name, Identifier... tags) { + BiomeType type = new BiomeType(identify, name); + registry.register(identify, type); + registry.addTag(identify, tags); + return type; + } + + public static void callForClazzLoad() { + } + +} diff --git a/src/main/java/xueli/mcremake/registry/MojanglesFont.java b/src/main/java/xueli/mcremake/registry/MojanglesFont.java new file mode 100644 index 00000000..a809e2b8 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/MojanglesFont.java @@ -0,0 +1,121 @@ +package xueli.mcremake.registry; + +import java.awt.Color; +import java.util.HashMap; + +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector3f; + +import xueli.game2.display.GameDisplay; +import xueli.game2.renderer.legacy.BackRenderBuffer; +import xueli.game2.renderer.legacy.RenderBuffer; +import xueli.game2.resource.ReloadableResourceTicket; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.mcremake.client.renderer.gui.MyRenderBuffer2D; +import xueli.mcremake.client.renderer.gui.RenderTypeTexture2D; +import xueli.registry.Identifier; + +/** + * A easy font renderer TODO: Later we should support custom font separator and + * try rendering a middle aligned text in one step + */ +public class MojanglesFont implements ResourceHolder { + + private static final Identifier FONT_TEXTURE_LOCATION = new Identifier("minecraft", + "font/default.png"); + + private static final int[] CHAR_SIZES = { 3, 8, 8, 7, 7, 7, 7, 8, 8, 8, 8, 8, 7, 8, 8, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, + 8, 7, 7, 7, 8, 8, 8, 1, 1, 4, 5, 5, 5, 5, 2, 4, 4, 4, 5, 1, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 4, + 5, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 3, 5, 5, 2, + 5, 5, 5, 5, 5, 4, 5, 5, 1, 5, 4, 2, 5, 5, 5, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 4, 1, 4, 6, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 3, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, + 6, 5, 5, 5, 1, 5, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, 6, 7, 6, 7, 7, 7, 6, 7, 7, 6, 8, 8, 5, 6, 6, + 6, 6, 6, 8, 5, 6, 7, 7, 8, 8, 8, 7, 6, 8, 1, }; + + private final GameDisplay ctx; + + private RenderTypeTexture2D renderer = new RenderTypeTexture2D(); + private ReloadableResourceTicket fontTexture; + + public MojanglesFont(GameDisplay ctx) { + this.ctx = ctx; + this.fontTexture = ctx.textureResource.register(FONT_TEXTURE_LOCATION, true); + + } + + @Override + public void reload() { + + } + + private HashMap buffers = new HashMap<>(); + + public float measureWidth(float x, float y, float size, float separateRatio, String str) { + float width = 0; + for (int i = 0; i < str.length(); i++) { + int c = str.charAt(i); + // Only support ASCII + c = c >= 256 ? 0 : c; + width += size * CHAR_SIZES[c] / 8.0f; + width += size * separateRatio; + } + return width; + } + + public void drawFont(float x, float y, float size, float separateRatio, String str, Color color) { + RenderBuffer buffer = renderer.getRenderBuffer(fontTexture.get().id()); + BackRenderBuffer backBuffer = buffers.computeIfAbsent(buffer, RenderBuffer::createBackBuffer); + + float colorR = color.getRed() / 255.0f; + float colorG = color.getGreen() / 255.0f; + float colorB = color.getBlue() / 255.0f; + Vector3f colorVector = new Vector3f(colorR, colorG, colorB); + + float charXPointer = x; + for (int i = 0; i < str.length(); i++) { + int c = str.charAt(i); + // Only support ASCII + c = c >= 256 ? 0 : c; + + int xInTex = c % 16; + int yInTex = c / 16; + + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_VERTEX, new Vector2f(charXPointer, y), + new Vector2f(charXPointer + size, y), new Vector2f(charXPointer, y + size)); + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_UV, new Vector2f(xInTex / 16.0f, yInTex / 16.0f), + new Vector2f((xInTex + 1) / 16.0f, yInTex / 16.0f), + new Vector2f(xInTex / 16.0f, (yInTex + 1) / 16.0f)); + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_COLOR, colorVector, colorVector, colorVector); + + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_VERTEX, new Vector2f(charXPointer + size, y + size), + new Vector2f(charXPointer + size, y), new Vector2f(charXPointer, y + size)); + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_UV, new Vector2f((xInTex + 1) / 16.0f, (yInTex + 1) / 16.0f), + new Vector2f((xInTex + 1) / 16.0f, yInTex / 16.0f), + new Vector2f(xInTex / 16.0f, (yInTex + 1) / 16.0f)); + backBuffer.applyToBuffer(MyRenderBuffer2D.ATTR_COLOR, colorVector, colorVector, colorVector); + + charXPointer += size * CHAR_SIZES[c] / 8.0f; + charXPointer += size * separateRatio; + + } + + } + + public void tick() { + buffers.values().forEach(BackRenderBuffer::flip); + + renderer.setDisplayDimension(ctx.getWidth(), ctx.getHeight()); + renderer.render(); + + buffers.clear(); + + } + + public void release() { + renderer.release(); + + } + +} diff --git a/src/main/java/xueli/mcremake/registry/MojanglesFontWidthGenerator.java b/src/main/java/xueli/mcremake/registry/MojanglesFontWidthGenerator.java new file mode 100644 index 00000000..6bfe7622 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/MojanglesFontWidthGenerator.java @@ -0,0 +1,48 @@ +package xueli.mcremake.registry; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import xueli.game2.resource.Resource; +import xueli.game2.resource.provider.ClassLoaderResourceProvider; +import xueli.registry.Identifier; + +public class MojanglesFontWidthGenerator { + + public static void main(String[] args) throws IOException { + ClassLoaderResourceProvider resourceManager = new ClassLoaderResourceProvider(); + Resource resource = resourceManager.getResource(new Identifier("minecraft", "font/default.png")); + InputStream in = resource.openInputStream(); + BufferedImage image = ImageIO.read(in); + in.close(); + + int[] widths = new int[256]; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + one_image_search: for (int k = 7; k >= 0; k--) { + widths[i * 16 + j] = k; + for (int l = 0; l < 8; l++) { + int x = j * 8 + k; + int y = i * 8 + l; +// System.out.println(x + ", " + y); + if (image.getRGB(x, y) != 0) { + break one_image_search; + } + } + } + } + } + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + System.out.print((widths[i * 16 + j] + 1) + ", "); + } + System.out.println(); + } + + } + +} diff --git a/src/main/java/xueli/mcremake/registry/PocketNativeType.java b/src/main/java/xueli/mcremake/registry/PocketNativeType.java new file mode 100644 index 00000000..4b6ca913 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/PocketNativeType.java @@ -0,0 +1,7 @@ +package xueli.mcremake.registry; + +public @interface PocketNativeType { + String version() default "0.1.1"; + + String[] value(); +} diff --git a/src/main/java/xueli/mcremake/registry/TerrainTextureAtlas.java b/src/main/java/xueli/mcremake/registry/TerrainTextureAtlas.java new file mode 100644 index 00000000..a70db0c8 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/TerrainTextureAtlas.java @@ -0,0 +1,48 @@ +package xueli.mcremake.registry; + +import org.lwjgl.opengl.GL30; +import org.lwjgl.utils.vector.Vector2f; + +import xueli.game2.resource.ReloadableResourceTicket; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.game2.resource.submanager.render.texture.atlas.AtlasResourceHolder; +import xueli.mcremake.client.CraftGameClient; +import xueli.registry.Identifier; + +public class TerrainTextureAtlas implements ResourceHolder { + + public static Identifier TERRAIN_TEXTURE_LOCATION = new Identifier("minecraft", "terrain.png"); + +// private final CraftGameClient ctx; + private ReloadableResourceTicket textureId; + + public TerrainTextureAtlas(CraftGameClient ctx) { +// this.ctx = ctx; + this.textureId = ctx.textureResource.register(TERRAIN_TEXTURE_LOCATION, true); + + } + + @Override + public void reload() { + + } + + public void bind() { + GL30.glBindTexture(GL30.GL_TEXTURE_2D, textureId.get().id()); + } + + public void unbind() { + GL30.glBindTexture(GL30.GL_TEXTURE_2D, 0); + } + + public AtlasResourceHolder getUVVertex(int x, int y) { + return new AtlasResourceHolder(new Vector2f(x / 16.0f, y / 16.0f), + new Vector2f((x + 1) / 16.0f, (y + 1) / 16.0f)); + } + + public int getTextureId() { + return textureId.get().id(); + } + +} diff --git a/src/main/java/xueli/mcremake/registry/block/BlockCollidableSolid.java b/src/main/java/xueli/mcremake/registry/block/BlockCollidableSolid.java new file mode 100644 index 00000000..ce5baa1e --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/block/BlockCollidableSolid.java @@ -0,0 +1,26 @@ +package xueli.mcremake.registry.block; + +import java.util.List; + +import org.lwjgl.utils.vector.Vector3d; + +import xueli.game2.phys.aabb.AABB; +import xueli.game2.phys.aabb.NameableAABB; +import xueli.mcremake.core.block.BlockCollidable; +import xueli.mcremake.core.world.WorldAccessible; + +public class BlockCollidableSolid implements BlockCollidable { + + public static final String AABB_NAME = "cg:solid_only"; + + @Override + public void getCollisionAABBs(int x, int y, int z, WorldAccessible world, List aabbs) { + aabbs.add(new AABB(new Vector3d(x, y, z), new Vector3d(x + 1, y + 1, z + 1))); + } + + @Override + public void getPickTestAABBs(int x, int y, int z, WorldAccessible world, List aabbs) { + aabbs.add(new NameableAABB(AABB_NAME, new Vector3d(x, y, z), new Vector3d(x + 1, y + 1, z + 1))); + } + +} diff --git a/src/main/java/xueli/mcremake/registry/item/ItemRenderTypeRegularBlock.java b/src/main/java/xueli/mcremake/registry/item/ItemRenderTypeRegularBlock.java new file mode 100644 index 00000000..357d4637 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/item/ItemRenderTypeRegularBlock.java @@ -0,0 +1,43 @@ +package xueli.mcremake.registry.item; + +import java.util.HashMap; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.ecs.ResourceListImpl; +import xueli.game2.renderer.ui.NanoGui; +import xueli.game2.resource.ResourceHolder; +import xueli.game2.resource.submanager.render.texture.Texture; +import xueli.mcremake.client.BlockIconGenerator; +import xueli.mcremake.client.renderer.item.ItemRenderType; +import xueli.mcremake.core.block.BlockType; + +public class ItemRenderTypeRegularBlock implements ItemRenderType { + + private final BlockIconGenerator iconGenerator; + private final HashMap guiImages = new HashMap<>(); + + public ItemRenderTypeRegularBlock(ResourceListImpl renderResources) { + iconGenerator = renderResources.get(BlockIconGenerator.class); + } + + public void renderUI(BlockType block, CompoundMap tags, float x, float y, float width, float height, NanoGui gui) { + int image = this.guiImages.computeIfAbsent(iconGenerator.getTextureId(block.namespace()), gui::registerImage); +// System.out.println(image); + gui.drawImage(x, y, width, height, 1.0f, image); + } + + @Override + public void reload() { + // When reload GUI class will recreate NanoVG instance when our registered image + // gets deleted + // So here we just clear the HashMap + this.guiImages.clear(); + + } + + @Override + public void release() { + } + +} diff --git a/src/main/java/xueli/mcremake/registry/item/ItemRenderTypes.java b/src/main/java/xueli/mcremake/registry/item/ItemRenderTypes.java new file mode 100644 index 00000000..78d4fb4c --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/item/ItemRenderTypes.java @@ -0,0 +1,15 @@ +package xueli.mcremake.registry.item; + +import xueli.game2.ecs.ResourceListGeneric; +import xueli.game2.ecs.ResourceListImpl; +import xueli.game2.resource.ResourceHolder; +import xueli.mcremake.client.renderer.item.ItemRenderType; + +public class ItemRenderTypes extends ResourceListGeneric { + + public ItemRenderTypes(ResourceListImpl renderResources) { + this.add(new ItemRenderTypeRegularBlock(renderResources)); + + } + +} diff --git a/src/main/java/xueli/mcremake/registry/item/ItemRendererRegularBlock.java b/src/main/java/xueli/mcremake/registry/item/ItemRendererRegularBlock.java new file mode 100644 index 00000000..a4724777 --- /dev/null +++ b/src/main/java/xueli/mcremake/registry/item/ItemRendererRegularBlock.java @@ -0,0 +1,26 @@ +package xueli.mcremake.registry.item; + +import com.flowpowered.nbt.CompoundMap; + +import xueli.game2.renderer.ui.NanoGui; +import xueli.mcremake.client.renderer.item.ItemRenderManager; +import xueli.mcremake.client.renderer.item.ItemVertexGatherer; +import xueli.mcremake.core.block.BlockType; + +public class ItemRendererRegularBlock implements ItemVertexGatherer { + + private final BlockType blockType; + + public ItemRendererRegularBlock(BlockType blockType) { + this.blockType = blockType; + } + + @Override + public void renderUI(CompoundMap tags, float x, float y, float width, float height, ItemRenderManager manager, + NanoGui gui) { + var renderType = manager.getRenderType(ItemRenderTypeRegularBlock.class); + renderType.renderUI(blockType, tags, x, y, width, height, gui); + + } + +} diff --git a/src/main/java/xueli/mcremake/server/CraftGameServer.java b/src/main/java/xueli/mcremake/server/CraftGameServer.java new file mode 100644 index 00000000..aed509df --- /dev/null +++ b/src/main/java/xueli/mcremake/server/CraftGameServer.java @@ -0,0 +1,53 @@ +package xueli.mcremake.server; + +import xueli.game2.lifecycle.RunnableLifeCycle; +import xueli.game2.network.Server; +import xueli.mcremake.network.PacketSourceSide; + +public class CraftGameServer implements RunnableLifeCycle { + + private volatile boolean isRunning = false; + + private final Server server; + + public CraftGameServer(int port) { + this.server = new Server<>(port, () -> new MyServerConnection(this), PacketSourceSide.FROM_SERVER.getProtocol(), + PacketSourceSide.FROM_CLIENT.getProtocol()); + + } + + @Override + public void init() { + server.init(); + + this.isRunning = true; + + } + + @Override + public void tick() { + server.tick(); + + } + + @Override + public void release() { + server.release(); + + } + + public void stop() { + this.isRunning = false; + + } + + @Override + public boolean isRunning() { + return isRunning; + } + + public Server getServer() { + return server; + } + +} diff --git a/src/main/java/xueli/mcremake/server/MyServerConnection.java b/src/main/java/xueli/mcremake/server/MyServerConnection.java new file mode 100644 index 00000000..3739c559 --- /dev/null +++ b/src/main/java/xueli/mcremake/server/MyServerConnection.java @@ -0,0 +1,35 @@ +package xueli.mcremake.server; + +import xueli.game2.network.ConnectionStageListener; +import xueli.game2.network.Packet; +import xueli.game2.network.ServerClientConnection; +import xueli.game2.network.processor.PacketProcessor; + +@SuppressWarnings("unused") +public class MyServerConnection extends ServerClientConnection { + + private final CraftGameServer ctx; + + private final PacketProcessor packetProcessor = new PacketProcessor(); + + private ConnectionStageListener listener = new ServerStageHelloListener(this); + + public MyServerConnection(CraftGameServer ctx) { + this.ctx = ctx; + } + + @Override + protected void packetRead(Packet msg) { + if (listener != null) { + listener.doProcess(msg); + } else { + System.err.println("Bugs? No Packet Listener for: " + msg); + } + + } + + public void setListener(ConnectionStageListener listener) { + this.listener = listener; + } + +} diff --git a/src/main/java/xueli/mcremake/server/ServerStageHelloListener.java b/src/main/java/xueli/mcremake/server/ServerStageHelloListener.java new file mode 100644 index 00000000..d45b2819 --- /dev/null +++ b/src/main/java/xueli/mcremake/server/ServerStageHelloListener.java @@ -0,0 +1,26 @@ +package xueli.mcremake.server; + +import xueli.game2.network.ConnectionStageListener; +import xueli.mcremake.network.protocol.C00HelloPacket; +import xueli.mcremake.network.protocol.S00PlayPacket; + +public class ServerStageHelloListener extends ConnectionStageListener { + + public ServerStageHelloListener(MyServerConnection ctx) { + super(ctx); + processor.addProcessor(C00HelloPacket.class, this::handleHello); + + } + + private void handleHello(C00HelloPacket packet) { + try { + Thread.currentThread().wait(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + getConnection().writeAndFlush(new S00PlayPacket(0, 0, 0)); + + } + +} diff --git a/src/main/java/xueli/registry/Identifier.java b/src/main/java/xueli/registry/Identifier.java new file mode 100644 index 00000000..fb3c81d3 --- /dev/null +++ b/src/main/java/xueli/registry/Identifier.java @@ -0,0 +1,45 @@ +package xueli.registry; + +import java.util.Objects; + +public record Identifier(String namespace, String location) { + + public Identifier(String location) { + this("default", location); + } + + public static Identifier serialize(String str) { + str = str.trim(); + int separatorIndex = str.indexOf(':'); + if (separatorIndex < 0) { + return new Identifier(str); + } else { + String namespace = str.substring(0, separatorIndex); + String location = str.substring(separatorIndex); + if (namespace.isBlank()) + return new Identifier(location); + return new Identifier(namespace, location); + } + } + + @Override + public String toString() { + return namespace + ":" + location; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Identifier that = (Identifier) o; + return namespace.equals(that.namespace) && location.equals(that.location); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, location); + } + +} diff --git a/src/main/java/xueli/registry/Registry.java b/src/main/java/xueli/registry/Registry.java new file mode 100644 index 00000000..14e64970 --- /dev/null +++ b/src/main/java/xueli/registry/Registry.java @@ -0,0 +1,22 @@ +package xueli.registry; + +import java.util.Set; +import java.util.function.BiConsumer; + +public interface Registry { + + public T getByName(Identifier name); + + public T getById(int id); + + public int getId(T t); + + public Set getAllContainTag(Identifier tag); + + public Set getTags(Identifier name); + + public void forEach(BiConsumer c); + + public WritableRegistry cloneToWritable(); + +} diff --git a/src/main/java/xueli/registry/RegistryImplement.java b/src/main/java/xueli/registry/RegistryImplement.java new file mode 100644 index 00000000..679decfc --- /dev/null +++ b/src/main/java/xueli/registry/RegistryImplement.java @@ -0,0 +1,83 @@ +package xueli.registry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +public class RegistryImplement implements WritableRegistry { + + private ArrayList list = new ArrayList<>(); + private HashMap map = new HashMap<>(); + + private HashMap> tagToRegistryMap = new HashMap<>(); + private HashMap> registryToTagMap = new HashMap<>(); + + private boolean frozen = false; + + @Override + public T getByName(Identifier name) { + return map.get(name); + } + + @Override + public T getById(int id) { + return list.get(id); + } + + @Override + public int getId(T t) { + return list.indexOf(t); + } + + @Override + public void register(Identifier name, T t) { + if (frozen) + throw new IllegalStateException("Please call \"cloneToWritable\" to write to another clone object!"); + list.add(t); + map.put(name, t); + } + + @Override + public void addTag(Identifier name, Identifier... tags) { + HashSet thisRegistryTagSet = registryToTagMap.computeIfAbsent(name, n -> new HashSet<>()); + + for (int i = 0; i < tags.length; i++) { + Identifier tag = tags[i]; + thisRegistryTagSet.add(tag); + tagToRegistryMap.computeIfAbsent(tag, t -> new HashSet<>()).add(name); + } + + } + + @Override + public Set getAllContainTag(Identifier tag) { + return tagToRegistryMap.get(tag); + } + + @Override + public Set getTags(Identifier name) { + return registryToTagMap.get(name); + } + + @Override + public void forEach(BiConsumer c) { + map.forEach(c); + } + + @Override + public Registry freeze() { + this.frozen = true; + return this; + } + + @Override + public WritableRegistry cloneToWritable() { + RegistryImplement writable = new RegistryImplement<>(); + writable.list = new ArrayList<>(this.list); + writable.map = new HashMap<>(this.map); + return writable; + } + +} diff --git a/src/main/java/xueli/registry/WritableRegistry.java b/src/main/java/xueli/registry/WritableRegistry.java new file mode 100644 index 00000000..1724bc5e --- /dev/null +++ b/src/main/java/xueli/registry/WritableRegistry.java @@ -0,0 +1,11 @@ +package xueli.registry; + +public interface WritableRegistry extends Registry { + + public void register(Identifier name, T t); + + public void addTag(Identifier name, Identifier... tags); + + public Registry freeze(); + +} diff --git a/src/main/java/xueli/registry/package-info.java b/src/main/java/xueli/registry/package-info.java new file mode 100644 index 00000000..bf91cdf7 --- /dev/null +++ b/src/main/java/xueli/registry/package-info.java @@ -0,0 +1,5 @@ +/** + * When coding my UI framework I think maybe "registry" can be + * used anywhere. + */ +package xueli.registry; \ No newline at end of file diff --git a/src/main/java/xueli/swingx/PropertyListenableModel.java b/src/main/java/xueli/swingx/PropertyListenableModel.java new file mode 100644 index 00000000..8d2f44fb --- /dev/null +++ b/src/main/java/xueli/swingx/PropertyListenableModel.java @@ -0,0 +1,19 @@ +package xueli.swingx; + +import java.beans.PropertyChangeListener; + +import javax.swing.event.SwingPropertyChangeSupport; + +public class PropertyListenableModel { + + private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); + + public void firePropertyChange(String name) { + pcs.firePropertyChange(name, null, null); + } + + public void addPropertyChangeListener(String name, PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(name, listener); + } + +} diff --git a/src/main/java/xueli/swingx/Service.java b/src/main/java/xueli/swingx/Service.java new file mode 100755 index 00000000..48964ad6 --- /dev/null +++ b/src/main/java/xueli/swingx/Service.java @@ -0,0 +1,41 @@ +package xueli.swingx; + +import javax.swing.Timer; + +public abstract class Service { + + protected final T bean; + private final Timer updateTimer; + private boolean started = false; + + public Service(T bean, int intervalMs) { + this.bean = bean; + this.updateTimer = new Timer(intervalMs, e -> this.updateTimer()); + + } + + public void start() { + if (started) { + System.err.println("Start twice? " + getClass().getName()); + return; + } + + updateTimer.start(); + + this.started = true; + + } + + protected abstract void updateTimer(); + + public void stop() { + if (!started) { + System.out.println("Stop twice? " + getClass().getName()); + return; + } + + updateTimer.stop(); + + } + +} diff --git a/src/main/java/xueli/swingx/SwingUtils.java b/src/main/java/xueli/swingx/SwingUtils.java new file mode 100644 index 00000000..b9ee5a11 --- /dev/null +++ b/src/main/java/xueli/swingx/SwingUtils.java @@ -0,0 +1,15 @@ +package xueli.swingx; + +import java.awt.Dimension; +import java.awt.Toolkit; + +import javax.swing.JFrame; + +public class SwingUtils { + + public static void centerWindow(int width, int height, JFrame f) { + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + f.setBounds((screen.width - width) / 2, (screen.height - height) / 2, width, height); + } + +} diff --git a/src/main/java/xueli/swingx/component/FullScreenLayeredPane.java b/src/main/java/xueli/swingx/component/FullScreenLayeredPane.java new file mode 100644 index 00000000..c575dd26 --- /dev/null +++ b/src/main/java/xueli/swingx/component/FullScreenLayeredPane.java @@ -0,0 +1,25 @@ +package xueli.swingx.component; + +import java.awt.Component; + +import javax.swing.JLayeredPane; + +public class FullScreenLayeredPane extends JLayeredPane { + + private static final long serialVersionUID = 2353556941953154263L; + + public FullScreenLayeredPane() { + } + + @Override + public void setBounds(int x, int y, int width, int height) { + super.setBounds(x, y, width, height); + + Component[] cs = getComponents(); + for (int i = 0; i < cs.length; i++) { + cs[i].setBounds(0, 0, width, height); + } + + } + +} diff --git a/src/main/java/xueli/swingx/component/ImageView.java b/src/main/java/xueli/swingx/component/ImageView.java new file mode 100644 index 00000000..4b2a5748 --- /dev/null +++ b/src/main/java/xueli/swingx/component/ImageView.java @@ -0,0 +1,211 @@ +package xueli.swingx.component; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.Ellipse2D; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; + +public class ImageView extends JComponent { + + private static final long serialVersionUID = 3405064847459096540L; + + private BufferedImage image; + private boolean rounded = false; + private ImageRenderer renderer; + + private Dimension preferredSize; + + public ImageView(BufferedImage image) { + this(image, false); + } + + public ImageView(BufferedImage image, boolean rounded) { + this(image, rounded, StretchMode.UNIFORM_FILL.renderer); + } + + public ImageView(BufferedImage image, boolean rounded, StretchMode stretchMode) { + this(image, rounded, stretchMode.renderer); + } + + public ImageView(BufferedImage image, boolean rounded, ImageRenderer renderer) { + this.image = image; + this.rounded = rounded; + this.renderer = renderer; + this.setDoubleBuffered(true); + + } + + public void setImage(BufferedImage image) { + this.image = image; + repaint(); + } + + public void setRounded(boolean rounded) { + this.rounded = rounded; + repaint(); + } + + public void setRenderer(ImageRenderer renderer) { + this.renderer = renderer; + repaint(); + } + + public void setRenderer(StretchMode mode) { + this.setRenderer(mode.renderer); + } + + @Override + public void setPreferredSize(Dimension preferredSize) { + this.preferredSize = preferredSize; +// System.out.println(this.isValid()); + } + + @Override + public Dimension getPreferredSize() { + if (this.preferredSize != null) + return this.preferredSize; + return this.renderer.getPreferredSize(image, rounded); + } + + @Override + protected void paintComponent(Graphics g) { + int width = getWidth(); + int height = getHeight(); + +// System.out.println(width + ", " + height); + + Graphics2D g2d = (Graphics2D) g; + this.renderer.drawImage(g2d, image, width, height, rounded); + + } + + public static enum StretchMode { + // Stretch the image fully to the size + STRETCH(new ImageRenderer() { + + @Override + public void drawImage(Graphics2D g2d, BufferedImage image, int parentWidth, int parentHeight, + boolean round) { + if (round) { + g2d.setClip(new Ellipse2D.Double(0, 0, parentWidth, parentHeight)); + } + g2d.drawImage(image, 0, 0, parentWidth, parentHeight, null); + + } + + }), + + // Fill the image to avoid the empty area in the long side + FILL(new ImageRenderer() { + + @Override + public Dimension getPreferredSize(BufferedImage image, boolean round) { + int max = Math.max(image.getWidth(), image.getHeight()); + return new Dimension(max, max); + } + + @Override + public void drawImage(Graphics2D g2d, BufferedImage image, int parentWidth, int parentHeight, + boolean round) { + int offsetX = 0, offsetY = 0; + + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + double ratio = (double) imageWidth / imageHeight; + + if (parentWidth >= parentHeight) { + imageHeight = parentHeight; + imageWidth = (int) (parentHeight / ratio); + offsetY = (parentHeight - imageHeight) / 2; + } else { + imageHeight = parentHeight; + imageWidth = (int) (parentHeight * ratio); + offsetX = (parentWidth - imageWidth) / 2; + } + g2d.drawImage(image, offsetX, offsetY, imageWidth, imageHeight, null); + } + + }), + + // Fill the image to avoid the empty area in the short side + UNIFORM_FILL(new ImageRenderer() { + + @Override + public Dimension getPreferredSize(BufferedImage image, boolean round) { + int max = Math.max(image.getWidth(), image.getHeight()); + return new Dimension(max, max); + } + + @Override + public void drawImage(Graphics2D g2d, BufferedImage image, int parentWidth, int parentHeight, + boolean round) { + int offsetX = 0, offsetY = 0; + + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + double ratio = (double) imageWidth / imageHeight; + + if (parentWidth >= parentHeight) { + imageHeight = parentHeight; + imageWidth = (int) (parentHeight * ratio); + offsetX = (parentWidth - imageWidth) / 2; + } else { + imageWidth = parentWidth; + imageHeight = (int) (parentWidth / ratio); + offsetY = (parentHeight - imageHeight) / 2; + } + + if (round) { + g2d.setClip(new Ellipse2D.Double(offsetX, offsetY, imageWidth, imageHeight)); + } + g2d.drawImage(image, offsetX, offsetY, imageWidth, imageHeight, null); + + } + + }), + + CENTER_NO_STRETCH(new ImageRenderer() { + + @Override + public Dimension getPreferredSize(BufferedImage image, boolean round) { + int max = Math.max(image.getWidth(), image.getHeight()); + return new Dimension(max, max); + } + + @Override + public void drawImage(Graphics2D g2d, BufferedImage image, int parentWidth, int parentHeight, + boolean round) { + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + int offsetX = (parentWidth - imageWidth) / 2; + int offsetY = (parentHeight - imageHeight) / 2; + if (round) { + g2d.setClip(new Ellipse2D.Double(offsetX, offsetY, imageWidth, imageHeight)); + } + g2d.drawImage(image, offsetX, offsetY, imageWidth, imageHeight, null); + }; + + }); + + ImageRenderer renderer; + + private StretchMode(ImageRenderer renderer) { + this.renderer = renderer; + } + + } + + public static interface ImageRenderer { + + default public Dimension getPreferredSize(BufferedImage image, boolean round) { + return new Dimension(image.getWidth(), image.getHeight()); + } + + public void drawImage(Graphics2D g2d, BufferedImage image, int parentWidth, int parentHeight, boolean round); + + } + +} diff --git a/src/main/java/xueli/swingx/component/SimpleLayerUI.java b/src/main/java/xueli/swingx/component/SimpleLayerUI.java new file mode 100644 index 00000000..5a8cd13c --- /dev/null +++ b/src/main/java/xueli/swingx/component/SimpleLayerUI.java @@ -0,0 +1,69 @@ +package xueli.swingx.component; + +import java.awt.AlphaComposite; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; +import javax.swing.plaf.LayerUI; + +public class SimpleLayerUI extends LayerUI { + + private static final long serialVersionUID = 2083072094679992579L; + + private float alpha = 1.0f; + private float scale = 1.0f; + + public SimpleLayerUI() { + } + + @Override + public void paint(Graphics g, JComponent c) { + int width = c.getWidth(); + int height = c.getHeight(); + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D gTemp = image.createGraphics(); + super.paint(gTemp, c); + gTemp.dispose(); + + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, this.alpha)); + + float realWidth = width * scale; + float realHeight = height * scale; + float realX = (width - realWidth) / 2.0f; + float realY = (height - realHeight) / 2.0f; + g2d.drawImage(image, (int) realX, (int) realY, (int) realWidth, (int) realHeight, null); + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + + } + + public float getAlpha() { + return alpha; + } + + public void setAlpha(float alpha) { + float oldValue = this.alpha; + this.alpha = alpha; + firePropertyChange("alpha", oldValue, this.alpha); + } + + public float getScale() { + return scale; + } + + public void setScale(float scale) { + float oldValue = this.scale; + this.scale = scale; + firePropertyChange("scale", oldValue, this.alpha); + } + +} diff --git a/src/main/java/xueli/swingx/layout/CoverAllLayout.java b/src/main/java/xueli/swingx/layout/CoverAllLayout.java new file mode 100644 index 00000000..92d8dbb7 --- /dev/null +++ b/src/main/java/xueli/swingx/layout/CoverAllLayout.java @@ -0,0 +1,42 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager; + +public class CoverAllLayout implements LayoutManager { + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return null; + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return new Dimension(0, 0); + } + + @Override + public void layoutContainer(Container parent) { + synchronized (parent.getTreeLock()) { + int parentWidth = parent.getWidth(); + int parentHeight = parent.getHeight(); + Component[] cs = parent.getComponents(); + for (int i = 0; i < cs.length; i++) { + Component c = cs[i]; + c.setBounds(0, 0, parentWidth, parentHeight); + } + } + + } + +} diff --git a/src/main/java/xueli/swingx/layout/HorizontalFilledLayout.java b/src/main/java/xueli/swingx/layout/HorizontalFilledLayout.java new file mode 100644 index 00000000..31c8f33a --- /dev/null +++ b/src/main/java/xueli/swingx/layout/HorizontalFilledLayout.java @@ -0,0 +1,177 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager2; +import java.util.HashMap; + +import xueli.swingx.responsive.ValueProvider; + +/** + * This layout places all the component in a line horizontally with many mode to + * select. + * + */ +public class HorizontalFilledLayout implements LayoutManager2 { + + private final VerticalAlign verticalAlign; + private final HorizontalAlign horizentalAlign; + + private final HashMap> componentLayoutConstraintMap = new HashMap<>(); + + public HorizontalFilledLayout(VerticalAlign verticalAlign, HorizontalAlign horizentalAlign) { + this.verticalAlign = verticalAlign; + this.horizentalAlign = horizentalAlign; + + } + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void addLayoutComponent(Component comp, Object constraints) { + if (constraints instanceof Number n) { + componentLayoutConstraintMap.put(comp, w -> n.intValue()); + } else if (constraints instanceof String str) { + componentLayoutConstraintMap.put(comp, ValueProvider.parseInteger(str)); + } + } + + @Override + public void removeLayoutComponent(Component comp) { + componentLayoutConstraintMap.remove(comp); + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + int preferredWidth = 0, preferredHeight = 0; + synchronized (parent.getTreeLock()) { + Component[] components = parent.getComponents(); + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + if (!child.isVisible()) + continue; + Dimension childPreferredSize = child.getPreferredSize(); + preferredHeight = Math.max(preferredHeight, childPreferredSize.height); + preferredWidth += childPreferredSize.width; + } + } + return new Dimension(preferredWidth, preferredHeight); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return null; + } + + private static record LayoutResult(int relativeX, int relativeY, int width, int height) { + } + + @Override + public void layoutContainer(Container parent) { + synchronized (parent.getTreeLock()) { + int parentWidth = parent.getWidth(); + int parentHeight = parent.getHeight(); + + Component[] components = parent.getComponents(); + LayoutResult[] layoutResults = new LayoutResult[components.length]; + + // To make horizental align easy, we first pretend to combine the components to + // one panel. + // Calculate its relative layout to this "existed" panel. + // After the loop, this variable will be the sum width of all components + int pointerX = 0; + + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + if (!child.isVisible()) + continue; + + var widthProvider = componentLayoutConstraintMap.get(child); + Dimension childPreferredSize = child.getPreferredSize(); + + int childWidth; + if (widthProvider == null) { + childWidth = childPreferredSize.width; + } else { + childWidth = widthProvider.get(parentWidth); + } + + int childY = 0; + int childHeight = 0; + + switch (this.verticalAlign) { + case ALIGN_TOP -> { + childHeight = childPreferredSize.height; + childY = 0; + } + case ALIGN_CENTER -> { + childHeight = childPreferredSize.height; + childY = (parentHeight - childHeight) / 2; + } + case ALIGN_BOTTOM -> { + childHeight = childPreferredSize.height; + childY = parentHeight - childHeight; + } + case FILL -> { + childHeight = parentHeight; + childY = 0; + } + } + + layoutResults[i] = new LayoutResult(pointerX, childY, childWidth, childHeight); + pointerX += childWidth; + } + + int layoutStartX = switch (this.horizentalAlign) { + case ALIGN_LEFT -> 0; + case CENTER -> (parentWidth - pointerX) / 2; + case ALIGN_RIGHT -> parentWidth - pointerX; + }; + + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + LayoutResult layoutResult = layoutResults[i]; + if (layoutResult == null) + continue; + child.setBounds(layoutStartX + layoutResult.relativeX, layoutResult.relativeY, layoutResult.width, + layoutResult.height); + } + + } + + } + + @Override + public Dimension maximumLayoutSize(Container target) { + return null; + } + + @Override + public float getLayoutAlignmentX(Container target) { + return 0; + } + + @Override + public float getLayoutAlignmentY(Container target) { + return 0; + } + +// int i = 0; + @Override + public void invalidateLayout(Container target) { +// System.out.println("[INVALIDATE] " + (i++)); +// this.layoutContainer(target); + } + + public static enum HorizontalAlign { + ALIGN_LEFT, CENTER, ALIGN_RIGHT, + } + + public static enum VerticalAlign { + ALIGN_TOP, ALIGN_CENTER, ALIGN_BOTTOM, FILL, + } + +} diff --git a/src/main/java/xueli/swingx/layout/LayoutListener.java b/src/main/java/xueli/swingx/layout/LayoutListener.java new file mode 100644 index 00000000..74336b7c --- /dev/null +++ b/src/main/java/xueli/swingx/layout/LayoutListener.java @@ -0,0 +1,91 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; + +/** + * The aim to write this listener is to make use of the "layoutContainer" + * method, because when the father's "layoutContainer" is called, its size is + * adjusted, but all of its children's size is not calculated. So to make + * responsive layout we can slide in this golden opportunity. + */ +public abstract class LayoutListener implements LayoutManager2 { + + private final LayoutManager layoutManager; + + public LayoutListener(LayoutManager layoutManager) { + this.layoutManager = layoutManager; + } + + @Override + public void addLayoutComponent(String name, Component comp) { + layoutManager.addLayoutComponent(name, comp); + } + + @Override + public void removeLayoutComponent(Component comp) { + layoutManager.removeLayoutComponent(comp); + + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return layoutManager.preferredLayoutSize(parent); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return layoutManager.minimumLayoutSize(parent); + } + + @Override + public void layoutContainer(Container parent) { + this.beforeLayout(); + layoutManager.layoutContainer(parent); + + } + + protected abstract void beforeLayout(); + + @Override + public void addLayoutComponent(Component comp, Object constraints) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.addLayoutComponent(comp, constraints); + } + } + + @Override + public Dimension maximumLayoutSize(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.maximumLayoutSize(target); + } + return null; + } + + @Override + public float getLayoutAlignmentX(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.getLayoutAlignmentX(target); + } + return 0; + } + + @Override + public float getLayoutAlignmentY(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.getLayoutAlignmentY(target); + } + return 0; + } + + @Override + public void invalidateLayout(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.invalidateLayout(target); + } + } + +} diff --git a/src/main/java/xueli/swingx/layout/OffsetLayout.java b/src/main/java/xueli/swingx/layout/OffsetLayout.java new file mode 100644 index 00000000..a7583383 --- /dev/null +++ b/src/main/java/xueli/swingx/layout/OffsetLayout.java @@ -0,0 +1,123 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; +import java.awt.Point; +import java.util.HashMap; + +public class OffsetLayout implements LayoutManager2 { + + private final LayoutManager layoutManager; + + private final HashMap originPositions = new HashMap<>(); + private final HashMap offsets = new HashMap<>(); + + public OffsetLayout(LayoutManager layoutManager) { + this.layoutManager = layoutManager; + } + + @Override + public void addLayoutComponent(String name, Component comp) { + layoutManager.addLayoutComponent(name, comp); + } + + @Override + public void removeLayoutComponent(Component comp) { + layoutManager.removeLayoutComponent(comp); + + originPositions.remove(comp); + offsets.remove(comp); + + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return layoutManager.preferredLayoutSize(parent); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return layoutManager.minimumLayoutSize(parent); + } + + @Override + public void layoutContainer(Container parent) { + layoutManager.layoutContainer(parent); + + synchronized (parent.getTreeLock()) { + Component[] cs = parent.getComponents(); + for (int i = 0; i < cs.length; i++) { + Component c = cs[i]; + Point originLocation = c.getLocation(); + originPositions.put(c, originLocation); + + Point offset = offsets.get(c); + if (offset != null) { + c.setLocation(originLocation.x + offset.x, originLocation.y + offset.y); + } + + } + } + + } + + public void setOffset(Component c, Point offset) { + if (offset == null) + offset = new Point(0, 0); + this.setOffset(c, offset.x, offset.y); + } + + public void setOffset(Component c, int x, int y) { + offsets.put(c, new Point(x, y)); + Point originLocation = originPositions.get(c); + if (originLocation != null) { + c.setLocation(originLocation.x + x, originLocation.y + y); + } + } + + public Point getOffset(Component c) { + return offsets.get(c); + } + + @Override + public void addLayoutComponent(Component comp, Object constraints) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.addLayoutComponent(comp, constraints); + } + } + + @Override + public Dimension maximumLayoutSize(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.maximumLayoutSize(target); + } + return null; + } + + @Override + public float getLayoutAlignmentX(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.getLayoutAlignmentX(target); + } + return 0; + } + + @Override + public float getLayoutAlignmentY(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.getLayoutAlignmentY(target); + } + return 0; + } + + @Override + public void invalidateLayout(Container target) { + if (layoutManager instanceof LayoutManager2 lm2) { + lm2.invalidateLayout(target); + } + } + +} diff --git a/src/main/java/xueli/swingx/layout/VFlowLayout.java b/src/main/java/xueli/swingx/layout/VFlowLayout.java new file mode 100644 index 00000000..36bc4d8f --- /dev/null +++ b/src/main/java/xueli/swingx/layout/VFlowLayout.java @@ -0,0 +1,288 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Insets; + +/** + * From: https://www.iteye.com/blog/tianqiushi-2343561 + * + * VerticalFlowLayout is similar to FlowLayout except it lays out components + * vertically. Extends FlowLayout because it mimics much of the behavior of the + * FlowLayout class, except vertically. An additional feature is that you can + * specify a fill to edge flag, which causes the VerticalFlowLayout manager to + * resize all components to expand to the column width Warning: This causes + * problems when the main panel has less space that it needs and it seems to + * prohibit multi-column output. Additionally there is a vertical fill flag, + * which fills the last component to the remaining height of the container. + */ +public class VFlowLayout extends FlowLayout { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Specify alignment top. + */ + public static final int TOP = 0; + + /** + * Specify a middle alignment. + */ + public static final int MIDDLE = 1; + + /** + * Specify the alignment to be bottom. + */ + public static final int BOTTOM = 2; + + int hgap; + int vgap; + boolean hfill; + boolean vfill; + + /** + * Construct a new VerticalFlowLayout with a middle alignment, and the fill to + * edge flag set. + */ + public VFlowLayout() { + this(TOP, 5, 5, true, false); + } + + /** + * Construct a new VerticalFlowLayout with a middle alignment. + * + * @param hfill the fill to edge flag + * @param vfill the vertical fill in pixels. + */ + public VFlowLayout(boolean hfill, boolean vfill) { + this(TOP, 5, 5, hfill, vfill); + } + + /** + * Construct a new VerticalFlowLayout with a middle alignment. + * + * @param align the alignment value + */ + public VFlowLayout(int align) { + this(align, 5, 5, true, false); + } + + /** + * Construct a new VerticalFlowLayout. + * + * @param align the alignment value + * @param hfill the horizontalfill in pixels. + * @param vfill the vertical fill in pixels. + */ + public VFlowLayout(int align, boolean hfill, boolean vfill) { + this(align, 5, 5, hfill, vfill); + } + + /** + * Construct a new VerticalFlowLayout. + * + * @param align the alignment value + * @param hgap the horizontal gap variable + * @param vgap the vertical gap variable + * @param hfill the fill to edge flag + * @param vfill true if the panel should vertically fill. + */ + public VFlowLayout(int align, int hgap, int vgap, boolean hfill, boolean vfill) { + setAlignment(align); + this.hgap = hgap; + this.vgap = vgap; + this.hfill = hfill; + this.vfill = vfill; + } + + /** + * Returns the preferred dimensions given the components in the target + * container. + * + * @param target the component to lay out + */ + public Dimension preferredLayoutSize(Container target) { + Dimension tarsiz = new Dimension(0, 0); + + for (int i = 0; i < target.getComponentCount(); i++) { + Component m = target.getComponent(i); + + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + tarsiz.width = Math.max(tarsiz.width, d.width); + + if (i > 0) { + tarsiz.height += hgap; + } + + tarsiz.height += d.height; + } + } + + Insets insets = target.getInsets(); + tarsiz.width += insets.left + insets.right + hgap * 2; + tarsiz.height += insets.top + insets.bottom + vgap * 2; + + return tarsiz; + } + + /** + * Returns the minimum size needed to layout the target container. + * + * @param target the component to lay out. + * @return the minimum layout dimension. + */ + public Dimension minimumLayoutSize(Container target) { + Dimension tarsiz = new Dimension(0, 0); + + for (int i = 0; i < target.getComponentCount(); i++) { + Component m = target.getComponent(i); + + if (m.isVisible()) { + Dimension d = m.getMinimumSize(); + tarsiz.width = Math.max(tarsiz.width, d.width); + + if (i > 0) { + tarsiz.height += vgap; + } + + tarsiz.height += d.height; + } + } + + Insets insets = target.getInsets(); + tarsiz.width += insets.left + insets.right + hgap * 2; + tarsiz.height += insets.top + insets.bottom + vgap * 2; + + return tarsiz; + } + + /** + * Set true to fill vertically. + * + * @param vfill true to fill vertically. + */ + public void setVerticalFill(boolean vfill) { + this.vfill = vfill; + } + + /** + * Returns true if the layout vertically fills. + * + * @return true if vertically fills the layout using the specified. + */ + public boolean getVerticalFill() { + return vfill; + } + + /** + * Set to true to enable horizontally fill. + * + * @param hfill true to fill horizontally. + */ + public void setHorizontalFill(boolean hfill) { + this.hfill = hfill; + } + + /** + * Returns true if the layout horizontally fills. + * + * @return true if horizontally fills. + */ + public boolean getHorizontalFill() { + return hfill; + } + + /** + * places the components defined by first to last within the target container + * using the bounds box defined. + * + * @param target the container. + * @param x the x coordinate of the area. + * @param y the y coordinate of the area. + * @param width the width of the area. + * @param height the height of the area. + * @param first the first component of the container to place. + * @param last the last component of the container to place. + */ + private void placethem(Container target, int x, int y, int width, int height, int first, int last) { + int align = getAlignment(); + + if (align == MIDDLE) { + y += height / 2; + } + + if (align == BOTTOM) { + y += height; + } + + for (int i = first; i < last; i++) { + Component m = target.getComponent(i); + Dimension md = m.getSize(); + + if (m.isVisible()) { + int px = x + (width - md.width) / 2; + m.setLocation(px, y); + y += vgap + md.height; + } + } + } + + /** + * Lays out the container. + * + * @param target the container to lay out. + */ + public void layoutContainer(Container target) { + Insets insets = target.getInsets(); + int maxheight = target.getSize().height - (insets.top + insets.bottom + vgap * 2); + int maxwidth = target.getSize().width - (insets.left + insets.right + hgap * 2); + int numcomp = target.getComponentCount(); + int x = insets.left + hgap, y = 0; + int colw = 0, start = 0; + + for (int i = 0; i < numcomp; i++) { + Component m = target.getComponent(i); + + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + + // fit last component to remaining height + if ((this.vfill) && (i == (numcomp - 1))) { + d.height = Math.max((maxheight - y), m.getPreferredSize().height); + } + + // fit component size to container width + if (this.hfill) { + m.setSize(maxwidth, d.height); + d.width = maxwidth; + } else { + m.setSize(d.width, d.height); + } + + if (y + d.height > maxheight) { + placethem(target, x, insets.top + vgap, colw, maxheight - y, start, i); + y = d.height; + x += hgap + colw; + colw = d.width; + start = i; + } else { + if (y > 0) { + y += vgap; + } + + y += d.height; + colw = Math.max(colw, d.width); + } + } + } + + placethem(target, x, insets.top + vgap, colw, maxheight - y, start, numcomp); + } + +} diff --git a/src/main/java/xueli/swingx/layout/VerticalFilledLayout.java b/src/main/java/xueli/swingx/layout/VerticalFilledLayout.java new file mode 100755 index 00000000..b0c1db06 --- /dev/null +++ b/src/main/java/xueli/swingx/layout/VerticalFilledLayout.java @@ -0,0 +1,173 @@ +package xueli.swingx.layout; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.LayoutManager2; +import java.util.HashMap; + +import xueli.swingx.responsive.ValueProvider; + +/** + * This layout places all the component in a line horizontally with many mode to + * select. + * + */ +public class VerticalFilledLayout implements LayoutManager2 { + + private final HorizontalAlign horizontalAlign; + private final VerticalAlign verticalAlign; + + private final HashMap> componentLayoutConstraintMap = new HashMap<>(); + + public VerticalFilledLayout(HorizontalAlign horizentalAlign, VerticalAlign verticalAlign) { + this.horizontalAlign = horizentalAlign; + this.verticalAlign = verticalAlign; + + } + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void addLayoutComponent(Component comp, Object constraints) { + if (constraints instanceof Number n) { + componentLayoutConstraintMap.put(comp, w -> n.intValue()); + } else if (constraints instanceof String str) { + componentLayoutConstraintMap.put(comp, ValueProvider.parseInteger(str)); + } + } + + @Override + public void removeLayoutComponent(Component comp) { + componentLayoutConstraintMap.remove(comp); + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + int preferredHeight = 0, preferredWidth = 0; + synchronized (parent.getTreeLock()) { + Component[] components = parent.getComponents(); + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + if (!child.isVisible()) + continue; + Dimension childPreferredSize = child.getPreferredSize(); + preferredWidth = Math.max(preferredWidth, childPreferredSize.width); + preferredHeight += childPreferredSize.height; + } + } + return new Dimension(preferredWidth, preferredHeight); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return null; + } + + private static record LayoutResult(int relativeX, int relativeY, int width, int height) { + } + + @Override + public void layoutContainer(Container parent) { + synchronized (parent.getTreeLock()) { + int parentWidth = parent.getWidth(); + int parentHeight = parent.getHeight(); + + Component[] components = parent.getComponents(); + LayoutResult[] layoutResults = new LayoutResult[components.length]; + + // To make horizental align easy, we first pretend to combine the components to + // one panel. + // Calculate its relative layout to this "existed" panel. + // After the loop, this variable will be the sum width of all components + int pointerY = 0; + + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + if (!child.isVisible()) + continue; + var heightProvider = componentLayoutConstraintMap.get(child); + Dimension childPreferredSize = child.getPreferredSize(); + + int childHeight; + if (heightProvider == null) { + childHeight = childPreferredSize.height; + } else { + childHeight = heightProvider.get(parentWidth); + } + + int childX = 0; + int childWidth = 0; + + switch (this.horizontalAlign) { + case ALIGN_LEFT -> { + childWidth = childPreferredSize.width; + childX = 0; + } + case ALIGN_CENTER -> { + childWidth = childPreferredSize.width; + childX = (parentWidth - childWidth) / 2; + } + case ALIGN_RIGHT -> { + childWidth = childPreferredSize.width; + childX = parentWidth - childWidth; + } + case FILL -> { + childWidth = parentWidth; + childX = 0; + } + } + + layoutResults[i] = new LayoutResult(childX, pointerY, childWidth, childHeight); + pointerY += childHeight; + } + + int layoutStartY = switch (this.verticalAlign) { + case ALIGN_TOP -> 0; + case CENTER -> (parentHeight - pointerY) / 2; + case ALIGN_BOTTOM -> parentHeight - pointerY; + }; + + for (int i = 0; i < components.length; i++) { + Component child = components[i]; + LayoutResult layoutResult = layoutResults[i]; + if (layoutResult == null) + continue; + child.setBounds(layoutResult.relativeX, layoutStartY + layoutResult.relativeY, layoutResult.width, + layoutResult.height); + } + + } + + } + + @Override + public Dimension maximumLayoutSize(Container target) { + return null; + } + + @Override + public float getLayoutAlignmentX(Container target) { + return 0; + } + + @Override + public float getLayoutAlignmentY(Container target) { + return 0; + } + + @Override + public void invalidateLayout(Container target) { + } + + public static enum VerticalAlign { + ALIGN_TOP, CENTER, ALIGN_BOTTOM, + } + + public static enum HorizontalAlign { + ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, FILL, + } + +} diff --git a/src/main/java/xueli/swingx/responsive/AnimationValueProvider.java b/src/main/java/xueli/swingx/responsive/AnimationValueProvider.java new file mode 100644 index 00000000..e624ce95 --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/AnimationValueProvider.java @@ -0,0 +1,9 @@ +package xueli.swingx.responsive; + +public interface AnimationValueProvider { + + public K getStart(); + + public K getEnd(); + +} diff --git a/src/main/java/xueli/swingx/responsive/ComponentAnimatorMap.java b/src/main/java/xueli/swingx/responsive/ComponentAnimatorMap.java new file mode 100644 index 00000000..e81989af --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/ComponentAnimatorMap.java @@ -0,0 +1,8 @@ +package xueli.swingx.responsive; + +import java.awt.Container; + +import xueli.animation.AnimationManager; + +record ComponentAnimatorMap(Container container, AnimationManager animator) { +} diff --git a/src/main/java/xueli/swingx/responsive/PropertyAccessible.java b/src/main/java/xueli/swingx/responsive/PropertyAccessible.java new file mode 100644 index 00000000..bfc8ab8a --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/PropertyAccessible.java @@ -0,0 +1,70 @@ +package xueli.swingx.responsive; + +import javax.swing.JLabel; + +import xueli.swingx.component.SimpleLayerUI; + +public interface PropertyAccessible { + + public T get(C comp); + + public void set(C comp, T val); + + public static PropertyAccessible fontAccessible(JLabel label) { + return new PropertyAccessible() { + @Override + public Double get(JLabel comp) { + return (double) comp.getFont().getSize(); + } + + @Override + public void set(JLabel comp, Double val) { + comp.setFont(comp.getFont().deriveFont(val.floatValue())); + + } + }; + } + +// public static PropertyAccessible fontAccessible(Label label) { +// return new PropertyAccessible() { +// @Override +// public Double get(Label comp) { +// return (double) comp.getFont().getSize(); +// } +// +// @Override +// public void set(Label comp, Double val) { +// comp.setFont(comp.getFont().deriveFont(val.floatValue())); +// } +// }; +// } + + public static PropertyAccessible layerAlphaAccessible(SimpleLayerUI ui) { + return new PropertyAccessible() { + @Override + public Double get(SimpleLayerUI comp) { + return (double) comp.getAlpha(); + } + + @Override + public void set(SimpleLayerUI comp, Double val) { + comp.setAlpha(val.floatValue()); + } + }; + } + + public static PropertyAccessible layerScaleAccessible(SimpleLayerUI ui) { + return new PropertyAccessible() { + @Override + public Double get(SimpleLayerUI comp) { + return (double) comp.getScale(); + } + + @Override + public void set(SimpleLayerUI comp, Double val) { + comp.setScale(val.floatValue()); + } + }; + } + +} diff --git a/src/main/java/xueli/swingx/responsive/Responsive.java b/src/main/java/xueli/swingx/responsive/Responsive.java new file mode 100644 index 00000000..95bea5c8 --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/Responsive.java @@ -0,0 +1,44 @@ +package xueli.swingx.responsive; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.util.HashMap; + +import xueli.swingx.layout.LayoutListener; + +@Deprecated +public class Responsive { + + private static final HashMap responsives = new HashMap<>(); + + public static void addResponsive(Component child, ValueProvider provider) { + Container father = child.getParent(); + if (father == null) { + System.err.println("No Father? " + child.toString()); + return; + } + responsives.computeIfAbsent(father, Responsive::new).addResponsiveChild(child, provider); + + } + + private final HashMap> responsiveData = new HashMap<>(); + + Responsive(Container father) { + father.setLayout(new LayoutListener(father.getLayout()) { + @Override + protected void beforeLayout() { + Dimension fatherSize = father.getSize(); + responsiveData.forEach((c, v) -> { + c.setPreferredSize(v.get(fatherSize)); + }); + } + }); + } + + public Responsive addResponsiveChild(Component child, ValueProvider provider) { + responsiveData.put(child, provider); + return this; + } + +} diff --git a/src/main/java/xueli/swingx/responsive/TransitionBindings.java b/src/main/java/xueli/swingx/responsive/TransitionBindings.java new file mode 100644 index 00000000..84635627 --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/TransitionBindings.java @@ -0,0 +1,94 @@ +package xueli.swingx.responsive; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; + +import xueli.animation.TransitionBinding; + +public class TransitionBindings { + + public static TransitionBinding newBindingDimension(Component component, + ValueProvider sizeProvider) { + return new TransitionBinding() { + private Dimension oldSize, newSize; + + @Override + public void animStart() { + Container parent = component.getParent(); + Dimension parentSize = parent.getSize(); + + oldSize = component.getPreferredSize(); + if (oldSize == null) + oldSize = new Dimension(); + newSize = sizeProvider.get(parentSize); + + super.animStart(); + + } + + @Override + public void animProgress(double val) { + component.setPreferredSize(new Dimension((int) (oldSize.width + (newSize.width - oldSize.width) * val), + (int) (oldSize.height + (newSize.height - oldSize.height) * val))); + // https://www.dovov.com/swing-guivalidaterevalidateinvalidate.html + component.revalidate(); // But it can cause lag because every time it will call to layout all the + // components + + } + }; + } + + public static TransitionBinding newBindingNumber(C component, + ValueProvider valueProvider, PropertyAccessible accessible) { + return new TransitionBinding() { + + private double oldValue, newValue; + + @Override + public void animStart() { + Container parent = component.getParent(); + Dimension parentSize = parent.getSize(); + + Double oldValueObj = accessible.get(component); + if (oldValueObj == null) + oldValue = 0; + else + oldValue = oldValueObj; + newValue = valueProvider.get(parentSize); + + super.animStart(); + + } + + @Override + public void animProgress(double val) { + accessible.set(component, (oldValue + (newValue - oldValue) * val)); + } + + }; + } + + public static TransitionBinding newBindingNumber(C c, AnimationValueProvider valueProvider, + PropertyAccessible accessible) { + return new TransitionBinding() { + + private double oldValue, newValue; + + @Override + public void animStart() { + this.oldValue = valueProvider.getStart(); + this.newValue = valueProvider.getEnd(); + super.animStart(); + + } + + @Override + public void animProgress(double val) { + accessible.set(c, (oldValue + (newValue - oldValue) * val)); + } + + }; + } + +} diff --git a/src/main/java/xueli/swingx/responsive/ValueProvider.java b/src/main/java/xueli/swingx/responsive/ValueProvider.java new file mode 100644 index 00000000..ad9a030c --- /dev/null +++ b/src/main/java/xueli/swingx/responsive/ValueProvider.java @@ -0,0 +1,119 @@ +package xueli.swingx.responsive; + +import java.awt.Component; +import java.awt.Dimension; + +public interface ValueProvider { + + public K get(T parent); + + public static ValueProvider parseDouble(String str) { + try { + Double val = Double.parseDouble(str); + return p -> val; + } catch (NumberFormatException e) { + } + + if (str.endsWith("px")) { + double value = Double.valueOf(str.substring(0, str.length() - 2)); + return w -> value; + } else if (str.endsWith("%")) { + double value = Double.valueOf(str.substring(0, str.length() - 1)); + return w -> w * value / 100.0; + } + + throw new NumberFormatException(); + } + + public static ValueProvider parseInteger(String str) { + try { + Integer val = Integer.parseInt(str); + return p -> val; + } catch (NumberFormatException e) { + } + + if (str.endsWith("px")) { + Integer value = Integer.valueOf(str.substring(0, str.length() - 2)); + return w -> value; + } else if (str.endsWith("%")) { + double value = Double.valueOf(str.substring(0, str.length() - 1)); + return w -> (int) (w * value / 100.0); + } + + throw new NumberFormatException(); + + } + + public static ValueProvider vminForDouble(double percentage, Component reference) { + if (reference == null) + return d -> percentage * Math.min(d.width, d.height) / 100.0; + else + return d -> { + Dimension referenceSize = reference.getSize(); + return percentage * Math.min(referenceSize.width, referenceSize.height) / 100.0; + }; + } + + public static ValueProvider vmaxForDouble(double percentage, Component reference) { + if (reference == null) + return d -> percentage * Math.max(d.width, d.height) / 100.0; + else + return d -> { + Dimension referenceSize = reference.getSize(); + return percentage * Math.max(referenceSize.width, referenceSize.height) / 100.0; + }; + } + + // The ratio should be the result of width / height + public static ValueProvider newProviderRatioHeight( + ValueProvider heightProvider, double ratio) { + return d -> { + int height = heightProvider.get(d.height); + int width = (int) (height * ratio); + return new Dimension(width, height); + }; + } + + // The ratio should be the result of width / height + public static ValueProvider newProviderRatioWidth( + ValueProvider widthProvider, double ratio) { + return d -> { + int width = widthProvider.get(d.height); + int height = (int) (width / ratio); + return new Dimension(width, height); + }; + } + + // The ratio should be the result of width / height + public static ValueProvider newProviderRatioVMin(ValueProvider minProvider, + double ratio) { + return d -> { + int childWidth, childHeight; + if (d.width >= d.height) { + childHeight = minProvider.get(d.height); + childWidth = (int) (childHeight * ratio); + } else { + childWidth = minProvider.get(d.width); + childHeight = (int) (childWidth / ratio); + } + return new Dimension(childWidth, childHeight); + }; + } + + // The ratio should be the result of width / height + public static ValueProvider newProviderRatioVMax(ValueProvider maxProvider, + double ratio) { + return d -> { + int childWidth, childHeight; + if (d.width <= d.height) { + childHeight = maxProvider.get(d.height); + childWidth = (int) (childHeight * ratio); + } else { + childWidth = maxProvider.get(d.width); + childHeight = (int) (childWidth / ratio); + } + return new Dimension(childWidth, childHeight); + }; + } + +} diff --git a/src/main/java/xueli/utils/Bytes.java b/src/main/java/xueli/utils/Bytes.java new file mode 100644 index 00000000..441d53c4 --- /dev/null +++ b/src/main/java/xueli/utils/Bytes.java @@ -0,0 +1,119 @@ +package xueli.utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class Bytes { + + public static byte[] getBytes(short data) { + byte[] bytes = new byte[2]; + bytes[0] = (byte) (data & 0xff); + bytes[1] = (byte) ((data & 0xff00) >> 8); + return bytes; + } + + public static byte[] getBytes(char data) { + byte[] bytes = new byte[2]; + bytes[0] = (byte) (data); + bytes[1] = (byte) (data >> 8); + return bytes; + } + + public static byte[] getBytes(int data) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) (data & 0xff); + bytes[1] = (byte) ((data & 0xff00) >> 8); + bytes[2] = (byte) ((data & 0xff0000) >> 16); + bytes[3] = (byte) ((data & 0xff000000) >> 24); + return bytes; + } + + public static byte[] getBytes(long data) { + byte[] bytes = new byte[8]; + bytes[0] = (byte) (data & 0xff); + bytes[1] = (byte) ((data >> 8) & 0xff); + bytes[2] = (byte) ((data >> 16) & 0xff); + bytes[3] = (byte) ((data >> 24) & 0xff); + bytes[4] = (byte) ((data >> 32) & 0xff); + bytes[5] = (byte) ((data >> 40) & 0xff); + bytes[6] = (byte) ((data >> 48) & 0xff); + bytes[7] = (byte) ((data >> 56) & 0xff); + return bytes; + } + + public static byte[] getBytes(float data) { + int intBits = Float.floatToIntBits(data); + return getBytes(intBits); + } + + public static byte[] getBytes(double data) { + long intBits = Double.doubleToLongBits(data); + return getBytes(intBits); + } + + public static byte[] getBytes(Object object) { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + + try { + ObjectOutputStream oo = new ObjectOutputStream(o); + oo.writeObject(object); + oo.flush(); + oo.close(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + return o.toByteArray(); + } + + public static short getShort(byte[] bytes) { + return (short) ((0xff & bytes[0]) | (0xff00 & (bytes[1] << 8))); + } + + public static char getChar(byte[] bytes) { + return (char) ((0xff & bytes[0]) | (0xff00 & (bytes[1] << 8))); + } + + public static int getInt(byte[] bytes) { + return (0xff & bytes[0]) | (0xff00 & (bytes[1] << 8)) | (0xff0000 & (bytes[2] << 16)) + | (0xff000000 & (bytes[3] << 24)); + } + + public static long getLong(byte[] bytes) { + return (0xffL & (long) bytes[0]) | (0xff00L & ((long) bytes[1] << 8)) | (0xff0000L & ((long) bytes[2] << 16)) + | (0xff000000L & ((long) bytes[3] << 24)) | (0xff00000000L & ((long) bytes[4] << 32)) + | (0xff0000000000L & ((long) bytes[5] << 40)) | (0xff000000000000L & ((long) bytes[6] << 48)) + | (0xff00000000000000L & ((long) bytes[7] << 56)); + } + + public static float getFloat(byte[] bytes) { + return Float.intBitsToFloat(getInt(bytes)); + } + + public static double getDouble(byte[] bytes) { + long l = getLong(bytes); + System.out.println(l); + return Double.longBitsToDouble(l); + } + + public static Object getObject(byte[] bytes) { + ByteArrayInputStream i = new ByteArrayInputStream(bytes); + Object object = null; + + try { + ObjectInputStream in = new ObjectInputStream(i); + object = in.readObject(); + + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + + return object; + } + +} diff --git a/src/main/java/xueli/utils/Codepoint.java b/src/main/java/xueli/utils/Codepoint.java new file mode 100644 index 00000000..e1f2fbd9 --- /dev/null +++ b/src/main/java/xueli/utils/Codepoint.java @@ -0,0 +1,9 @@ +package xueli.utils; + +public class Codepoint { + + public static String codepointToString(int codepoint) { + return new String(Character.toChars(codepoint)); + } + +} diff --git a/src/main/java/xueli/utils/ExecutorThisThread.java b/src/main/java/xueli/utils/ExecutorThisThread.java new file mode 100644 index 00000000..eb365037 --- /dev/null +++ b/src/main/java/xueli/utils/ExecutorThisThread.java @@ -0,0 +1,57 @@ +package xueli.utils; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +public class ExecutorThisThread { + + private ConcurrentLinkedQueue runnables = new ConcurrentLinkedQueue<>(); + + public ExecutorThisThread() { + } + + @Deprecated + public Callable execute(Runnable command) { + // Block the thread when "get" the result from another thread + LinkedBlockingDeque blockingDeque = new LinkedBlockingDeque<>(); + Callable callable = () -> blockingDeque.poll(Long.MAX_VALUE, TimeUnit.DAYS); + runnables.add(() -> { + command.run(); + blockingDeque.add(new Object()); + }); + return callable; + } + + @Deprecated + public Callable execute(Callable callable) { + LinkedBlockingDeque blockingDeque = new LinkedBlockingDeque<>(); + Callable newCallable = () -> blockingDeque.poll(Long.MAX_VALUE, TimeUnit.DAYS); + runnables.add(() -> { + try { + T call = callable.call(); + blockingDeque.add(call); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return newCallable; + } + + public void peekAndRun() { + Runnable run = runnables.poll(); + if (run == null) + return; + run.run(); + } + + public void peekAndRunAll() { + while (!runnables.isEmpty()) { + Runnable run = runnables.poll(); + if (run != null) + run.run(); + } + } + +} diff --git a/src/main/java/xueli/utils/MyWeakHashMap.java b/src/main/java/xueli/utils/MyWeakHashMap.java new file mode 100644 index 00000000..70a17576 --- /dev/null +++ b/src/main/java/xueli/utils/MyWeakHashMap.java @@ -0,0 +1,1216 @@ +package xueli.utils; + +import java.lang.ref.ReferenceQueue; + +/* + * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * Copied from JDK source but add external ReferenceQueue support! + * + */ + +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +/** + * Hash table based implementation of the {@code Map} interface, with weak + * keys. An entry in a {@code WeakHashMap} will automatically be removed + * when its key is no longer in ordinary use. More precisely, the presence of a + * mapping for a given key will not prevent the key from being discarded by the + * garbage collector, that is, made finalizable, finalized, and then reclaimed. + * When a key has been discarded its entry is effectively removed from the map, + * so this class behaves somewhat differently from other {@code Map} + * implementations. + * + *

+ * Both null values and the null key are supported. This class has performance + * characteristics similar to those of the {@code HashMap} class, and has the + * same efficiency parameters of initial capacity and load + * factor. + * + *

+ * Like most collection classes, this class is not synchronized. A synchronized + * {@code WeakHashMap} may be constructed using the + * {@link Collections#synchronizedMap Collections.synchronizedMap} method. + * + *

+ * This class is intended primarily for use with key objects whose + * {@code equals} methods test for object identity using the {@code ==} + * operator. Once such a key is discarded it can never be recreated, so it is + * impossible to do a lookup of that key in a {@code WeakHashMap} at some later + * time and be surprised that its entry has been removed. This class will work + * perfectly well with key objects whose {@code equals} methods are not based + * upon object identity, such as {@code String} instances. With such recreatable + * key objects, however, the automatic removal of {@code WeakHashMap} entries + * whose keys have been discarded may prove to be confusing. + * + *

+ * The behavior of the {@code WeakHashMap} class depends in part upon the + * actions of the garbage collector, so several familiar (though not required) + * {@code Map} invariants do not hold for this class. Because the garbage + * collector may discard keys at any time, a {@code WeakHashMap} may behave as + * though an unknown thread is silently removing entries. In particular, even if + * you synchronize on a {@code WeakHashMap} instance and invoke none of its + * mutator methods, it is possible for the {@code size} method to return smaller + * values over time, for the {@code isEmpty} method to return {@code false} and + * then {@code true}, for the {@code containsKey} method to return {@code true} + * and later {@code false} for a given key, for the {@code get} method to return + * a value for a given key but later return {@code null}, for the {@code put} + * method to return {@code null} and the {@code remove} method to return + * {@code false} for a key that previously appeared to be in the map, and for + * successive examinations of the key set, the value collection, and the entry + * set to yield successively smaller numbers of elements. + * + *

+ * Each key object in a {@code WeakHashMap} is stored indirectly as the referent + * of a weak reference. Therefore a key will automatically be removed only after + * the weak references to it, both inside and outside of the map, have been + * cleared by the garbage collector. + * + *

+ * Implementation note: The value objects in a + * {@code WeakHashMap} are held by ordinary strong references. Thus care should + * be taken to ensure that value objects do not strongly refer to their own + * keys, either directly or indirectly, since that will prevent the keys from + * being discarded. Note that a value object may refer indirectly to its key via + * the {@code WeakHashMap} itself; that is, a value object may strongly refer to + * some other key object whose associated value object, in turn, strongly refers + * to the key of the first value object. If the values in the map do not rely on + * the map holding strong references to them, one way to deal with this is to + * wrap values themselves within {@code WeakReferences} before inserting, as in: + * {@code m.put(key, new WeakReference(value))}, and then unwrapping upon each + * {@code get}. + * + *

+ * The iterators returned by the {@code iterator} method of the collections + * returned by all of this class's "collection view methods" are + * fail-fast: if the map is structurally modified at any time after the + * iterator is created, in any way except through the iterator's own + * {@code remove} method, the iterator will throw a + * {@link ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the future. + * + *

+ * Note that the fail-fast behavior of an iterator cannot be guaranteed as it + * is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators throw + * {@code ConcurrentModificationException} on a best-effort basis. Therefore, it + * would be wrong to write a program that depended on this exception for its + * correctness: the fail-fast behavior of iterators should be used only to + * detect bugs. + * + *

+ * This class is a member of the Java + * Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Doug Lea + * @author Josh Bloch + * @author Mark Reinhold + * @since 1.2 + * @see java.util.HashMap + * @see java.lang.ref.WeakReference + */ +public class MyWeakHashMap extends AbstractMap implements Map { + + /** + * The default initial capacity -- MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified by + * either of the constructors with arguments. MUST be a power of two <= 1<<30. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The table, resized as necessary. Length MUST Always be a power of two. + */ + Entry[] table; + + /** + * The number of key-value mappings contained in this weak hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * The load factor for the hash table. + */ + private final float loadFactor; + + /** + * Reference queue for cleared WeakEntries + */ + private final ReferenceQueue queue; + + private final Consumer keyRemovedConsumer; + + /** + * The number of times this WeakHashMap has been structurally modified. + * Structural modifications are those that change the number of mappings in the + * map or otherwise modify its internal structure (e.g., rehash). This field is + * used to make iterators on Collection-views of the map fail-fast. + * + * @see ConcurrentModificationException + */ + int modCount; + + @SuppressWarnings("unchecked") + private Entry[] newTable(int n) { + return (Entry[]) new Entry[n]; + } + + /** + * Constructs a new, empty {@code WeakHashMap} with the given initial capacity + * and the given load factor. + * + * @param initialCapacity The initial capacity of the {@code WeakHashMap} + * @param loadFactor The load factor of the {@code WeakHashMap} + * @throws IllegalArgumentException if the initial capacity is negative, or if + * the load factor is nonpositive. + */ + public MyWeakHashMap(int initialCapacity, float loadFactor, ReferenceQueue rq, + Consumer keyRemovedConsumer) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Initial Capacity: " + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal Load factor: " + loadFactor); + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + table = newTable(capacity); + this.loadFactor = loadFactor; + threshold = (int) (capacity * loadFactor); + + this.queue = rq; + this.keyRemovedConsumer = keyRemovedConsumer; + + } + + /** + * Constructs a new, empty {@code WeakHashMap} with the given initial capacity + * and the default load factor (0.75). + * + * @param initialCapacity The initial capacity of the {@code WeakHashMap} + * @throws IllegalArgumentException if the initial capacity is negative + */ + public MyWeakHashMap(int initialCapacity, ReferenceQueue rq, Consumer keyRemovedConsumer) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, rq, keyRemovedConsumer); + } + + /** + * Constructs a new, empty {@code WeakHashMap} with the default initial capacity + * (16) and load factor (0.75). + */ + public MyWeakHashMap(ReferenceQueue rq, Consumer keyRemovedConsumer) { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, rq, keyRemovedConsumer); + } + + /** + * Constructs a new {@code WeakHashMap} with the same mappings as the specified + * map. The {@code WeakHashMap} is created with the default load factor (0.75) + * and an initial capacity sufficient to hold the mappings in the specified map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + * @since 1.3 + */ + public MyWeakHashMap(Map m, ReferenceQueue rq, Consumer keyRemovedConsumer) { + this(Math.max((int) ((float) m.size() / DEFAULT_LOAD_FACTOR + 1.0F), DEFAULT_INITIAL_CAPACITY), + DEFAULT_LOAD_FACTOR, rq, keyRemovedConsumer); + putAll(m); + } + + // internal utilities + + /** + * Value representing null keys inside tables. + */ + private static final Object NULL_KEY = new Object(); + + /** + * Use NULL_KEY for key if it is null. + */ + private static Object maskNull(Object key) { + return (key == null) ? NULL_KEY : key; + } + + /** + * Returns internal representation of null key back to caller as null. + */ + static Object unmaskNull(Object key) { + return (key == NULL_KEY) ? null : key; + } + + /** + * Checks for equality of non-null reference x and possibly-null y. By default + * uses Object.equals. + */ + private boolean matchesKey(Entry e, Object key) { + // check if the given entry refers to the given key without + // keeping a strong reference to the entry's referent + if (e.refersTo(key)) + return true; + + // then check for equality if the referent is not cleared + Object k = e.get(); + return k != null && key.equals(k); + } + + /** + * Retrieve object hash code and applies a supplemental hash function to the + * result hash, which defends against poor quality hash functions. This is + * critical because HashMap uses power-of-two length hash tables, that otherwise + * encounter collisions for hashCodes that do not differ in lower bits. + */ + final int hash(Object k) { + int h = k.hashCode(); + + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + /** + * Returns index for hash code h. + */ + private static int indexFor(int h, int length) { + return h & (length - 1); + } + + /** + * Expunges stale entries from the table. + */ + private void expungeStaleEntries() { + for (Object x; (x = queue.poll()) != null;) { + synchronized (queue) { + @SuppressWarnings("unchecked") + Entry e = (Entry) x; + int i = indexFor(e.hash, table.length); + + Entry prev = table[i]; + Entry p = prev; + while (p != null) { + Entry next = p.next; + if (p == e) { + if (prev == e) + table[i] = next; + else + prev.next = next; + // Must not null out e.next; + // stale entries may be in use by a HashIterator + + keyRemovedConsumer.accept(e.value); + e.value = null; // Help GC + + size--; + break; + } + prev = p; + p = next; + } + } + } + } + + /** + * Returns the table after first expunging stale entries. + */ + private Entry[] getTable() { + expungeStaleEntries(); + return table; + } + + /** + * Returns the number of key-value mappings in this map. This result is a + * snapshot, and may not reflect unprocessed entries that will be removed before + * next attempted access because they are no longer referenced. + */ + public int size() { + if (size == 0) + return 0; + expungeStaleEntries(); + return size; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. This result + * is a snapshot, and may not reflect unprocessed entries that will be removed + * before next attempted access because they are no longer referenced. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} if + * this map contains no mapping for the key. + * + *

+ * More formally, if this map contains a mapping from a key {@code k} to a value + * {@code v} such that {@code Objects.equals(key, k)}, then this method returns + * {@code v}; otherwise it returns {@code null}. (There can be at most one such + * mapping.) + * + *

+ * A return value of {@code null} does not necessarily indicate that the + * map contains no mapping for the key; it's also possible that the map + * explicitly maps the key to {@code null}. The {@link #containsKey containsKey} + * operation may be used to distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null) { + if (e.hash == h && matchesKey(e, k)) + return e.value; + e = e.next; + } + return null; + } + + /** + * Returns {@code true} if this map contains a mapping for the specified key. + * + * @param key The key whose presence in this map is to be tested + * @return {@code true} if there is a mapping for {@code key}; {@code false} + * otherwise + */ + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + /** + * Returns the entry associated with the specified key in this map. Returns null + * if the map contains no mapping for this key. + */ + Entry getEntry(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null && !(e.hash == h && matchesKey(e, k))) + e = e.next; + return e; + } + + /** + * Associates the specified value with the specified key in this map. If the map + * previously contained a mapping for this key, the old value is replaced. + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return the previous value associated with {@code key}, or {@code null} if + * there was no mapping for {@code key}. (A {@code null} return can also + * indicate that the map previously associated {@code null} with + * {@code key}.) + */ + public V put(K key, V value) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + + for (Entry e = tab[i]; e != null; e = e.next) { + if (h == e.hash && matchesKey(e, k)) { + V oldValue = e.value; + if (value != oldValue) + e.value = value; + return oldValue; + } + } + + modCount++; + Entry e = tab[i]; + tab[i] = new Entry<>(k, value, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return null; + } + + /** + * Rehashes the contents of this map into a new array with a larger capacity. + * This method is called automatically when the number of keys in this map + * reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not resize the map, + * but sets threshold to Integer.MAX_VALUE. This has the effect of preventing + * future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; must be greater + * than current capacity unless current capacity is + * MAXIMUM_CAPACITY (in which case value is irrelevant). + */ + void resize(int newCapacity) { + Entry[] oldTable = getTable(); + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = newTable(newCapacity); + transfer(oldTable, newTable); + table = newTable; + + /* + * If ignoring null elements and processing ref queue caused massive shrinkage, + * then restore old table. This should be rare, but avoids unbounded expansion + * of garbage-filled tables. + */ + if (size >= threshold / 2) { + threshold = (int) (newCapacity * loadFactor); + } else { + expungeStaleEntries(); + transfer(newTable, oldTable); + table = oldTable; + } + } + + /** Transfers all entries from src to dest tables */ + private void transfer(Entry[] src, Entry[] dest) { + for (int j = 0; j < src.length; ++j) { + Entry e = src[j]; + src[j] = null; + while (e != null) { + Entry next = e.next; + if (e.refersTo(null)) { + e.next = null; // Help GC + e.value = null; // " " + size--; + } else { + int i = indexFor(e.hash, dest.length); + e.next = dest[i]; + dest[i] = e; + } + e = next; + } + } + } + + /** + * Copies all of the mappings from the specified map to this map. These mappings + * will replace any mappings that this map had for any of the keys currently in + * the specified map. + * + * @param m mappings to be stored in this map. + * @throws NullPointerException if the specified map is null. + */ + public void putAll(Map m) { + int numKeysToBeAdded = m.size(); + if (numKeysToBeAdded == 0) + return; + + /* + * Expand the map if the map if the number of mappings to be added is greater + * than or equal to threshold. This is conservative; the obvious condition is + * (m.size() + size) >= threshold, but this condition could result in a map with + * twice the appropriate capacity, if the keys to be added overlap with the keys + * already in this map. By using the conservative calculation, we subject + * ourself to at most one extra resize. + */ + if (numKeysToBeAdded > threshold) { + int targetCapacity = (int) (numKeysToBeAdded / loadFactor + 1); + if (targetCapacity > MAXIMUM_CAPACITY) + targetCapacity = MAXIMUM_CAPACITY; + int newCapacity = table.length; + while (newCapacity < targetCapacity) + newCapacity <<= 1; + if (newCapacity > table.length) + resize(newCapacity); + } + + for (Map.Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * Removes the mapping for a key from this weak hash map if it is present. More + * formally, if this map contains a mapping from key {@code k} to value + * {@code v} such that (key==null ? k==null : + * key.equals(k)), that mapping is removed. (The map can contain at most + * one such mapping.) + * + *

+ * Returns the value to which this map previously associated the key, or + * {@code null} if the map contained no mapping for the key. A return value of + * {@code null} does not necessarily indicate that the map contained no + * mapping for the key; it's also possible that the map explicitly mapped the + * key to {@code null}. + * + *

+ * The map will not contain a mapping for the specified key once the call + * returns. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or {@code null} if + * there was no mapping for {@code key} + */ + public V remove(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && matchesKey(e, k)) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return e.value; + } + prev = e; + e = next; + } + + return null; + } + + /** Special version of remove needed by Entry set */ + boolean removeMapping(Object o) { + if (!(o instanceof Map.Entry entry)) + return false; + Entry[] tab = getTable(); + Object k = maskNull(entry.getKey()); + int h = hash(k); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && e.equals(entry)) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return true; + } + prev = e; + e = next; + } + + return false; + } + + /** + * Removes all of the mappings from this map. The map will be empty after this + * call returns. + */ + public void clear() { + // clear out ref queue. We don't need to expunge entries + // since table is getting cleared. + while (queue.poll() != null) + ; + + modCount++; + Arrays.fill(table, null); + size = 0; + + // Allocation of array may have caused GC, which may have caused + // additional entries to go stale. Removing these entries from the + // reference queue will make them eligible for reclamation. + while (queue.poll() != null) + ; + } + + /** + * Returns {@code true} if this map maps one or more keys to the specified + * value. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the specified value + */ + public boolean containsValue(Object value) { + if (value == null) + return containsNullValue(); + + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (value.equals(e.value)) + return true; + return false; + } + + /** + * Special-case code for containsValue with null argument + */ + private boolean containsNullValue() { + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (e.value == null) + return true; + return false; + } + + /** + * The entries in this hash table extend WeakReference, using its main ref field + * as the key. + */ + private static class Entry extends WeakReference implements Map.Entry { + V value; + final int hash; + Entry next; + + /** + * Creates new entry. + */ + Entry(Object key, V value, ReferenceQueue queue, int hash, Entry next) { + super(key, queue); + this.value = value; + this.hash = hash; + this.next = next; + } + + @SuppressWarnings("unchecked") + public K getKey() { + return (K) MyWeakHashMap.unmaskNull(get()); + } + + public V getValue() { + return value; + } + + public V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry e)) + return false; + K k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + V v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + public int hashCode() { + K k = getKey(); + V v = getValue(); + return Objects.hashCode(k) ^ Objects.hashCode(v); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + private abstract class HashIterator implements Iterator { + private int index; + private Entry entry; + private Entry lastReturned; + private int expectedModCount = modCount; + + /** + * Strong reference needed to avoid disappearance of key between hasNext and + * next + */ + private Object nextKey; + + /** + * Strong reference needed to avoid disappearance of key between nextEntry() and + * any use of the entry + */ + private Object currentKey; + + HashIterator() { + index = isEmpty() ? 0 : table.length; + } + + public boolean hasNext() { + Entry[] t = table; + + while (nextKey == null) { + Entry e = entry; + int i = index; + while (e == null && i > 0) + e = t[--i]; + entry = e; + index = i; + if (e == null) { + currentKey = null; + return false; + } + nextKey = e.get(); // hold on to key in strong ref + if (nextKey == null) + entry = entry.next; + } + return true; + } + + /** The common parts of next() across different types of iterators */ + protected Entry nextEntry() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (nextKey == null && !hasNext()) + throw new NoSuchElementException(); + + lastReturned = entry; + entry = entry.next; + currentKey = nextKey; + nextKey = null; + return lastReturned; + } + + public void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + MyWeakHashMap.this.remove(currentKey); + expectedModCount = modCount; + lastReturned = null; + currentKey = null; + } + + } + + private class EntryIterator extends HashIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + + // Views + + private transient Set> entrySet; + + /** + * Returns a {@link Set} view of the mappings contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. If the map is modified while an iteration over the set is in + * progress (except through the iterator's own {@code remove} operation, or + * through the {@code setValue} operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set supports + * element removal, which removes the corresponding mapping from the map, via + * the {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. + */ + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + return o instanceof Map.Entry e && getEntry(e.getKey()) != null && getEntry(e.getKey()).equals(e); + } + + public boolean remove(Object o) { + return removeMapping(o); + } + + public int size() { + return MyWeakHashMap.this.size(); + } + + public void clear() { + MyWeakHashMap.this.clear(); + } + + private List> deepCopy() { + List> list = new ArrayList<>(size()); + for (Map.Entry e : this) + list.add(new AbstractMap.SimpleEntry<>(e)); + return list; + } + + public Object[] toArray() { + return deepCopy().toArray(); + } + + public T[] toArray(T[] a) { + return deepCopy().toArray(a); + } + + public Spliterator> spliterator() { + return new EntrySpliterator<>(MyWeakHashMap.this, 0, -1, 0, 0); + } + } + + @SuppressWarnings("unchecked") + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + int expectedModCount = modCount; + + Entry[] tab = getTable(); + for (Entry entry : tab) { + while (entry != null) { + Object key = entry.get(); + if (key != null) { + action.accept((K) MyWeakHashMap.unmaskNull(key), entry.value); + } + entry = entry.next; + + if (expectedModCount != modCount) { + throw new ConcurrentModificationException(); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void replaceAll(BiFunction function) { + Objects.requireNonNull(function); + int expectedModCount = modCount; + + Entry[] tab = getTable(); + ; + for (Entry entry : tab) { + while (entry != null) { + Object key = entry.get(); + if (key != null) { + entry.value = function.apply((K) MyWeakHashMap.unmaskNull(key), entry.value); + } + entry = entry.next; + + if (expectedModCount != modCount) { + throw new ConcurrentModificationException(); + } + } + } + } + + /** + * Similar form as other hash Spliterators, but skips dead elements. + */ + static class WeakHashMapSpliterator { + final MyWeakHashMap map; + MyWeakHashMap.Entry current; // current node + int index; // current index, modified on advance/split + int fence; // -1 until first use; then one past last index + int est; // size estimate + int expectedModCount; // for comodification checks + + WeakHashMapSpliterator(MyWeakHashMap m, int origin, int fence, int est, int expectedModCount) { + this.map = m; + this.index = origin; + this.fence = fence; + this.est = est; + this.expectedModCount = expectedModCount; + } + + final int getFence() { // initialize fence and size on first use + int hi; + if ((hi = fence) < 0) { + MyWeakHashMap m = map; + est = m.size(); + expectedModCount = m.modCount; + hi = fence = m.table.length; + } + return hi; + } + + public final long estimateSize() { + getFence(); // force init + return (long) est; + } + } + + static final class KeySpliterator extends WeakHashMapSpliterator implements Spliterator { + KeySpliterator(MyWeakHashMap m, int origin, int fence, int est, int expectedModCount) { + super(m, origin, fence, est, expectedModCount); + } + + public KeySpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : new KeySpliterator<>(map, lo, index = mid, est >>>= 1, expectedModCount); + } + + public void forEachRemaining(Consumer action) { + int i, hi, mc; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap m = map; + MyWeakHashMap.Entry[] tab = m.table; + if ((hi = fence) < 0) { + mc = expectedModCount = m.modCount; + hi = fence = tab.length; + } else + mc = expectedModCount; + if (tab.length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)) { + MyWeakHashMap.Entry p = current; + current = null; // exhaust + do { + if (p == null) + p = tab[i++]; + else { + Object x = p.get(); + p = p.next; + if (x != null) { + @SuppressWarnings("unchecked") + K k = (K) MyWeakHashMap.unmaskNull(x); + action.accept(k); + } + } + } while (p != null || i < hi); + } + if (m.modCount != mc) + throw new ConcurrentModificationException(); + } + + public boolean tryAdvance(Consumer action) { + int hi; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap.Entry[] tab = map.table; + if (tab.length >= (hi = getFence()) && index >= 0) { + while (current != null || index < hi) { + if (current == null) + current = tab[index++]; + else { + Object x = current.get(); + current = current.next; + if (x != null) { + @SuppressWarnings("unchecked") + K k = (K) MyWeakHashMap.unmaskNull(x); + action.accept(k); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + } + } + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT; + } + } + + static final class ValueSpliterator extends WeakHashMapSpliterator implements Spliterator { + ValueSpliterator(MyWeakHashMap m, int origin, int fence, int est, int expectedModCount) { + super(m, origin, fence, est, expectedModCount); + } + + public ValueSpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : new ValueSpliterator<>(map, lo, index = mid, est >>>= 1, expectedModCount); + } + + public void forEachRemaining(Consumer action) { + int i, hi, mc; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap m = map; + MyWeakHashMap.Entry[] tab = m.table; + if ((hi = fence) < 0) { + mc = expectedModCount = m.modCount; + hi = fence = tab.length; + } else + mc = expectedModCount; + if (tab.length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)) { + MyWeakHashMap.Entry p = current; + current = null; // exhaust + do { + if (p == null) + p = tab[i++]; + else { + Object x = p.get(); + V v = p.value; + p = p.next; + if (x != null) + action.accept(v); + } + } while (p != null || i < hi); + } + if (m.modCount != mc) + throw new ConcurrentModificationException(); + } + + public boolean tryAdvance(Consumer action) { + int hi; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap.Entry[] tab = map.table; + if (tab.length >= (hi = getFence()) && index >= 0) { + while (current != null || index < hi) { + if (current == null) + current = tab[index++]; + else { + Object x = current.get(); + V v = current.value; + current = current.next; + if (x != null) { + action.accept(v); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + } + } + return false; + } + + public int characteristics() { + return 0; + } + } + + static final class EntrySpliterator extends WeakHashMapSpliterator + implements Spliterator> { + EntrySpliterator(MyWeakHashMap m, int origin, int fence, int est, int expectedModCount) { + super(m, origin, fence, est, expectedModCount); + } + + public EntrySpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : new EntrySpliterator<>(map, lo, index = mid, est >>>= 1, expectedModCount); + } + + public void forEachRemaining(Consumer> action) { + int i, hi, mc; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap m = map; + MyWeakHashMap.Entry[] tab = m.table; + if ((hi = fence) < 0) { + mc = expectedModCount = m.modCount; + hi = fence = tab.length; + } else + mc = expectedModCount; + if (tab.length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)) { + MyWeakHashMap.Entry p = current; + current = null; // exhaust + do { + if (p == null) + p = tab[i++]; + else { + Object x = p.get(); + V v = p.value; + p = p.next; + if (x != null) { + @SuppressWarnings("unchecked") + K k = (K) MyWeakHashMap.unmaskNull(x); + action.accept(new AbstractMap.SimpleImmutableEntry<>(k, v)); + } + } + } while (p != null || i < hi); + } + if (m.modCount != mc) + throw new ConcurrentModificationException(); + } + + public boolean tryAdvance(Consumer> action) { + int hi; + if (action == null) + throw new NullPointerException(); + MyWeakHashMap.Entry[] tab = map.table; + if (tab.length >= (hi = getFence()) && index >= 0) { + while (current != null || index < hi) { + if (current == null) + current = tab[index++]; + else { + Object x = current.get(); + V v = current.value; + current = current.next; + if (x != null) { + @SuppressWarnings("unchecked") + K k = (K) MyWeakHashMap.unmaskNull(x); + action.accept(new AbstractMap.SimpleImmutableEntry<>(k, v)); + if (map.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + } + } + } + return false; + } + + public int characteristics() { + return Spliterator.DISTINCT; + } + } + +} diff --git a/src/main/java/xueli/utils/StringTokens.java b/src/main/java/xueli/utils/StringTokens.java new file mode 100644 index 00000000..de12fca7 --- /dev/null +++ b/src/main/java/xueli/utils/StringTokens.java @@ -0,0 +1,81 @@ +package xueli.utils; + +import java.util.ArrayList; + +public class StringTokens { + + public static String[] splitToken(String target, String... tokens) { + ArrayList ts = new ArrayList<>(); + ts.add(target); + + for (String token : tokens) { + ArrayList newTokens = new ArrayList<>(); + + for (int i = 0; i < ts.size(); i++) { + String string = ts.get(i); + + int indexOf = 0; + ArrayList strings = new ArrayList<>(); + + while (true) { + int thisIndexOf = string.indexOf(token, indexOf); + if (thisIndexOf == -1) { + if (indexOf < string.length()) + strings.add(string.substring(indexOf)); + break; + } + String sub = string.substring(indexOf, thisIndexOf); + if (thisIndexOf != 0) + strings.add(sub); + strings.add(token); + indexOf = thisIndexOf + token.length(); + + } + + newTokens.addAll(strings); + + } + + ts = newTokens; + + } + + return ts.toArray(new String[0]); + } + + public static String[] split(String target, String noMoreRegex) { + int indexOf = 0; + ArrayList strings = new ArrayList<>(); + + while (true) { + int thisIndexOf = target.indexOf(noMoreRegex, indexOf); + if (thisIndexOf == -1) { + strings.add(target.substring(indexOf)); + break; + } + String sub = target.substring(indexOf, thisIndexOf); + strings.add(sub); + indexOf = thisIndexOf + 1; + + } + + return strings.toArray(new String[0]); + } + + public static String padLeft(String origin, int length, char ch) { + int origin_length = origin.length(); + int need_pad_length = length - origin_length; + if (need_pad_length <= 0) + return origin.substring(0, length); + return Character.toString(ch).repeat(need_pad_length) + origin; + } + + public static String padRight(String origin, int length, char ch) { + int origin_length = origin.length(); + int need_pad_length = length - origin_length; + if (need_pad_length <= 0) + return origin.substring(0, length); + return origin + Character.toString(ch).repeat(need_pad_length); + } + +} diff --git a/src/main/java/xueli/utils/ThreadDumpHelper.java b/src/main/java/xueli/utils/ThreadDumpHelper.java new file mode 100644 index 00000000..237eb60c --- /dev/null +++ b/src/main/java/xueli/utils/ThreadDumpHelper.java @@ -0,0 +1,49 @@ +package xueli.utils; + +import java.util.Map; +import java.util.Map.Entry; + +public class ThreadDumpHelper { + + private static final int DUMP_BEFORE_NUM = 5; + + // TODO: Maybe later use my JRich to make the debug more readable + public static void dumpAllThreads() { + Map m = Thread.getAllStackTraces(); +// Table table = new Table<>(); + +// int count = 1; + + System.out.println("== Thread Dump =="); + for (Entry e : m.entrySet()) { + Thread t = e.getKey(); + StackTraceElement[] es = e.getValue(); + + StringBuilder stackTraceBuilder = new StringBuilder(); + for (int i = 0; i < Math.min(DUMP_BEFORE_NUM, es.length); i++) { + StackTraceElement stackTraceElement = es[i]; + stackTraceBuilder.append("\t"); + stackTraceBuilder.append(stackTraceElement.getClassName()).append(".") + .append(stackTraceElement.getMethodName()).append("(").append(stackTraceElement.getFileName()) + .append(":").append(stackTraceElement.getLineNumber()).append(")"); + stackTraceBuilder.append(System.lineSeparator()); + } + + String traces = stackTraceBuilder.substring(0, Math.max(0, stackTraceBuilder.length() - 1)); + +// table.put(0, count, t.getName()); +// table.put(1, count, t.getState().toString()); +// table.put(2, count, traces); + System.out.println(t.getName() + ": " + t.getState()); + if (!traces.isBlank()) + System.out.println(traces); + +// count++; + + } + + System.out.println("================="); + + } + +} diff --git a/src/main/java/xueli/utils/TimeHelper.java b/src/main/java/xueli/utils/TimeHelper.java new file mode 100644 index 00000000..407068ea --- /dev/null +++ b/src/main/java/xueli/utils/TimeHelper.java @@ -0,0 +1,34 @@ +package xueli.utils; + +public class TimeHelper { + + public static String toString(long date) { + long current = System.currentTimeMillis() / 1000; + long seconds = current - date; + + if (seconds == 0) + return "in 1 second"; + if (seconds == 1) + return "1 second ago"; + if (seconds < 60) + return seconds + " seconds ago"; + + long minute = seconds / 60; + if (minute == 1) + return "1 minute ago"; + if (minute < 60) + return minute + " minutes ago"; + + long hour = minute / 60; + if (hour == 1) + return "1 hour ago"; + if (hour < 24) + return hour + " hours ago"; + + long day = hour / 24; + if (day == 1) + return "1 day ago"; + return day + " days ago"; + } + +} diff --git a/src/main/java/xueli/utils/ZLib.java b/src/main/java/xueli/utils/ZLib.java new file mode 100644 index 00000000..60e4ac75 --- /dev/null +++ b/src/main/java/xueli/utils/ZLib.java @@ -0,0 +1,46 @@ +package xueli.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.InflaterInputStream; +import java.util.zip.DeflaterOutputStream; + +import io.netty.buffer.ByteBuf; + +@Deprecated +public class ZLib { + + public static void compress(ByteBuf input, ByteBuf output) throws IOException { + try (DeflaterOutputStream out = new DeflaterOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.writeByte(b); + } + })) { + while (input.readableBytes() > 0) { + out.write(input.readByte()); + } + out.finish(); + out.flush(); + } + + } + + public static byte[] decompress(ByteBuf input, int size) throws IOException { + ByteBuf compressed = input.readBytes(size); + try (InflaterInputStream in = new InflaterInputStream(new InputStream() { + @Override + public int read() throws IOException { + return compressed.readByte(); + } + })) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + in.transferTo(out); + + return out.toByteArray(); + } + } + +} diff --git a/src/main/java/xueli/utils/buffer/BufferPool.java b/src/main/java/xueli/utils/buffer/BufferPool.java new file mode 100644 index 00000000..a3480178 --- /dev/null +++ b/src/main/java/xueli/utils/buffer/BufferPool.java @@ -0,0 +1,95 @@ +package xueli.utils.buffer; + +import java.util.ArrayList; +import java.util.LinkedList; + +// The memory is managed by the pool! +// Not thread safe :{ +public class BufferPool implements AutoCloseable { + + private final int perSize; + + private final LinkedList allocated = new LinkedList<>(); + private final LinkedList freed = new LinkedList<>(); + + private final ArrayList listeners = new ArrayList<>(); + + public BufferPool(int perSize) { + this.perSize = perSize; + } + + public void initialSpare(int initialCount) { + for(int i = 0; i < initialCount; i++) { + this.spareMore(); + } + } + + private void spareMore() { + var newBuffer = new LotsOfByteBuffer(this.perSize); + freed.add(newBuffer); + listeners.forEach(l -> l.onNewBufferAllocated(newBuffer)); + + } + + public MemoryHandler spare() { + if(freed.size() == 0) { + this.spareMore(); + } + + final var buffer = freed.remove(freed.size() - 1); + + // This is the solution of a annoying bug! + buffer.setReadWrite(false); + buffer.clear(); + + allocated.add(buffer); + + listeners.forEach(l -> l.onBufferMarkUsing(buffer)); + + return new MemoryHandler() { + + @Override + public LotsOfByteBuffer getBuffer() { + return buffer; + } + + @Override + public void release() { + allocated.remove(buffer); + freed.add(buffer); + listeners.forEach(l -> l.onBufferRecycled(buffer)); + } + + }; + } + + public void addListener(BufferPoolListener listener) { + this.listeners.add(listener); + } + + public void removeListener(BufferPoolListener listener) { + this.listeners.remove(listener); + } + + private void freeBuffer(LotsOfByteBuffer buffer) { + listeners.forEach(l -> l.beforeBufferReleased(buffer)); + buffer.release(); + + } + + public int getAllocatedCount() { + return this.allocated.size(); + } + + public int getFreeCount() { + return this.freed.size(); + } + + @Override + public void close() throws Exception { + this.allocated.forEach(this::freeBuffer); + this.freed.forEach(this::freeBuffer); + + } + +} \ No newline at end of file diff --git a/src/main/java/xueli/utils/buffer/BufferPoolListener.java b/src/main/java/xueli/utils/buffer/BufferPoolListener.java new file mode 100644 index 00000000..5301bda8 --- /dev/null +++ b/src/main/java/xueli/utils/buffer/BufferPoolListener.java @@ -0,0 +1,10 @@ +package xueli.utils.buffer; + +public interface BufferPoolListener { + + default public void onNewBufferAllocated(LotsOfByteBuffer buffer) {} + default public void onBufferMarkUsing(LotsOfByteBuffer buffer) {} + default public void onBufferRecycled(LotsOfByteBuffer buffer) {} + default public void beforeBufferReleased(LotsOfByteBuffer buffer) {} + +} \ No newline at end of file diff --git a/src/main/java/xueli/utils/buffer/BufferSyncor.java b/src/main/java/xueli/utils/buffer/BufferSyncor.java new file mode 100644 index 00000000..991c61f2 --- /dev/null +++ b/src/main/java/xueli/utils/buffer/BufferSyncor.java @@ -0,0 +1,80 @@ +package xueli.utils.buffer; + +import java.util.function.Consumer; + +// Will the memory be freed by OpenGL or OpenAL instead of having to be freed by ourselves? —— LovelyZeeiam +/* + * This object acts as a buffer manager, providing back buffer or take control of an external buffer. + * So buffers will be automatically freed when it is appropriate time. + */ +public class BufferSyncor { + + LotsOfByteBuffer currentBuffer; + private boolean shouldSync = false; + + private LotsOfByteBuffer toBeSyncBuffer; + + public BufferSyncor() { + } + + public synchronized BackBuffer createBackBuffer() { + if (toBeSyncBuffer != null) { + toBeSyncBuffer.release(); + } + toBeSyncBuffer = new LotsOfByteBuffer(); + toBeSyncBuffer.setReadWrite(false); + return new BackBuffer() { + + @Override + public LotsOfByteBuffer getBuffer() { + return toBeSyncBuffer; + } + + @Override + public void markSync() { + toBeSyncBuffer.setReadWrite(true); + shouldSync = true; + } + + }; + } + +// public LotsOfByteBuffer getLatestBuffer() { +// return toBeSyncBuffer == null ? currentBuffer : toBeSyncBuffer; +// } + + public synchronized void updateBuffer(LotsOfByteBuffer buf) { + if (toBeSyncBuffer != null && toBeSyncBuffer != buf) { + toBeSyncBuffer.release(); + } + toBeSyncBuffer = buf; + shouldSync = true; + } + + public void doingSyncIfNecessary(Consumer c) { + synchronized (this) { + if (shouldSync) { + LotsOfByteBuffer lastBuffer = this.currentBuffer; + + currentBuffer = toBeSyncBuffer; + toBeSyncBuffer = null; + c.accept(this.currentBuffer); + + if (lastBuffer != null && lastBuffer != currentBuffer) { + currentBuffer.release(); + } + + shouldSync = false; + } + } + } + + public interface BackBuffer { + + public LotsOfByteBuffer getBuffer(); + + public void markSync(); + + } + +} diff --git a/src/main/java/xueli/utils/buffer/LotsOfByteBuffer.java b/src/main/java/xueli/utils/buffer/LotsOfByteBuffer.java new file mode 100644 index 00000000..b551efce --- /dev/null +++ b/src/main/java/xueli/utils/buffer/LotsOfByteBuffer.java @@ -0,0 +1,170 @@ +package xueli.utils.buffer; + +import java.nio.ByteBuffer; + +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.utils.vector.Vector2f; +import org.lwjgl.utils.vector.Vector3f; +import org.lwjgl.utils.vector.Vector4f; + +/** + *

A object managing memory, but the buffer won't be freed by GC!

+ * + *

Actually the class is named arbitrary :}

+ * + */ +public class LotsOfByteBuffer { + + private static final int DEFAULT_CAPACITY = 32768; + private static final int STEP_EXPAND = 65536; + + private ByteBuffer buffer; + private int size; + + public LotsOfByteBuffer() { + this(DEFAULT_CAPACITY); + } + + public LotsOfByteBuffer(int initialCapacity) { + // The direct memory should be freed explicitly, or the game will jam when it + // comes to GC because it will free about 1GB at the same time in my computer. + this.buffer = MemoryUtil.memAlloc(initialCapacity); + + this.size = initialCapacity; + + } + + private void predictAndExpand(int more) { + if (buffer.position() + more >= buffer.capacity()) { + int capacity = this.size = this.buffer.capacity() + STEP_EXPAND; + ByteBuffer newBuffer = MemoryUtil.memAlloc(capacity); + this.buffer.flip(); + newBuffer.put(this.buffer); + this.buffer = newBuffer; + } + } + + public void position(int newPos) { + this.buffer.position(newPos); + } + + public int position() { + return this.buffer.position(); + } + + public void putByte(byte v) { + predictAndExpand(Byte.BYTES); + buffer.put(v); + + } + + public byte readByte() { + return buffer.get(); + } + + public byte readByte(int position) { + return buffer.get(position); + } + + public void putFloat(float v) { + predictAndExpand(Float.BYTES); + buffer.putFloat(v); +// System.out.println(buffer.position()); + } + + public float readFloat() { + return buffer.getFloat(); + } + + public float readFloat(int position) { + return buffer.getFloat(position); + } + + public void putInt(int v) { + predictAndExpand(Integer.BYTES); + buffer.putInt(v); + } + + public int readInt() { + return buffer.getInt(); + } + + public int readInt(int position) { + return buffer.getInt(position); + } + + public void putShort(short v) { + predictAndExpand(Short.BYTES); + buffer.putShort(v); + } + + public short readShort() { + return buffer.getShort(); + } + + public short readShort(int position) { + return buffer.getShort(position); + } + + // TODO: read the following data structures + public void putVector3f(Vector3f v) { + predictAndExpand(3 * Float.BYTES); + buffer.putFloat(v.x); + buffer.putFloat(v.y); + buffer.putFloat(v.z); + } + + public void putVector4f(Vector4f v) { + predictAndExpand(4 * Float.BYTES); + buffer.putFloat(v.x); + buffer.putFloat(v.y); + buffer.putFloat(v.z); + buffer.putFloat(v.w); + } + + public void putVector2f(Vector2f v) { + predictAndExpand(2 * Float.BYTES); + buffer.putFloat(v.x); + buffer.putFloat(v.y); + } + + public void clear() { + buffer.clear(); + } + + private boolean isRead = false; + + public void setReadWrite(boolean read) { + if (this.isRead == read) + return; + if (read) { + buffer.flip(); + } else { + buffer.compact(); + } + this.isRead = read; + + } + + public ByteBuffer getBuffer() { + if (this.buffer == null) + throw new IllegalStateException(); + return buffer; + } + + public int getSize() { + return size; + } + + public void release() { + MemoryUtil.memFree(this.buffer); + this.buffer = null; + + } + + @Override + public String toString() { + return "LotsOfByteBuffer [buffer=" + buffer + "]"; + } + +} diff --git a/src/main/java/xueli/utils/buffer/MemoryHandler.java b/src/main/java/xueli/utils/buffer/MemoryHandler.java new file mode 100644 index 00000000..a1f7aaa9 --- /dev/null +++ b/src/main/java/xueli/utils/buffer/MemoryHandler.java @@ -0,0 +1,9 @@ +package xueli.utils.buffer; + +// Suddenly Xueli remember that C# has something similar :} +public interface MemoryHandler { + + public LotsOfByteBuffer getBuffer(); + public void release(); + +} \ No newline at end of file diff --git a/src/main/java/xueli/utils/clazz/ClazzUtils.java b/src/main/java/xueli/utils/clazz/ClazzUtils.java new file mode 100644 index 00000000..a35d0152 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/ClazzUtils.java @@ -0,0 +1,147 @@ +package xueli.utils.clazz; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import java.util.LinkedList; + +import xueli.utils.io.Files; + +@Deprecated +public class ClazzUtils { + + private ClazzUtils() { + } + + public static List> getAllAnnotatedClass(Class annotation) { + List> clazzes = new ArrayList<>(); + + List> all = getAllClasses(); + clazzes = all.stream().filter(c -> { + if (c.getAnnotation(annotation) != null) + return true; + return false; + }).collect(Collectors.toList()); + + return clazzes; + } + + public static List> getClassExtendedBy(Class c) { + List> clazzes = new ArrayList<>(); + + List> all = getAllClasses(); + for (Class clazz : all) { + try { + Class extended = clazz.asSubclass(c); + if (!extended.getName().equals(c.getName())) + clazzes.add(extended); + } catch (ClassCastException e) { + continue; + } + } + + return clazzes; + } + + public static List> getAllClasses() { + ArrayList> clazzes = new ArrayList<>(); + + // process current module + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + String path = loader.getResource("./").getPath(); + LinkedList allFiles = Files.getAllFiles(path); + + for (File file : allFiles) { + if (!file.exists()) + continue; + if (!file.getPath().endsWith(".class")) + continue; + + String realPath = file.getPath().substring(path.length() - 1); + // the file path separater might be only useful in Windows + String packageName = realPath.substring(0, realPath.length() - ".class".length()).replace('\\', '.'); + + Class clazz = null; + try { + clazz = Class.forName(packageName); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + continue; + } + clazzes.add(clazz); + + } + + try { + Enumeration paths = loader.getResources("META-INF"); + + while (paths.hasMoreElements()) { + URL u = paths.nextElement(); + try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { u }, + Thread.currentThread().getContextClassLoader())) { + // TODO: NOT SUPPORT LINUX + String p = u.getPath(); + String jarpath = new File(p).getParent(); + jarpath = jarpath.substring(!jarpath.contains("file:\\") ? 0 : "file:\\".length(), + !jarpath.endsWith("!") ? jarpath.length() : jarpath.length() - 1); + + try (JarFile file = new JarFile(jarpath)) { + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + JarEntry e = entries.nextElement(); + if (e.isDirectory()) + continue; + + String name = e.getName(); + if (name.contains("META-INF")) + continue; + if (name.contains("module-info")) + continue; + + if (name.endsWith(".class")) { + String className = name.substring(0, name.length() - ".class".length()); + className = className.replaceAll("/", "."); + clazzes.add(urlClassLoader.loadClass(className)); + + } + + } + } + } + + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return clazzes; + } + + public static Field[] getAllFields(Class c) { + ArrayList fields = new ArrayList<>(); + fields.addAll(List.of(c.getDeclaredFields())); + + Class s = c.getSuperclass(); + if (s != null) + fields.addAll(List.of(getAllFields(s))); + + return fields.toArray(new Field[0]); + } + + public static void printAllSuperClass(Class c) { + do { + System.out.println(c.getName()); + c = c.getSuperclass(); + } while (c != null); + } + +} diff --git a/src/main/java/xueli/utils/clazz/test/Clazz1.java b/src/main/java/xueli/utils/clazz/test/Clazz1.java new file mode 100644 index 00000000..632d1473 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/Clazz1.java @@ -0,0 +1,16 @@ +package xueli.utils.clazz.test; + +@Test(name = "clazz1", value = 114514) +public class Clazz1 { + + public Clazz1() { + + } + + public void invoke1(@Param(name = "param1") String p1) { + } + + public void invoke2(@Param(name = "param1") String p1, @Param(name = "param2") String p2) { + } + +} diff --git a/src/main/java/xueli/utils/clazz/test/Clazz2.java b/src/main/java/xueli/utils/clazz/test/Clazz2.java new file mode 100644 index 00000000..860f2b17 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/Clazz2.java @@ -0,0 +1,16 @@ +package xueli.utils.clazz.test; + +@Test(name = "clazz2", value = 1919180) +public class Clazz2 { + + public Clazz2() { + + } + + public void invoke1(@Test(name = "param1", value = 233) String p1) { + } + + public void invoke2(@Test(name = "param1", value = 455) String p1, @Param(name = "param2") String p2) { + } + +} diff --git a/src/main/java/xueli/utils/clazz/test/Param.java b/src/main/java/xueli/utils/clazz/test/Param.java new file mode 100644 index 00000000..4b5a7a64 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/Param.java @@ -0,0 +1,9 @@ +package xueli.utils.clazz.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Param { + public String name(); +} \ No newline at end of file diff --git a/src/main/java/xueli/utils/clazz/test/ReflectionTestClazz.java b/src/main/java/xueli/utils/clazz/test/ReflectionTestClazz.java new file mode 100644 index 00000000..cbecf126 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/ReflectionTestClazz.java @@ -0,0 +1,15 @@ +package xueli.utils.clazz.test; + +import xueli.utils.clazz.ClazzUtils; + +public class ReflectionTestClazz { + + public static void main(String[] args) { + for (Class clazz : ClazzUtils.getAllAnnotatedClass(Test.class)) { + System.out.println(clazz); + + } + + } + +} diff --git a/src/main/java/xueli/utils/clazz/test/Test.java b/src/main/java/xueli/utils/clazz/test/Test.java new file mode 100644 index 00000000..ec4be9f7 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/Test.java @@ -0,0 +1,11 @@ +package xueli.utils.clazz.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Test { + public String name(); + + public int value(); +} diff --git a/src/main/java/xueli/utils/clazz/test/package-info.java b/src/main/java/xueli/utils/clazz/test/package-info.java new file mode 100644 index 00000000..25ddb993 --- /dev/null +++ b/src/main/java/xueli/utils/clazz/test/package-info.java @@ -0,0 +1,2 @@ +@Deprecated +package xueli.utils.clazz.test; \ No newline at end of file diff --git a/src/main/java/xueli/utils/collection/CollectionHelper.java b/src/main/java/xueli/utils/collection/CollectionHelper.java new file mode 100644 index 00000000..eedc58d8 --- /dev/null +++ b/src/main/java/xueli/utils/collection/CollectionHelper.java @@ -0,0 +1,46 @@ +package xueli.utils.collection; + +import java.util.Collection; + +public class CollectionHelper { + + public static String toString(int[][] array) { + StringBuilder str = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + for (int j = 0; j < array[i].length; j++) { + str.append(array[i][j]).append(" "); + } + str.append(System.lineSeparator()); + } + return str.toString(); + } + + public static String toString(Collection c) { + return toString(c, 0); + } + + private static String toString(Collection c, int tabLevel) { + StringBuilder b = new StringBuilder(); + + b.append(c.getClass().getName()).append(": ").append(c.size()).append(" {"); + for (Object object : c) { + b.append(System.lineSeparator()); + + for (int i = 0; i <= tabLevel; i++) + b.append("\t"); + + if (object instanceof Collection) { + b.append(toString((Collection) object, tabLevel + 1)); + } else + b.append(object.toString()); + } + b.append(System.lineSeparator()); + for (int i = 0; i < tabLevel; i++) + b.append("\t"); + b.append("}"); + + return b.toString(); + + } + +} diff --git a/src/main/java/xueli/utils/collection/DirectArrayList.java b/src/main/java/xueli/utils/collection/DirectArrayList.java new file mode 100644 index 00000000..20e34c7e --- /dev/null +++ b/src/main/java/xueli/utils/collection/DirectArrayList.java @@ -0,0 +1,132 @@ +package xueli.utils.collection; + +import io.netty.util.internal.shaded.org.jctools.util.UnsafeAccess; +import sun.misc.Unsafe; + +/** + * This class provides direct memory access to store a list, so "Unsafe" is used to allocate memory directly, + * which means that you should invoke "release" method explicitly to free the memory. + * + * Maybe in the future this class will implement all methods in official "List" interface. + * + */ +// Wait, someone writes this but doesn't put into use? —— XueLi +// I don't know. —— LovelyZeeiam +public class DirectArrayList { + + static final Unsafe unsafe = UnsafeAccess.UNSAFE; + static final int DEFAULT_EXPAND_STEP = 512; + + private int expandStep; + private long startPtr, currentPtr, endPtr; + + public DirectArrayList() { + this(DEFAULT_EXPAND_STEP); + } + + public DirectArrayList(int expandStep) { + this(expandStep, expandStep); + } + + public DirectArrayList(int initialSize, int expandStep) { + // The max size of primitive type is 8. To prevent duplicate calculation and + // allocation, we just set the value max than 8. + this.expandStep = Math.min(expandStep, 8); + + this.startPtr = unsafe.allocateMemory(initialSize); + this.endPtr = this.startPtr + initialSize - 1; + this.currentPtr = this.startPtr; + + } + + public void putByte(byte b) { + this.checkExtend(); + unsafe.putByte(currentPtr, b); + currentPtr += Byte.BYTES; + + } + + public void putChar(char c) { + this.checkExtend(); + unsafe.putChar(currentPtr, c); + currentPtr += Character.BYTES; + + } + + public void putInt(int i) { + this.checkExtend(); + unsafe.putInt(currentPtr, i); + currentPtr += Integer.BYTES; + + } + + public void putShort(short s) { + this.checkExtend(); + unsafe.putShort(currentPtr, s); + currentPtr += Short.BYTES; + + } + + public void putFloat(float f) { + this.checkExtend(); + unsafe.putFloat(currentPtr, f); + currentPtr += Float.BYTES; + + } + + public void putDouble(double d) { + this.checkExtend(); + unsafe.putDouble(currentPtr, d); + currentPtr += Double.BYTES; + + } + + public void putLong(long l) { + this.checkExtend(); + unsafe.putLong(currentPtr, l); + currentPtr += Long.BYTES; + + } + + private void checkExtend() { + if (this.currentPtr > this.endPtr) { +// System.out.println(this); + long newToEndPlus = this.endPtr - this.startPtr + this.expandStep; + long pointerFromStart = this.currentPtr - this.startPtr; + this.startPtr = unsafe.reallocateMemory(this.startPtr, newToEndPlus + 1); + this.currentPtr = this.startPtr + pointerFromStart; + this.endPtr = this.startPtr + newToEndPlus; + } + + } + + public void clear() { + this.currentPtr = this.startPtr; + } + + public long capacity() { + return this.endPtr - this.startPtr + 1; + } + + public long position() { + return this.currentPtr - this.startPtr; + } + + public void position(long newPos) { + this.currentPtr = this.startPtr + newPos; + } + + public long getStartPtr() { + return startPtr; + } + + public void release() { + unsafe.freeMemory(this.startPtr); + } + + @Override + public String toString() { + return "CByteVector [startPtr=" + startPtr + ", currentPtr=" + currentPtr + ", endPtr=" + endPtr + "]"; + } + +} diff --git a/src/main/java/xueli/utils/collection/SegmentTree.java b/src/main/java/xueli/utils/collection/SegmentTree.java new file mode 100644 index 00000000..546f41ba --- /dev/null +++ b/src/main/java/xueli/utils/collection/SegmentTree.java @@ -0,0 +1,112 @@ +package xueli.utils.collection; + +import java.util.Arrays; +import java.util.Objects; + +// Who put it here? —— XueLi +public class SegmentTree { + + private TreeNode[] nodes; + + public SegmentTree(int[] list) { + Objects.requireNonNull(list); + this.nodes = new TreeNode[list.length << 2];//Only 4 times of space required + this.buildTree(1, 1, list.length, list); + System.out.println(Arrays.toString(this.nodes)); + + } + + private void buildTree(int nodeIndex, int left, int right, int[] list) { + TreeNode node = this.nodes[nodeIndex] = new TreeNode(); + node.left = left; + node.right = right; + if (left == right) { + node.value = list[left - 1]; + return; + } +// int middle = (int) Math.sqrt((left * left + right * right) / 2.0); + int middle = (left + right) >> 1; +// int middle = (int) (a - b) / (Math.log(a) - Math.log(b)); +// int middle = (int) Math.sqrt(left * right); +// int middle = (int) 2 / (1.0 / left + 1.0 / right); + int leftIndex = nodeIndex << 1, rightIndex = leftIndex | 1; + this.buildTree(leftIndex, left, middle, list); + this.buildTree(rightIndex, middle + 1, right, list); + node.value = this.nodes[leftIndex].value + this.nodes[rightIndex].value; + } + + public int sum(int left, int right) { + return this.sum(1, left, right); + } + + private void pushdown(int nodeIndex) { + TreeNode node = this.nodes[nodeIndex]; + TreeNode leftNode = this.nodes[nodeIndex << 1]; + TreeNode rightNode = this.nodes[(nodeIndex << 1) | 1]; + leftNode.lazy += node.lazy; + rightNode.lazy += node.lazy; + leftNode.value += node.lazy * (leftNode.right - leftNode.left + 1); + rightNode.value += node.lazy * (rightNode.right - rightNode.left + 1); + node.lazy = 0; + } + + private int sum(int nodeIndex, int left, int right) { + TreeNode node = this.nodes[nodeIndex]; + if (left <= node.left && right >= node.right) + return node.value; + if ((left <= node.right && left >= node.left) || (right <= node.right && right >= node.left)) { + if(node.lazy != 0) { + pushdown(nodeIndex); + } + int s = 0; + int leftIndex = nodeIndex << 1, rightIndex = leftIndex | 1; + s += sum(leftIndex, left, right); + s += sum(rightIndex, left, right); + return s; + } + return 0; + } + + private void add(int nodeIndex, int left, int right, int mod) { + TreeNode node = this.nodes[nodeIndex]; + if(left <= node.left && right >= node.right) { + node.lazy += mod; + node.value += (node.right - node.left + 1) * mod; + } + if(node.lazy != 0) { + pushdown(nodeIndex); + } + int leftIndex = nodeIndex << 1, rightIndex = leftIndex | 1; + int middle = (node.left + node.right) >> 1; + if(left <= middle) { + add(leftIndex, left, right ,mod); + } + if(right > middle) { + add(rightIndex, left, right, mod); + } + node.value = this.nodes[leftIndex].value + this.nodes[rightIndex].value; + } + + public void add(int left, int right, int mod) { + add(1, left, right, mod); + } + + private static class TreeNode { + public int value = 0; + public int left, right; + public int lazy = 0; + + @Override + public String toString() { + return String.format("{%s, %s, %s, %s}", value, lazy, left, right); + } + + } + + public static void main(String[] args) { + var tree = new SegmentTree(new int[] { 1, 5, 2, 5, 3, 1, 3, 4, 6, 6, 3, 2, 4, 5, 6, 5, 2, 3 }); + System.out.println(tree.sum(2, 21)); + + } + +} diff --git a/src/main/java/xueli/utils/concurrent/ControllerExecutorService.java b/src/main/java/xueli/utils/concurrent/ControllerExecutorService.java new file mode 100644 index 00000000..ec88d226 --- /dev/null +++ b/src/main/java/xueli/utils/concurrent/ControllerExecutorService.java @@ -0,0 +1,172 @@ +package xueli.utils.concurrent; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class ControllerExecutorService extends AbstractExecutorService implements ScheduledExecutorService { + + private TreeMap> queue = new TreeMap<>(); // 可äģĨ攚成äēŒå‰æ ‘ + + public void tick() { + Long thisTime = System.nanoTime(); + synchronized (queue) { + Entry> entry; + while ((entry = queue.floorEntry(thisTime)) != null) { +// System.out.println(entry); + queue.remove(entry.getKey()); + entry.getValue().forEach(Runnable::run); + } + } + + } + + @Override + public void shutdown() { + } + + @Override + public List shutdownNow() { + ArrayList remaining = new ArrayList<>(); + queue.values().forEach(remaining::addAll); + return remaining; + } + + @Override + public void execute(Runnable command) { + synchronized (queue) { + queue.computeIfAbsent(System.nanoTime(), t -> new ArrayList<>()).add(command); + } + } + + private final AtomicInteger sequenceCounter = new AtomicInteger(); + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + long runTime = unit.toNanos(delay) + System.nanoTime(); + MyScheduledFuture future = new MyScheduledFuture<>(runTime, command, null, + sequenceCounter.getAndIncrement()); + synchronized (queue) { + queue.computeIfAbsent(runTime, t -> new ArrayList<>()).add(future); + } + return future; + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + long runTime = unit.toNanos(delay) + System.nanoTime(); + MyScheduledFuture future = new MyScheduledFuture<>(runTime, callable, sequenceCounter.getAndIncrement()); + synchronized (queue) { + queue.computeIfAbsent(runTime, t -> new ArrayList<>()).add(future); + } + return future; + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + long runTime = unit.toNanos(initialDelay) + System.nanoTime(); + MyScheduledFuture future = new MyScheduledFuture<>(runTime, unit.toNanos(period), command, null, + sequenceCounter.getAndIncrement()); + synchronized (queue) { + queue.computeIfAbsent(runTime, t -> new ArrayList<>()).add(future); + } + return future; + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + throw new UnsupportedOperationException( + "Actually the programmer can't figure out what the difference is between this and the method above :{"); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return true; + } + + class MyScheduledFuture extends FutureTask implements ScheduledFuture { + + private long time, period = 0; + private final int sequenceNum; + + public MyScheduledFuture(long time, Callable callable, int sequenceNum) { + super(callable); + this.time = time; + this.sequenceNum = sequenceNum; + } + + public MyScheduledFuture(long time, Runnable runnable, V result, int sequenceNum) { + super(runnable, result); + this.time = time; + this.sequenceNum = sequenceNum; + } + + public MyScheduledFuture(long time, long period, Runnable runnable, V result, int sequenceNum) { + super(runnable, result); + this.time = time; + this.period = period; + this.sequenceNum = sequenceNum; + } + + @Override + public long getDelay(TimeUnit unit) { + return unit.convert(time - System.nanoTime(), TimeUnit.NANOSECONDS); + } + + @Override + public int compareTo(Delayed other) { + if (other == this) + return 0; + if (other instanceof MyScheduledFuture x) { + long diff = time - x.time; + if (diff < 0) + return -1; + else if (diff > 0) + return 1; + else if (sequenceNum < x.sequenceNum) + return -1; + else + return 1; + } + long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; + } + + @Override + public void run() { + if (period == 0) { + super.run(); + } else if (!this.isCancelled()) { + super.runAndReset(); + this.time += this.period; + synchronized (queue) { + queue.computeIfAbsent(this.time, t -> new ArrayList<>()).add(this); + } + + } + } + + } + +} diff --git a/src/main/java/xueli/utils/download/DeterminedListWorker.java b/src/main/java/xueli/utils/download/DeterminedListWorker.java new file mode 100644 index 00000000..fbd4fd4f --- /dev/null +++ b/src/main/java/xueli/utils/download/DeterminedListWorker.java @@ -0,0 +1,108 @@ +package xueli.utils.download; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +import com.google.common.collect.Lists; + +/** + * DeterminedListWorker is a worker that keeps working until the + * list gets empty. When the task throws an exception, it can send the exception + * to the BiConsumer, but at the same time, another task can be + * submitted. This can be very useful when doing work filled with exceptions. + *
+ * It has two executor, one of which from the constructor is + * + * @param The task carrier + */ +public abstract class DeterminedListWorker { + + private final CopyOnWriteArrayList workList = Lists.newCopyOnWriteArrayList(); + private final ExecutorService executor; + + /** + * @param executor The executor that all the download task apart will run at. + */ + public DeterminedListWorker(ExecutorService executor) { + this.executor = Objects.requireNonNull(executor); + } + + public void submit(T t) { + workList.add(t); + } + + /** + * The runTask method will be used to process the task carrier. + * + * @param t The task carrier + * @throws Exception used when it is thought that the exception is extremely + * severe that it must be thrown immediately, stopping all the + * tasks right now. When this happens, you can use + * forEach to take the remaining tasks back. + */ + protected abstract void runTask(T t) throws Exception; + + public boolean run() { + return this.runTheWorker(); + } + + /** + * Submit the task manager to another executor service and get a + * Future representing its state. This can be working if a + * multi-file downloading is considered. + */ + public Future run(ExecutorService executor) { + return executor.submit(this::runTheWorker); + } + + /** + * The runTheWorker method actually serves as a task manager, which + * takes the task, submits to the executor from the constructor and tracks its + * progress. + */ + protected boolean runTheWorker() { + while (!workList.isEmpty()) { + this.onTasksRestart(); + + ArrayList> futures = Lists.newArrayList(); + + for (T t : workList) { + workList.remove(t); + Future future = this.executor.submit(() -> { + try { + this.runTask(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + futures.add(future); + } + + for (Future f : futures) { + try { + f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + } + return true; + } + + /** + * Execute when the new run is executed + */ + protected void onTasksRestart() { + } + + public synchronized void forEach(Consumer action) { + this.workList.forEach(action); + } + +} diff --git a/src/main/java/xueli/utils/download/DownloadInstance.java b/src/main/java/xueli/utils/download/DownloadInstance.java new file mode 100644 index 00000000..487a6812 --- /dev/null +++ b/src/main/java/xueli/utils/download/DownloadInstance.java @@ -0,0 +1,182 @@ +package xueli.utils.download; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +public abstract class DownloadInstance extends DeterminedListWorker implements DownloadStateListener { + + protected final URL url; + protected final File output; + protected final Map headers; + + protected long fileSize = 0; + + DownloadInstance(URL url, File output, Map headers, ExecutorService executor) { + super(executor); + this.url = url; + this.output = output; + this.headers = headers; + + } + + public static DownloadInstance create(URL url, File output, Map headers, ExecutorService executor) + throws IOException { + return create(url, output, headers, Integer.MAX_VALUE, executor); + } + + /** + * Create a DownloadInstance instance that is ready to run. It will + * check whether the link support Range parameter, get the length + * of content and make relative decisions, which is called initializing. + * + * @param url The downloading link + * @param output The output file + * @param headers The headers that is necessary to make the connection + * @param chunkSize The chunk size of multithreaded downloading. When the value + * is not bigger than zero, the method won't consider the + * multithreaded downloading but returns to the + * RangeDownloadInstance. + * @param executor The executor that runs multithreaded downloading task + * @throws IOException throw when an exception on initializing is thrown + */ + public static DownloadInstance create(URL url, File output, Map headers, long chunkSize, + ExecutorService executor) throws IOException { + output.createNewFile(); + + String proto = url.getProtocol().toLowerCase(); + URLConnection con = createConnection(url, headers); + + boolean supportRange = false; + if ("http".equals(proto) || "https".equals(proto)) { + // Open the connection to check whether Range is supported + HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); + httpCon.setRequestProperty("Range", "bytes=0-"); + int responseCode = httpCon.getResponseCode(); + + switch (responseCode) { + case HttpURLConnection.HTTP_OK: + break; + case HttpURLConnection.HTTP_PARTIAL: + supportRange = true; + break; + case HttpURLConnection.HTTP_MOVED_TEMP: + case HttpURLConnection.HTTP_MOVED_PERM: + case HttpURLConnection.HTTP_SEE_OTHER: + String newLocation = httpCon.getHeaderField("Location"); + URL newUrl = new URL(newLocation); + return create(newUrl, output, headers, chunkSize, executor); + default: + throw new IOException(String.format("Get %d when fetch url: %s%n", responseCode, url)); + } + } + long fileSize = con.getContentLengthLong(); + + DownloadInstance instance; + // To support some occasions when a negative file size is sent back. Often this + // will happen when trying to access a remote website. + if (fileSize > 0) { + instance = supportRange + ? (chunkSize > 0 ? new MultiThreadedDownloadInstance(url, output, headers, chunkSize, executor) + : new RangeDownloadInstance(url, output, headers, executor)) + : new SingleThreadDownloadInstance(url, output, headers, executor); + } else { + instance = new SingleThreadDownloadInstance(url, output, headers, executor); + } + + instance.fileSize = fileSize; + instance.init(con); + + return instance; + } + + public static URLConnection createConnection(URL url, Map headers) throws IOException { + URLConnection con = url.openConnection(); + con.setConnectTimeout(5000); + con.setReadTimeout(3000); + + if (con instanceof HttpURLConnection && headers != null) { + headers.forEach(con::setRequestProperty); + } + + return con; + } + + /** + * submit the initial connection to avoid spending time on connection + */ + protected void init(URLConnection initialConnection) { + } + + protected void submitDownloadTask(Range range) { + try { + this.submit(RangeConnection.makeConnection(range.getFrom(), range.getTo(), url, headers)); + } catch (IOException e) { + this.processDownloadFail(range, null, 0, e); + } + } + + protected String genRandomString() { + return "." + RandomUtils.genRandomStr(20); + } + + @Override + protected void runTask(RangeConnection rangeCon) throws Exception { + long downloadSize = rangeCon.getTo() - rangeCon.getFrom() + 1; + long byteCount = 0; + + InputStream in = null; + OutputStream out = null; + + String randomStr = genRandomString(); + File file = new File(output.getAbsolutePath() + (randomStr == null ? "" : randomStr)); + + byte[] ioBuffer = new byte[4096]; + + try { + in = rangeCon.getConnection().getInputStream(); + out = new BufferedOutputStream(new FileOutputStream(file)); + + int read = 0; + while (read != -1) { + long remaining = downloadSize - byteCount; + + // To ensure read count is an "integer" + // And also make sure that the initial connection won't read more + int thisTimeReadCount = (int) Math.min(ioBuffer.length, Math.min(remaining, Integer.MAX_VALUE)); + if (thisTimeReadCount <= 0) + break; + + read = in.read(ioBuffer, 0, thisTimeReadCount); + + if (read > 0) { + out.write(ioBuffer, 0, read); + byteCount += read; + } + + } + + this.processDownloadSuccess(rangeCon, file); + + } catch (IOException e) { + this.processDownloadFail(rangeCon, file, byteCount, e); + } finally { + if (in != null) + in.close(); + if (out != null) { + out.flush(); + out.close(); + } + } + + } + +} diff --git a/src/main/java/xueli/utils/download/DownloadMain.java b/src/main/java/xueli/utils/download/DownloadMain.java new file mode 100644 index 00000000..d23ebab1 --- /dev/null +++ b/src/main/java/xueli/utils/download/DownloadMain.java @@ -0,0 +1,30 @@ +package xueli.utils.download; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class DownloadMain { + + public static void main(String[] args) throws Exception { + String url, filePath; + int chunkSize; + try { + url = args[0]; + filePath = args[1]; + chunkSize = args.length > 2 ? Integer.parseInt(args[2]) : Integer.MAX_VALUE; + } catch (Exception e) { + System.out.println("Usage: java -jar (JarPath) (URL) (FilePath) [ChunkSize]"); + return; + } + + ExecutorService executor = Executors.newWorkStealingPool(); + DownloadInstance downloadInstance = DownloadInstance.create(new URL(url), new File(filePath), null, chunkSize, + executor); + downloadInstance.run(); + executor.shutdownNow(); + + } + +} diff --git a/src/main/java/xueli/utils/download/DownloadStateListener.java b/src/main/java/xueli/utils/download/DownloadStateListener.java new file mode 100644 index 00000000..033cb1bc --- /dev/null +++ b/src/main/java/xueli/utils/download/DownloadStateListener.java @@ -0,0 +1,11 @@ +package xueli.utils.download; + +import java.io.File; + +public interface DownloadStateListener { + + void processDownloadSuccess(Range range, File file); + + void processDownloadFail(Range range, File file, long downloadedSize, Exception e); + +} diff --git a/src/main/java/xueli/utils/download/DynamicChunkSize.java b/src/main/java/xueli/utils/download/DynamicChunkSize.java new file mode 100644 index 00000000..0deeddb2 --- /dev/null +++ b/src/main/java/xueli/utils/download/DynamicChunkSize.java @@ -0,0 +1,46 @@ +package xueli.utils.download; + +import java.io.File; +import java.util.ArrayList; +import java.util.stream.Collectors; + +/** + * Dynamically calculate the chunk size according to the previous downloading + * size + */ +public class DynamicChunkSize implements DownloadStateListener { + + private final ArrayList downloadSizes = new ArrayList<>(); + private long value; + + public DynamicChunkSize(long initialValue) { + this.value = initialValue; + } + + public void onTasksRestart() { + if (!downloadSizes.isEmpty()) { + double averageValueDouble = downloadSizes.stream().collect(Collectors.averagingLong(l -> l)); + this.value = (long) averageValueDouble; + downloadSizes.clear(); + } + + } + + @Override + public void processDownloadSuccess(Range range, File file) { + long size = range.getTo() - range.getFrom() + 1; + downloadSizes.add(size); + } + + @Override + public void processDownloadFail(Range range, File file, long downloadedSize, Exception e) { + if (downloadedSize <= 0) + return; + downloadSizes.add(downloadedSize); + } + + public long getValue() { + return value; + } + +} diff --git a/src/main/java/xueli/utils/download/MultiThreadedDownloadInstance.java b/src/main/java/xueli/utils/download/MultiThreadedDownloadInstance.java new file mode 100644 index 00000000..44d874fd --- /dev/null +++ b/src/main/java/xueli/utils/download/MultiThreadedDownloadInstance.java @@ -0,0 +1,61 @@ +package xueli.utils.download; + +import java.io.File; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +class MultiThreadedDownloadInstance extends RangeDownloadInstance { + + private final DynamicChunkSize chunkSize; + + MultiThreadedDownloadInstance(URL url, File output, Map headers, long initialChunkSize, + ExecutorService executor) { + super(url, output, headers, executor); + this.chunkSize = new DynamicChunkSize(initialChunkSize); + } + + @Override + protected void init(URLConnection initialConnection) { + long chunkSize = this.chunkSize.getValue(); + this.submit(new RangeConnection(0, Math.min(chunkSize - 1, fileSize), initialConnection)); + if (chunkSize <= fileSize) { + this.submitDownloadTask(new Range(chunkSize, fileSize)); + } + + } + + @Override + protected void onTasksRestart() { + this.chunkSize.onTasksRestart(); + + } + + @Override + public void processDownloadSuccess(Range range, File file) { + this.chunkSize.processDownloadSuccess(range, file); + super.processDownloadSuccess(range, file); + } + + @Override + public void processDownloadFail(Range range, File file, long downloadedSize, Exception e) { + this.chunkSize.processDownloadFail(range, file, downloadedSize, e); + super.processDownloadFail(range, file, downloadedSize, e); + } + + @Override + protected void submitDownloadTask(Range range) { + long chunkSize = this.chunkSize.getValue(); + long start = range.getFrom(); + long end = range.getTo(); + while (start < end) { + long rangeEnd = start + chunkSize; + Range cutRange = new Range(start, Math.min(rangeEnd - 1, end)); + super.submitDownloadTask(cutRange); + start = rangeEnd; + } + + } + +} diff --git a/src/main/java/xueli/utils/download/RandomUtils.java b/src/main/java/xueli/utils/download/RandomUtils.java new file mode 100644 index 00000000..e680f4b5 --- /dev/null +++ b/src/main/java/xueli/utils/download/RandomUtils.java @@ -0,0 +1,24 @@ +package xueli.utils.download; + +import java.util.ArrayList; +import java.util.Random; + +public class RandomUtils { + + private static final String CHARS = "qwertyuiopasdfghjklzxcvbnm1234567890"; + private static final Random RANDOM = new Random(); + + private static final ArrayList hasRandomStrings = new ArrayList<>(); + + public static String genRandomStr(int length) { + StringBuffer buf = new StringBuffer(length); + RANDOM.ints(length).forEach(i -> buf.append(CHARS.charAt(Math.floorMod(i, CHARS.length())))); + String s = buf.toString(); + if (hasRandomStrings.contains(s)) { + return genRandomStr(length); + } + hasRandomStrings.add(s); + return s; + } + +} diff --git a/src/main/java/xueli/utils/download/Range.java b/src/main/java/xueli/utils/download/Range.java new file mode 100644 index 00000000..a5d6df49 --- /dev/null +++ b/src/main/java/xueli/utils/download/Range.java @@ -0,0 +1,31 @@ +package xueli.utils.download; + +/** + * The Range class indicates the range of the file. What is worth + * noticing is that the range is closed at the both sides, enabling it to apply + * to the HTTP request directly but supposed to be processed when it comes to + * file seek like RandomAccessFile. + */ +public class Range { + + private final long from, to; + + public Range(long from, long to) { + this.from = from; + this.to = to; + } + + public long getFrom() { + return from; + } + + public long getTo() { + return to; + } + + @Override + public String toString() { + return "Range{" + "from=" + from + ", to=" + to + '}'; + } + +} diff --git a/src/main/java/xueli/utils/download/RangeConnection.java b/src/main/java/xueli/utils/download/RangeConnection.java new file mode 100644 index 00000000..6eab101d --- /dev/null +++ b/src/main/java/xueli/utils/download/RangeConnection.java @@ -0,0 +1,33 @@ +package xueli.utils.download; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; + +class RangeConnection extends Range { + + private final URLConnection connection; + + public RangeConnection(long start, long to, URLConnection connection) { + super(start, to); + this.connection = connection; + } + + public static RangeConnection makeConnection(long start, long to, URL url, Map headers) + throws IOException { + URLConnection con = DownloadInstance.createConnection(url, headers); + con.setRequestProperty("Range", String.format("bytes=%d-%d", start, to)); + return new RangeConnection(start, to, con); + } + + public URLConnection getConnection() { + return connection; + } + + @Override + public String toString() { + return "RangeConnection{" + "connection=" + connection + ", start=" + getFrom() + ", to=" + getTo() + '}'; + } + +} diff --git a/src/main/java/xueli/utils/download/RangeDownloadInstance.java b/src/main/java/xueli/utils/download/RangeDownloadInstance.java new file mode 100644 index 00000000..14e80f55 --- /dev/null +++ b/src/main/java/xueli/utils/download/RangeDownloadInstance.java @@ -0,0 +1,78 @@ +package xueli.utils.download; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; + +class RangeDownloadInstance extends DownloadInstance { + + protected CopyOnWriteArrayList fileList = new CopyOnWriteArrayList<>(); + protected CopyOnWriteArrayList toBeDeletedList = new CopyOnWriteArrayList<>(); + + RangeDownloadInstance(URL url, File output, Map headers, ExecutorService executor) { + super(url, output, headers, executor); + } + + @Override + protected void init(URLConnection initialConnection) { + this.submit(new RangeConnection(0, fileSize - 1, initialConnection)); + } + + @Override + public void processDownloadFail(Range range, File file, long downloadedSize, Exception e) { + long downloadEndPointer = range.getFrom() + downloadedSize - 1; + this.submitDownloadTask(new Range(downloadEndPointer + 1, range.getTo())); + if (downloadedSize > 0) { + fileList.add(new RangeFile(range.getFrom(), downloadEndPointer, file)); + } else { + toBeDeletedList.add(file); + } + } + + @Override + public void processDownloadSuccess(Range range, File file) { + fileList.add(new RangeFile(range.getFrom(), range.getTo(), file)); + } + + @Override + protected boolean runTheWorker() { + if (!super.runTheWorker()) + return false; + + try { + return mergeTheFiles(); + } catch (IOException e) { + throw new RuntimeException(e); + } +// return true; + } + + private boolean mergeTheFiles() throws IOException { + byte[] ioBuffer = new byte[4096]; + RandomAccessFile randomAccessFile = new RandomAccessFile(output, "rw"); + randomAccessFile.setLength(fileSize); + for (RangeFile rangeFile : fileList) { + randomAccessFile.seek(rangeFile.getFrom()); + BufferedInputStream in = new BufferedInputStream(new FileInputStream(rangeFile.getFile())); + while (in.available() > 0) { + int readBytes = in.read(ioBuffer); + randomAccessFile.write(ioBuffer, 0, readBytes); + } + in.close(); + } + randomAccessFile.close(); + + fileList.forEach(fragment -> fragment.getFile().delete()); + toBeDeletedList.forEach(File::delete); + + return true; + } + +} diff --git a/src/main/java/xueli/utils/download/RangeFile.java b/src/main/java/xueli/utils/download/RangeFile.java new file mode 100644 index 00000000..870b2295 --- /dev/null +++ b/src/main/java/xueli/utils/download/RangeFile.java @@ -0,0 +1,23 @@ +package xueli.utils.download; + +import java.io.File; + +class RangeFile extends Range { + + private final File file; + + public RangeFile(long start, long to, File file) { + super(start, to); + this.file = file; + } + + public File getFile() { + return file; + } + + @Override + public String toString() { + return "RangeFile{" + "from=" + getFrom() + ", to=" + getTo() + ", file=" + file + '}'; + } + +} diff --git a/src/main/java/xueli/utils/download/SingleThreadDownloadInstance.java b/src/main/java/xueli/utils/download/SingleThreadDownloadInstance.java new file mode 100644 index 00000000..a777a913 --- /dev/null +++ b/src/main/java/xueli/utils/download/SingleThreadDownloadInstance.java @@ -0,0 +1,35 @@ +package xueli.utils.download; + +import java.io.File; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +class SingleThreadDownloadInstance extends DownloadInstance { + + SingleThreadDownloadInstance(URL url, File output, Map headers, ExecutorService executor) { + super(url, output, headers, executor); + } + + @Override + protected void init(URLConnection initialConnection) { + this.submit(new RangeConnection(0, fileSize - 1, initialConnection)); + } + + @Override + protected String genRandomString() { + return null; + } + + @Override + public void processDownloadFail(Range range, File file, long downloadedSize, Exception e) { + this.submitDownloadTask(range); + file.delete(); + } + + @Override + public void processDownloadSuccess(Range range, File file) { + } + +} diff --git a/src/main/java/xueli/utils/events/DeferredEventBus.java b/src/main/java/xueli/utils/events/DeferredEventBus.java new file mode 100644 index 00000000..1852e211 --- /dev/null +++ b/src/main/java/xueli/utils/events/DeferredEventBus.java @@ -0,0 +1,36 @@ +package xueli.utils.events; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class DeferredEventBus { + + private HashMap, Queue> eventStorage = new HashMap<>(); + + public DeferredEventBus() { + } + + public void post(Object obj) { + Class clazz = obj.getClass(); + Queue queue = eventStorage.computeIfAbsent(clazz, c -> new LinkedList<>()); + queue.add(obj); + } + + @SuppressWarnings("unchecked") + public T read(Class clazz) { + Queue queue = eventStorage.get(clazz); + if (queue == null) + return null; + return (T) queue.poll(); + } + + public void clear() { + eventStorage.values().forEach(Queue::clear); + } + + public void copyTo(DeferredEventBus e) { + this.eventStorage.forEach((c, q) -> e.eventStorage.computeIfAbsent(c, c1 -> new LinkedList<>()).addAll(q)); + } + +} diff --git a/src/main/java/xueli/utils/events/EventBus.java b/src/main/java/xueli/utils/events/EventBus.java new file mode 100644 index 00000000..ee74dc18 --- /dev/null +++ b/src/main/java/xueli/utils/events/EventBus.java @@ -0,0 +1,38 @@ +package xueli.utils.events; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.function.Consumer; + +public class EventBus { + + private final HashMap, ArrayList>> registers = new HashMap<>(); + + public EventBus() { + } + + public void register(Class clazz, Consumer listener) { + registers.computeIfAbsent(clazz, c -> new ArrayList<>()).add(listener); + } + + public void unregister(Class clazz, Consumer listener) { + ArrayList> consumers = registers.get(clazz); + if (consumers == null || consumers.isEmpty()) + return; + consumers.remove(listener); + } + + public void post(Object t) { + Class clazz = t.getClass(); + ArrayList> consumers = registers.get(clazz); + if (consumers == null || consumers.isEmpty()) + return; + consumers.forEach(c -> genericPost(t, c)); + } + + @SuppressWarnings("unchecked") + private void genericPost(Object t, Consumer c) { + c.accept((T) t); + } + +} diff --git a/src/main/java/xueli/utils/exception/CrashReport.java b/src/main/java/xueli/utils/exception/CrashReport.java new file mode 100644 index 00000000..9727d91b --- /dev/null +++ b/src/main/java/xueli/utils/exception/CrashReport.java @@ -0,0 +1,72 @@ +package xueli.utils.exception; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +public class CrashReport { + + private static final String NICE_COMMENT[] = { "AbaAbaAba", "Why did things go like this?", + "But it works on my computer /(ToT)/~~", "ElectroBoooooom!", "Also try Marshmello!", "Go get xueli!", + "What bad ideas could xueli be thinking of?" }; + + private String state; + private Throwable e; + + public CrashReport(String state, Throwable e) { + this.e = e; + this.state = state; + } + + public void showCrashReport() { + new Thread(() -> { + e.printStackTrace(); + this.showErrorDialog(getNiceComment(), getMessage()); + }).start(); + + } + + private void showErrorDialog(String title, String message) { + JFrame frame = new JFrame(); + frame.setAlwaysOnTop(true); + JOptionPane.showMessageDialog(frame, message, title, JOptionPane.ERROR_MESSAGE); + frame.dispose(); + + } + + public String getMessage() { + StringBuilder builder = new StringBuilder(); + Throwable t = e; + + builder.append("[" + this.state + "] Exception "); + while (true) { + builder.append(t.getClass().getName()); + if (t.getMessage() != null && !t.getMessage().isBlank()) + builder.append(": ").append(t.getMessage()); + + StackTraceElement[] es = t.getStackTrace(); + for (int i = 0; i < Math.min(es.length, 4); i++) { + StackTraceElement element = es[i]; + builder.append(System.lineSeparator()); + builder.append(" ").append(element.getClassName()).append(":").append(element.getMethodName()) + .append("(").append(element.getFileName()).append(":").append(element.getLineNumber()) + .append(")"); + } + + builder.append(System.lineSeparator()); + + t = t.getCause(); + if (t == null) + break; + + builder.append("Caused by "); + + } + + return builder.toString(); + } + + public String getNiceComment() { + return NICE_COMMENT[(int) (System.currentTimeMillis() % NICE_COMMENT.length)]; + } + +} diff --git a/src/main/java/xueli/utils/io/BufferedRandomAccessFile.java b/src/main/java/xueli/utils/io/BufferedRandomAccessFile.java new file mode 100644 index 00000000..70fe3637 --- /dev/null +++ b/src/main/java/xueli/utils/io/BufferedRandomAccessFile.java @@ -0,0 +1,434 @@ +package xueli.utils.io; + +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + *

+ * http://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. + */ + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; + +/** + * A BufferedRandomAccessFile is like a + * RandomAccessFile, but it uses a private buffer so that most + * operations do not require a disk access. + *

+ *

+ * Note: The operations on this class are unmonitored. Also, the correct + * functioning of the RandomAccessFile methods that are not + * overridden here relies on the implementation of those methods in the + * superclass. + *

+ * To describe the above fields, we introduce the following abstractions for the + * file "f": + *

+ * len(f) the length of the file curr(f) the current position in the file c(f) + * the abstract contents of the file disk(f) the contents of f's backing disk + * file closed(f) true iff the file is closed + *

+ * "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a + * character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if + * "c(f)" contains unflushed writes not reflected in "disk(f)". The flush + * operation has the effect of making "disk(f)" identical to "c(f)". + *

+ * A file is said to be *valid* if the following conditions hold: + *

+ * V1. The "closed" and "curr" fields are correct: + *

+ * f.closed == closed(f) f.curr == curr(f) + *

+ * V2. The current position is either contained in the buffer, or just past the + * buffer: + *

+ * f.lo <= f.curr <= f.hi + *

+ * V3. Any (possibly) un-flushed characters are stored in "f.buff": + *

+ * (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo]) + *

+ * V4. For all characters not covered by V3, c(f) and disk(f) agree: + *

+ * (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] == + * disk(f)[i]) + *

+ * V5. "f.dirty" is true iff the buffer contains bytes that should be flushed to + * the file; by V3 and V4, only part of the buffer can be dirty. + *

+ * f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo]) + *

+ * V6. this.maxHi == this.lo + this.buff.length + *

+ * Note that "f.buff" can be "null" in a valid file, since the range of + * characters in V3 is empty when "f.lo == f.curr". + *

+ * A file is said to be *ready* if the buffer contains the current position, + * i.e., when: + *

+ * R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi + *

+ * When a file is ready, reading or writing a single byte can be performed by + * reading or writing the in-memory buffer without performing a disk operation. + *

+ *

+ * !!!This class come from network ,I just adjust code style!!! + * + * @author zhaofeng + * @date 2018 -05-02 + */ +public final class BufferedRandomAccessFile extends RandomAccessFile { + /** + * 64K buffer + */ + private static final int LOG_BUFF_SZ = 16; + private static final int BUFF_SZ = (1 << LOG_BUFF_SZ); + private static final long BUFF_MASK = ~(((long) BUFF_SZ) - 1L); + private String path_; + + /** + * This implementation is based on the buffer implementation in Modula-3's "Rd", + * "Wr", "RdClass", and "WrClass" interfaces. + *

+ * true iff un-flushed bytes exist + */ + private boolean dirty_; + /** + * dirty_ can be cleared by e.g. seek, so track sync separately + */ + private boolean syncNeeded_; + /** + * current position in file + */ + private long curr_; + /** + * bounds on characters in "buff" + */ + private long lo_, hi_; + /** + * local buffer + */ + private byte[] buff_; + /** + * this.lo + this.buff.length + */ + private long maxHi_; + /** + * buffer contains last file block? + */ + private boolean hitEOF_; + /** + * disk position + */ + private long diskPos_; + + /** + * Open a new BufferedRandomAccessFile on file in mode + * mode, which should be "r" for reading only, or "rw" for reading + * and writing. + * + * @param file the file + * @param mode the mode + * @throws IOException the io exception + */ + public BufferedRandomAccessFile(File file, String mode) throws IOException { + this(file, mode, 0); + } + + /** + * Instantiates a new Buffered random access file. + * + * @param file the file + * @param mode the mode + * @param size the size + * @throws IOException the io exception + */ + public BufferedRandomAccessFile(File file, String mode, int size) throws IOException { + super(file, mode); + path_ = file.getAbsolutePath(); + this.init(size); + } + + /** + * Open a new BufferedRandomAccessFile on the file named + * name in mode mode, which should be "r" for reading + * only, or "rw" for reading and writing. + * + * @param name the name + * @param mode the mode + * @throws IOException the io exception + */ + public BufferedRandomAccessFile(String name, String mode) throws IOException { + this(name, mode, 0); + } + + /** + * Instantiates a new Buffered random access file. + * + * @param name the name + * @param mode the mode + * @param size the size + * @throws FileNotFoundException the file not found exception + */ + public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException { + super(name, mode); + path_ = name; + this.init(size); + } + + private void init(int size) { + this.dirty_ = false; + this.lo_ = this.curr_ = this.hi_ = 0; + this.buff_ = (size > BUFF_SZ) ? new byte[size] : new byte[BUFF_SZ]; + this.maxHi_ = (long) BUFF_SZ; + this.hitEOF_ = false; + this.diskPos_ = 0L; + } + + /** + * Gets path. + * + * @return the path + */ + public String getPath() { + return path_; + } + + /** + * Sync. + * + * @throws IOException the io exception + */ + public void sync() throws IOException { + if (syncNeeded_) { + flush(); + getChannel().force(true); + syncNeeded_ = false; + } + } + + @Override + public void close() throws IOException { + this.flush(); + this.buff_ = null; + super.close(); + } + + /** + * Flush any bytes in the file's buffer that have not yet been written to disk. + * If the file was created read-only, this method is a no-op. + * + * @throws IOException the io exception + */ + public void flush() throws IOException { + this.flushBuffer(); + } + + /** + * Flush any dirty bytes in the buffer to disk. + */ + private void flushBuffer() throws IOException { + if (this.dirty_) { + if (this.diskPos_ != this.lo_) { + super.seek(this.lo_); + } + int len = (int) (this.curr_ - this.lo_); + super.write(this.buff_, 0, len); + this.diskPos_ = this.curr_; + this.dirty_ = false; + } + } + + /** + * Read at most "this.buff.length" bytes into "this.buff", returning the number + * of bytes read. If the return result is less than "this.buff.length", then EOF + * was read. + */ + private int fillBuffer() throws IOException { + int cnt = 0; + int rem = this.buff_.length; + while (rem > 0) { + int n = super.read(this.buff_, cnt, rem); + if (n < 0) { + break; + } + cnt += n; + rem -= n; + } + if ((cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length))) { + // make sure buffer that wasn't read is initialized with -1 + Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff); + } + this.diskPos_ += cnt; + return cnt; + } + + /** + * This method positions this.curr at position pos. If + * pos does not fall in the current buffer, it flushes the current + * buffer and loads the correct one. + *

+ *

+ * On exit from this routine this.curr == this.hi iff + * pos is at or past the end-of-file, which can only happen if the + * file was opened in read-only mode. + */ + @Override + public void seek(long pos) throws IOException { + if (pos >= this.hi_ || pos < this.lo_) { + // seeking outside of current buffer -- flush and read + this.flushBuffer(); + this.lo_ = pos & BUFF_MASK; // start at BuffSz boundary + this.maxHi_ = this.lo_ + (long) this.buff_.length; + if (this.diskPos_ != this.lo_) { + super.seek(this.lo_); + this.diskPos_ = this.lo_; + } + int n = this.fillBuffer(); + this.hi_ = this.lo_ + (long) n; + } else { + // seeking inside current buffer -- no read required + if (pos < this.curr_) { + // if seeking backwards, we must flush to maintain V4 + this.flushBuffer(); + } + } + this.curr_ = pos; + } + + @Override + public long getFilePointer() { + return this.curr_; + } + + /** + * max accounts for the case where we have written past the old file length, but + * not yet flushed our buffer + * + * @return + * @throws IOException + */ + @Override + public long length() throws IOException { + return Math.max(this.curr_, super.length()); + } + + @Override + public int read() throws IOException { + if (readEnd()) { + return -1; + } + byte res = this.buff_[(int) (this.curr_ - this.lo_)]; + this.curr_++; + return ((int) res) & 0xFF; // convert byte -> int + } + + @Override + public int read(byte[] b) throws IOException { + return this.read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (readEnd()) { + return -1; + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(this.buff_, buffOff, b, off, len); + this.curr_ += len; + return len; + } + + private boolean readEnd() throws IOException { + if (this.curr_ >= this.hi_) { + // test for EOF + // if (this.hi < this.maxHi) return -1; + if (this.hitEOF_) { + return true; + } + // slow path -- read another buffer + this.seek(this.curr_); + if (this.curr_ == this.hi_) { + return true; + } + } + return false; + } + + @Override + public void write(int b) throws IOException { + if (this.curr_ >= this.hi_) { + if (this.hitEOF_ && this.hi_ < this.maxHi_) { + // at EOF -- bump "hi" + this.hi_++; + } else { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) { + // appending to EOF -- bump "hi" + this.hi_++; + } + } + } + this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b; + this.curr_++; + this.dirty_ = true; + syncNeeded_ = true; + } + + @Override + public void write(byte[] b) throws IOException { + this.write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int n = this.writeAtMost(b, off, len); + off += n; + len -= n; + this.dirty_ = true; + syncNeeded_ = true; + } + } + + /** + * Write at most "len" bytes to "b" starting at position "off", and return the + * number of bytes written. + */ + private int writeAtMost(byte[] b, int off, int len) throws IOException { + if (this.curr_ >= this.hi_) { + if (this.hitEOF_ && this.hi_ < this.maxHi_) { + // at EOF -- bump "hi" + this.hi_ = this.maxHi_; + } else { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) { + // appending to EOF -- bump "hi" + this.hi_ = this.maxHi_; + } + } + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(b, off, this.buff_, buffOff, len); + this.curr_ += len; + return len; + } +} diff --git a/src/main/java/xueli/utils/io/FileInspector.java b/src/main/java/xueli/utils/io/FileInspector.java new file mode 100644 index 00000000..69bbdc07 --- /dev/null +++ b/src/main/java/xueli/utils/io/FileInspector.java @@ -0,0 +1,87 @@ +package xueli.utils.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.fusesource.jansi.Ansi; + +import xueli.utils.StringTokens; + +public class FileInspector implements AutoCloseable { + + public static final int COLUME_BYTE_COUNT = 64; + + private final RandomAccessFile file; + + public FileInspector(File file) throws FileNotFoundException { + this.file = new RandomAccessFile(file, "r"); + } + + public long getLength() throws IOException { + return file.length(); + } + + public void inspect(long start, long end) throws IOException { + long length = getLength(); + if (start > length) + throw new IOException("Can't seek above file length: " + start); + if (end < start) + throw new IOException("end < start: " + end + " < " + start); + start = Math.max(start, 0); + end = Math.min(end, length); + + file.seek(start); + + Ansi a = Ansi.ansi(); + long pointer = (start / COLUME_BYTE_COUNT) * COLUME_BYTE_COUNT; + file.seek(pointer); + + int bytePrintCounter = COLUME_BYTE_COUNT; // initial value + byte[] temp = new byte[COLUME_BYTE_COUNT]; + + while (pointer < end) { + if (bytePrintCounter == COLUME_BYTE_COUNT) { + a.a(" | "); + for (int i = 0; i < COLUME_BYTE_COUNT; i++) { + if (Character.isISOControl(temp[i])) { + a.a(' '); + } else { + a.a((char) temp[i]); + } + } + + String startHex = Long.toHexString(pointer); + a.newline(); + a.a(String.format("%8s | ", startHex)); + + bytePrintCounter = 0; + file.read(temp); + + } + + if (pointer >= start && pointer < end) { + String byteHex = Integer.toHexString(temp[bytePrintCounter]); + byteHex = StringTokens.padLeft(byteHex, 2, '0'); + a.a(byteHex).a(" "); + + pointer++; + bytePrintCounter++; + + } + + bytePrintCounter++; + + } + + System.out.println(a.toString()); + + } + + @Override + public void close() throws Exception { + file.close(); + } + +} diff --git a/src/main/java/xueli/utils/io/Files.java b/src/main/java/xueli/utils/io/Files.java new file mode 100644 index 00000000..5707bc77 --- /dev/null +++ b/src/main/java/xueli/utils/io/Files.java @@ -0,0 +1,191 @@ +package xueli.utils.io; + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URLDecoder; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import javax.imageio.ImageIO; + +import org.lwjgl.BufferUtils; + +public class Files { + + public static void fileOutput(String name, String content) throws IOException { + fileOutput(name, content.getBytes(StandardCharsets.UTF_8)); + + } + + public static void fileOutput(String name, byte[] bytes) throws IOException { + File file = new File(name); + if (!file.exists()) + file.createNewFile(); + + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + out.write(bytes); + out.flush(); + out.close(); + } + + public static void fileOutput(String name, int[] bytes) throws IOException { + File file = new File(name); + if (!file.exists()) + file.createNewFile(); + + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + DataOutputStream stream = new DataOutputStream(out); + for (int i = 0; i < bytes.length; i++) { + stream.writeInt(bytes[i]); + } + out.flush(); + out.close(); + } + + public static void mkDir(String path) { + new File(path).mkdirs(); + + } + + public static String readAllString(File file) throws IOException { + return new String(readAllByte(file)); + } + + public static String readAllStringFromIn(InputStream in) throws IOException { + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + + public static byte[] readAllByte(File file) throws IOException { + BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); + byte[] data = new byte[in.available()]; + in.read(data); + in.close(); + return data; + } + + private static void getAllFiles(File file, List list) { + + if(file.isDirectory()) { + File[] allFiles = file.listFiles(); + for(int i = 0; i < allFiles.length; ++i) { + getAllFiles(file, list); + } + + } + + else + list.add(file); + + } + + public static ArrayList getAllFiles(File file) { + ArrayList list=new ArrayList<>(); + getAllFiles(file,list); + return list; + } + + public static ArrayList getAllFiles(String path) { + return getAllFiles(new File(path)); + } + + public static int[] readImageAndReturnRawData(String path) { + BufferedImage image = null; + try { + image = ImageIO.read(new File(path)); + } catch (IOException e) { + e.printStackTrace(); + } + int[] data = new int[image.getWidth() * image.getHeight()]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), data, 0, image.getWidth()); + return data; + } + /** + * Incorrect implementation + * A file packed in a jar or zip file cannot be presented as a File object at all. + * If the code is packed, this function works incorrectly. + * Use an alternative to solve it. + * This function is tested by Java_Herobrine, but he is unable to fix it. + * @author Java_Herobrine + */ + @Deprecated + public static File getResourcePackedInJar(String path) { + File f = new File(URLDecoder.decode(Thread.currentThread().getContextClassLoader().getResource(path).getPath(), + StandardCharsets.UTF_8)); + System.err.print("Method getResourcePackedInJar is invoked with parameter: "+path); + System.err.println(f); + System.err.println(f.exists()); + return f; + } + + public static InputStream getResourcePackedInJarStream(String path) { + return Files.class.getResourceAsStream(path); + } + + public static byte[] readResourcePackedInJar(String path) throws IOException { + InputStream in = Files.class.getResourceAsStream(path); + if (in == null) + throw new IOException("Stream is null! Maybe the file doesn't exist?"); + byte[] all = in.readAllBytes(); + in.close(); + return all; + } + + private static ArrayList stacks = new ArrayList<>(); + + public static ByteBuffer readResourcePackedInJarAndPackedToBuffer(String path) throws IOException { + byte[] all = readResourcePackedInJar(path); + ByteBuffer buffer = BufferUtils.createByteBuffer(all.length); + stacks.add(buffer); + return buffer.put(all).flip(); + } + + public static String readResourcePackedInJarAndPackedToString(String path) throws IOException { + byte[] all = readResourcePackedInJar(path); + return new String(all); + } + + public static void writeObject(Object obj, File file) throws Exception { + FileOutputStream out = new FileOutputStream(file); + ObjectOutputStream oo = new ObjectOutputStream(out); + oo.writeObject(obj); + oo.flush(); + oo.close(); + } + + public static Object readObject(File file) throws Exception { + FileInputStream in = new FileInputStream(file); + ObjectInputStream oi = new ObjectInputStream(in); + Object o = oi.readObject(); + oi.close(); + return o; + } + + public static String getNameExcludeSuffix(String fileName) { + String dest = fileName; + int lastIndex = dest.lastIndexOf('.'); + if (lastIndex != -1) + dest = dest.substring(0, lastIndex); + int lastIndexSeparator = dest.lastIndexOf(File.separatorChar); + if (lastIndexSeparator != -1) + dest = dest.substring(lastIndexSeparator + 1); + return dest; + } + + public static String getFileExtension(String path) { + String fileName = new File(path).getName(); + int index = fileName.lastIndexOf('.'); + return (index == -1) ? "" : fileName.substring(index + 1); + } + +} diff --git a/src/main/java/xueli/utils/io/FolderFilter.java b/src/main/java/xueli/utils/io/FolderFilter.java new file mode 100644 index 00000000..826f3bbc --- /dev/null +++ b/src/main/java/xueli/utils/io/FolderFilter.java @@ -0,0 +1,15 @@ +package xueli.utils.io; + +import java.io.File; +import java.io.FileFilter; + +public class FolderFilter implements FileFilter { + + public static final FolderFilter INSTANCE = new FolderFilter(); + + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + +} diff --git a/src/main/java/xueli/utils/io/SuffixFilter.java b/src/main/java/xueli/utils/io/SuffixFilter.java new file mode 100644 index 00000000..e79e1d92 --- /dev/null +++ b/src/main/java/xueli/utils/io/SuffixFilter.java @@ -0,0 +1,19 @@ +package xueli.utils.io; + +import java.io.File; +import java.io.FileFilter; + +public class SuffixFilter implements FileFilter { + + private String suffix; + + public SuffixFilter(String suffix) { + this.suffix = "." + suffix; + } + + @Override + public boolean accept(File pathname) { + return pathname.getName().endsWith(suffix); + } + +} diff --git a/src/main/java/xueli/utils/logger/InvokeDaemon.java b/src/main/java/xueli/utils/logger/InvokeDaemon.java new file mode 100644 index 00000000..a3048c87 --- /dev/null +++ b/src/main/java/xueli/utils/logger/InvokeDaemon.java @@ -0,0 +1,35 @@ +package xueli.utils.logger; + +// Used to observe the method invoke +public class InvokeDaemon { + + private final Class clazz; + private boolean enable = false; + + public InvokeDaemon(Class clazz) { + this.clazz = clazz; + + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public boolean isEnable() { + return enable; + } + + public void announce() { + if(!enable) return; + + StackTraceElement[] es = Thread.currentThread().getStackTrace(); + StackTraceElement methodSource = es[2]; + StackTraceElement invoker = es[3]; + + if(invoker.getClassName().equals(clazz.getName())) return; + + System.out.println("[" + clazz.getSimpleName() + "] " + methodSource.getMethodName() + " from " + invoker.getFileName() + ":" + invoker.getLineNumber()); + + } + +} diff --git a/src/main/java/xueli/utils/logger/Logger.java b/src/main/java/xueli/utils/logger/Logger.java new file mode 100644 index 00000000..b39258f6 --- /dev/null +++ b/src/main/java/xueli/utils/logger/Logger.java @@ -0,0 +1,134 @@ +package xueli.utils.logger; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Objects; +import java.util.logging.Level; + +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Attribute; +import org.fusesource.jansi.AnsiConsole; + +public class Logger { + +// private static final Ansi.Color STATE_COLOR = Ansi.Color.BLUE; + + private static final HashMap textColor = new HashMap<>(); + + static { +// AnsiConsole.systemInstall(); + + textColor.put(Level.INFO, Ansi.Color.GREEN); + textColor.put(Level.SEVERE, Ansi.Color.RED); + textColor.put(Level.WARNING, Ansi.Color.YELLOW); + + } + + private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("HH:mm:ss"); + +// private final Stack state = new Stack<>(); + + private static Logger DEFAULT_LOGGER = new Logger(); + + public static Logger getInstance() { + return DEFAULT_LOGGER; + } + +// public void pushState(String state) { +// this.state.push(state); +// } +// +// public void popState() { +// state.pop(); +// } + + public void info(Object o) { + wrapLog(o, Level.INFO); + } + + public void error(Object o) { + wrapLog(o, Level.SEVERE); + } + + public void warning(Object o) { + wrapLog(o, Level.WARNING); + } + + private void wrapLog(Object s, Level l) { + StackTraceElement[] es = Thread.currentThread().getStackTrace(); + StackTraceElement ste = es[3]; + + Ansi.Color color = textColor.get(l); + if (color == null) + color = Ansi.Color.DEFAULT; + + Ansi a = Ansi.ansi(); + int consoleWidth = AnsiConsole.getTerminalWidth(); + if (consoleWidth == 0) + consoleWidth = 100; + + String timeStr = DATE_FORMATTER.format(new Date()); + String pathStr = String.format("%s:%d", ste.getFileName(), ste.getLineNumber()); + int contentLeftStart = timeStr.length() + 1; + int contentRightEnd = consoleWidth - pathStr.length() - 1; + int contentPerLine = contentRightEnd - contentLeftStart; + String contentStr = Objects.toString(s); + int oneLineLogSpace = contentPerLine - contentStr.length(); + + a.reset(); + if (contentPerLine <= 0) { + a.a(timeStr).newline(); + a.a(Attribute.ITALIC).a(pathStr).a(Attribute.ITALIC_OFF).newline(); + a.fg(color).render(contentStr).fgDefault().newline(); + } else { + a.a(timeStr).a(" "); + + if (oneLineLogSpace < 0) { + int lineStartInStr = 0, lineEndInStr = contentPerLine; + a.fg(color).render(contentStr.substring(lineStartInStr, lineEndInStr)).fgDefault(); + lineStartInStr += contentPerLine; + lineEndInStr += contentPerLine; + + a.a(" ").a(Attribute.ITALIC).a(pathStr).a(Attribute.ITALIC_OFF).newline(); + + while (true) { + if (lineStartInStr >= contentStr.length()) + break; + lineEndInStr = Math.min(contentStr.length(), lineEndInStr); + + a.a(" ".repeat(contentLeftStart)); + a.fg(color).render(contentStr.substring(lineStartInStr, lineEndInStr)).fgDefault(); + a.newline(); + + lineStartInStr += contentPerLine; + lineEndInStr += contentPerLine; + + } + + } else { + a.fg(color).render(contentStr).fgDefault().a(" ".repeat(oneLineLogSpace)).a(" ").a(Attribute.ITALIC) + .a(pathStr).newline(); + + } + + } + + a.reset(); + System.out.print(a); + + } + +// private String stackOut() { +// StringBuilder b = new StringBuilder(); +// if (!state.empty()) { +// for (int i = 0; i < state.size() - 1; i++) { +// String e = state.get(i); +// b.append(e).append(": "); +// } +// b.append(state.peek()); +// } +// return b.toString(); +// } + +} diff --git a/src/main/java/xueli/utils/mojang/SkinGetter.java b/src/main/java/xueli/utils/mojang/SkinGetter.java new file mode 100644 index 00000000..8d7b6c46 --- /dev/null +++ b/src/main/java/xueli/utils/mojang/SkinGetter.java @@ -0,0 +1,66 @@ +package xueli.utils.mojang; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Base64; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import xueli.utils.io.Files; +import xueli.utils.logger.Logger; + +public class SkinGetter { + + private static final Logger LOGGER = new Logger(); + + public static boolean saveSkin(String playerName, String path) { + try { + URL uuidUrl = new URL("https://api.mojang.com/users/profiles/minecraft/" + playerName); + InputStream uuidIn = uuidUrl.openStream(); + JsonObject uuidObj = new Gson().fromJson(new InputStreamReader(uuidIn), JsonObject.class); + uuidIn.close(); + if (uuidObj == null) { + LOGGER.error("Found no player named: " + playerName); + return false; + } + String uuid = uuidObj.get("id").getAsString(); + + URL profileUrl = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid); + InputStream profileIn = profileUrl.openStream(); + JsonObject profileObj = new Gson().fromJson(new InputStreamReader(profileIn), JsonObject.class); + profileIn.close(); + String textureValue = profileObj.get("properties").getAsJsonArray().get(0).getAsJsonObject().get("value") + .getAsString(); + String decodedTextureJson = new String(Base64.getDecoder().decode(textureValue)); + + JsonObject textureJson = new Gson().fromJson(decodedTextureJson, JsonObject.class); + String textureUrlString = textureJson.get("textures").getAsJsonObject().get("SKIN").getAsJsonObject() + .get("url").getAsString(); + LOGGER.info("Get player skin path: " + textureUrlString); + URL textureUrl = new URL(textureUrlString); + InputStream textureIn = textureUrl.openStream(); + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + int data = 0; + while ((data = textureIn.read()) != -1) { + byteOut.write(data); + } + + Files.fileOutput(path, byteOut.toByteArray()); + + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public static void main(String[] args) { + + } + +} diff --git a/src/main/java/xueli/utils/notifiable/LazyNotifiableValue.java b/src/main/java/xueli/utils/notifiable/LazyNotifiableValue.java new file mode 100644 index 00000000..050ec718 --- /dev/null +++ b/src/main/java/xueli/utils/notifiable/LazyNotifiableValue.java @@ -0,0 +1,26 @@ +package xueli.utils.notifiable; + +public class LazyNotifiableValue extends NotifiableValue { + + private T lazyValue = null; + + public LazyNotifiableValue() { + } + + public LazyNotifiableValue(T initialValue) { + super(initialValue); + } + + @Override + public void set(T value) { + this.lazyValue = value; + } + + public void flush() { + if (this.lazyValue != null && !this.lazyValue.equals(this.getValue())) { + super.set(this.lazyValue); + this.lazyValue = null; + } + } + +} diff --git a/src/main/java/xueli/utils/notifiable/NotifiableValue.java b/src/main/java/xueli/utils/notifiable/NotifiableValue.java new file mode 100644 index 00000000..7c97e756 --- /dev/null +++ b/src/main/java/xueli/utils/notifiable/NotifiableValue.java @@ -0,0 +1,31 @@ +package xueli.utils.notifiable; + +import java.util.ArrayList; +import java.util.function.Consumer; + +public class NotifiableValue { + + private T value = null; + private ArrayList> listeners = new ArrayList<>(); + + public NotifiableValue() { + } + + public NotifiableValue(T initialValue) { + this.value = initialValue; + } + + public void addListeners(Consumer listener) { + listeners.add(listener); + } + + public void set(T value) { + this.value = value; + listeners.forEach(c -> c.accept(value)); + } + + public T getValue() { + return value; + } + +} diff --git a/src/main/java/xueli/utils/properties/Parsable.java b/src/main/java/xueli/utils/properties/Parsable.java new file mode 100644 index 00000000..d4649443 --- /dev/null +++ b/src/main/java/xueli/utils/properties/Parsable.java @@ -0,0 +1,5 @@ +package xueli.utils.properties; + +public interface Parsable { + public T parse(String s) throws Exception; +} diff --git a/src/main/java/xueli/utils/properties/PropertiesReflection.java b/src/main/java/xueli/utils/properties/PropertiesReflection.java new file mode 100644 index 00000000..1a69d82a --- /dev/null +++ b/src/main/java/xueli/utils/properties/PropertiesReflection.java @@ -0,0 +1,95 @@ +package xueli.utils.properties; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import xueli.utils.logger.Logger; + +public class PropertiesReflection { + + private static final Logger LOGGER = new Logger(); + + private static HashMap, Parsable> parsers = new HashMap<>(); + + static { + parsers.put(boolean.class, Boolean::parseBoolean); + parsers.put(byte.class, Byte::parseByte); + parsers.put(short.class, Short::parseShort); + parsers.put(int.class, Integer::parseInt); + parsers.put(long.class, Long::parseLong); + parsers.put(float.class, Float::parseFloat); + parsers.put(double.class, Double::parseDouble); + parsers.put(char.class, (s)->{return s.charAt(0);}); + parsers.put(String.class, (s)->{return s;}); + + } + + public static void registerParser(Parsable parsable, Class clazz) { + parsers.put(clazz, parsable); + + } + + public static void reflect(Object obj, File properties) throws Exception { +// Logger.getInstance().pushState("Reflection"); + + Properties p = new Properties(); + p.load(new FileInputStream(properties)); + + boolean instance = !(obj instanceof Class); + Class objClazz = instance ? obj.getClass() : (Class) obj; + HashMap> annotations = new HashMap<>(); + Field[] fields0 = objClazz.getDeclaredFields();//It's slow + for (Field f : fields0) { + Property property = f.getAnnotation(Property.class); + if (property == null) + continue; + String pName = property.value(); + + if (!annotations.containsKey(pName)) + annotations.put(pName, new ArrayList<>()); + annotations.get(pName).add(f); + + try { + f.setAccessible(true); + }catch(InaccessibleObjectException ee) { + LOGGER.error("LovelyZeeiam may hate modules and private fields. ^v^"); + continue; + } + + } + + Object modifyTarget = instance ? obj : null; + + for (Map.Entry entry : p.entrySet()) { + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + + ArrayList fields = annotations.get(key); + if (fields == null) + continue; + + for (Field field : fields) { + Class fieldClazz = field.getType(); + if (parsers.containsKey(fieldClazz)) { + Parsable parser = parsers.get(fieldClazz); + field.set(modifyTarget, parser.parse(value)); + } else { + LOGGER.warning("Not supported field type \"" + fieldClazz.getName() + "\" when setting key \"" + key + + "\" in field \"" + field.getName() + "\""); + } + + } + +// Logger.getInstance().popState(); + + } + + } + +} diff --git a/src/main/java/xueli/utils/properties/Property.java b/src/main/java/xueli/utils/properties/Property.java new file mode 100644 index 00000000..ec1dee9e --- /dev/null +++ b/src/main/java/xueli/utils/properties/Property.java @@ -0,0 +1,14 @@ +package xueli.utils.properties; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD }) +@Documented +public @interface Property { + public String value(); +} diff --git a/src/main/resources/assets/minecraft/armor/chain_1.png b/src/main/resources/assets/minecraft/armor/chain_1.png new file mode 100644 index 00000000..3632af5b Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/chain_1.png differ diff --git a/src/main/resources/assets/minecraft/armor/chain_2.png b/src/main/resources/assets/minecraft/armor/chain_2.png new file mode 100644 index 00000000..330425b1 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/chain_2.png differ diff --git a/src/main/resources/assets/minecraft/armor/cloth_1.png b/src/main/resources/assets/minecraft/armor/cloth_1.png new file mode 100644 index 00000000..f3cf4aa3 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/cloth_1.png differ diff --git a/src/main/resources/assets/minecraft/armor/cloth_2.png b/src/main/resources/assets/minecraft/armor/cloth_2.png new file mode 100644 index 00000000..15fb9084 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/cloth_2.png differ diff --git a/src/main/resources/assets/minecraft/armor/diamond_1.png b/src/main/resources/assets/minecraft/armor/diamond_1.png new file mode 100644 index 00000000..339da658 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/diamond_1.png differ diff --git a/src/main/resources/assets/minecraft/armor/diamond_2.png b/src/main/resources/assets/minecraft/armor/diamond_2.png new file mode 100644 index 00000000..c220c123 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/diamond_2.png differ diff --git a/src/main/resources/assets/minecraft/armor/gold_1.png b/src/main/resources/assets/minecraft/armor/gold_1.png new file mode 100644 index 00000000..885f309b Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/gold_1.png differ diff --git a/src/main/resources/assets/minecraft/armor/gold_2.png b/src/main/resources/assets/minecraft/armor/gold_2.png new file mode 100644 index 00000000..9d1ea3b3 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/gold_2.png differ diff --git a/src/main/resources/assets/minecraft/armor/iron_1.png b/src/main/resources/assets/minecraft/armor/iron_1.png new file mode 100644 index 00000000..374ab076 Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/iron_1.png differ diff --git a/src/main/resources/assets/minecraft/armor/iron_2.png b/src/main/resources/assets/minecraft/armor/iron_2.png new file mode 100644 index 00000000..53af4f4d Binary files /dev/null and b/src/main/resources/assets/minecraft/armor/iron_2.png differ diff --git a/src/main/resources/assets/minecraft/art/kz.png b/src/main/resources/assets/minecraft/art/kz.png new file mode 100644 index 00000000..dc6bd6b3 Binary files /dev/null and b/src/main/resources/assets/minecraft/art/kz.png differ diff --git a/src/main/resources/assets/minecraft/environment/clouds.png b/src/main/resources/assets/minecraft/environment/clouds.png new file mode 100644 index 00000000..b4a78c2f Binary files /dev/null and b/src/main/resources/assets/minecraft/environment/clouds.png differ diff --git a/src/main/resources/assets/minecraft/environment/rain.png b/src/main/resources/assets/minecraft/environment/rain.png new file mode 100644 index 00000000..e9dc16c6 Binary files /dev/null and b/src/main/resources/assets/minecraft/environment/rain.png differ diff --git a/src/main/resources/assets/minecraft/environment/snow.png b/src/main/resources/assets/minecraft/environment/snow.png new file mode 100644 index 00000000..84417c5c Binary files /dev/null and b/src/main/resources/assets/minecraft/environment/snow.png differ diff --git a/src/main/resources/assets/minecraft/font/default.png b/src/main/resources/assets/minecraft/font/default.png new file mode 100644 index 00000000..fb7686ea Binary files /dev/null and b/src/main/resources/assets/minecraft/font/default.png differ diff --git a/src/main/resources/assets/minecraft/gui/background.png b/src/main/resources/assets/minecraft/gui/background.png new file mode 100644 index 00000000..b29e0092 Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/background.png differ diff --git a/src/main/resources/assets/minecraft/gui/container.png b/src/main/resources/assets/minecraft/gui/container.png new file mode 100644 index 00000000..bd1d383c Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/container.png differ diff --git a/src/main/resources/assets/minecraft/gui/crafting.png b/src/main/resources/assets/minecraft/gui/crafting.png new file mode 100644 index 00000000..da831189 Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/crafting.png differ diff --git a/src/main/resources/assets/minecraft/gui/furnace.png b/src/main/resources/assets/minecraft/gui/furnace.png new file mode 100644 index 00000000..a5834e19 Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/furnace.png differ diff --git a/src/main/resources/assets/minecraft/gui/gui.png b/src/main/resources/assets/minecraft/gui/gui.png new file mode 100644 index 00000000..81af329e Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/gui.png differ diff --git a/src/main/resources/assets/minecraft/gui/icons.png b/src/main/resources/assets/minecraft/gui/icons.png new file mode 100644 index 00000000..73fe9bbd Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/icons.png differ diff --git a/src/main/resources/assets/minecraft/gui/inventory.png b/src/main/resources/assets/minecraft/gui/inventory.png new file mode 100644 index 00000000..0b5f2916 Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/inventory.png differ diff --git a/src/main/resources/assets/minecraft/gui/items.png b/src/main/resources/assets/minecraft/gui/items.png new file mode 100644 index 00000000..ea7d4f7c Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/items.png differ diff --git a/src/main/resources/assets/minecraft/gui/logo.png b/src/main/resources/assets/minecraft/gui/logo.png new file mode 100644 index 00000000..b7c28795 Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/logo.png differ diff --git a/src/main/resources/assets/minecraft/gui/unknown_pack.png b/src/main/resources/assets/minecraft/gui/unknown_pack.png new file mode 100644 index 00000000..3a45a90e Binary files /dev/null and b/src/main/resources/assets/minecraft/gui/unknown_pack.png differ diff --git a/src/main/resources/assets/minecraft/item/arrows.png b/src/main/resources/assets/minecraft/item/arrows.png new file mode 100644 index 00000000..75c58287 Binary files /dev/null and b/src/main/resources/assets/minecraft/item/arrows.png differ diff --git a/src/main/resources/assets/minecraft/item/boat.png b/src/main/resources/assets/minecraft/item/boat.png new file mode 100644 index 00000000..132a0f7c Binary files /dev/null and b/src/main/resources/assets/minecraft/item/boat.png differ diff --git a/src/main/resources/assets/minecraft/item/cart.png b/src/main/resources/assets/minecraft/item/cart.png new file mode 100644 index 00000000..32af68e3 Binary files /dev/null and b/src/main/resources/assets/minecraft/item/cart.png differ diff --git a/src/main/resources/assets/minecraft/item/door.png b/src/main/resources/assets/minecraft/item/door.png new file mode 100644 index 00000000..52df2d92 Binary files /dev/null and b/src/main/resources/assets/minecraft/item/door.png differ diff --git a/src/main/resources/assets/minecraft/item/sign.png b/src/main/resources/assets/minecraft/item/sign.png new file mode 100644 index 00000000..e8294724 Binary files /dev/null and b/src/main/resources/assets/minecraft/item/sign.png differ diff --git a/src/main/resources/assets/minecraft/misc/dial.png b/src/main/resources/assets/minecraft/misc/dial.png new file mode 100644 index 00000000..140e7e34 Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/dial.png differ diff --git a/src/main/resources/assets/minecraft/misc/foliagecolor.png b/src/main/resources/assets/minecraft/misc/foliagecolor.png new file mode 100644 index 00000000..a98e378f Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/foliagecolor.png differ diff --git a/src/main/resources/assets/minecraft/misc/grasscolor.png b/src/main/resources/assets/minecraft/misc/grasscolor.png new file mode 100644 index 00000000..a6d9c209 Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/grasscolor.png differ diff --git a/src/main/resources/assets/minecraft/misc/pumpkinblur.png b/src/main/resources/assets/minecraft/misc/pumpkinblur.png new file mode 100644 index 00000000..c6e2ffc9 Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/pumpkinblur.png differ diff --git a/src/main/resources/assets/minecraft/misc/shadow.png b/src/main/resources/assets/minecraft/misc/shadow.png new file mode 100644 index 00000000..06d999b2 Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/shadow.png differ diff --git a/src/main/resources/assets/minecraft/misc/vignette.png b/src/main/resources/assets/minecraft/misc/vignette.png new file mode 100644 index 00000000..f236acb3 Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/vignette.png differ diff --git a/src/main/resources/assets/minecraft/misc/water.png b/src/main/resources/assets/minecraft/misc/water.png new file mode 100644 index 00000000..8b92f9bc Binary files /dev/null and b/src/main/resources/assets/minecraft/misc/water.png differ diff --git a/src/main/resources/assets/minecraft/mob/char.png b/src/main/resources/assets/minecraft/mob/char.png new file mode 100644 index 00000000..7cfa08a8 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/char.png differ diff --git a/src/main/resources/assets/minecraft/mob/chicken.png b/src/main/resources/assets/minecraft/mob/chicken.png new file mode 100644 index 00000000..d4812939 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/chicken.png differ diff --git a/src/main/resources/assets/minecraft/mob/cow.png b/src/main/resources/assets/minecraft/mob/cow.png new file mode 100644 index 00000000..2080ebcb Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/cow.png differ diff --git a/src/main/resources/assets/minecraft/mob/creeper.png b/src/main/resources/assets/minecraft/mob/creeper.png new file mode 100644 index 00000000..e0a5e0a1 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/creeper.png differ diff --git a/src/main/resources/assets/minecraft/mob/ghast.png b/src/main/resources/assets/minecraft/mob/ghast.png new file mode 100644 index 00000000..e83a60da Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/ghast.png differ diff --git a/src/main/resources/assets/minecraft/mob/ghast_fire.png b/src/main/resources/assets/minecraft/mob/ghast_fire.png new file mode 100644 index 00000000..fff9718c Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/ghast_fire.png differ diff --git a/src/main/resources/assets/minecraft/mob/pig.png b/src/main/resources/assets/minecraft/mob/pig.png new file mode 100644 index 00000000..5c1efc2d Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/pig.png differ diff --git a/src/main/resources/assets/minecraft/mob/pigman.png b/src/main/resources/assets/minecraft/mob/pigman.png new file mode 100644 index 00000000..c900b362 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/pigman.png differ diff --git a/src/main/resources/assets/minecraft/mob/pigzombie.png b/src/main/resources/assets/minecraft/mob/pigzombie.png new file mode 100644 index 00000000..0a0a25a4 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/pigzombie.png differ diff --git a/src/main/resources/assets/minecraft/mob/saddle.png b/src/main/resources/assets/minecraft/mob/saddle.png new file mode 100644 index 00000000..aaea7a6d Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/saddle.png differ diff --git a/src/main/resources/assets/minecraft/mob/sheep.png b/src/main/resources/assets/minecraft/mob/sheep.png new file mode 100644 index 00000000..98cfa9ac Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/sheep.png differ diff --git a/src/main/resources/assets/minecraft/mob/sheep_fur.png b/src/main/resources/assets/minecraft/mob/sheep_fur.png new file mode 100644 index 00000000..f1291a5f Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/sheep_fur.png differ diff --git a/src/main/resources/assets/minecraft/mob/skeleton.png b/src/main/resources/assets/minecraft/mob/skeleton.png new file mode 100644 index 00000000..9d223394 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/skeleton.png differ diff --git a/src/main/resources/assets/minecraft/mob/slime.png b/src/main/resources/assets/minecraft/mob/slime.png new file mode 100644 index 00000000..42fc8736 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/slime.png differ diff --git a/src/main/resources/assets/minecraft/mob/spider.png b/src/main/resources/assets/minecraft/mob/spider.png new file mode 100644 index 00000000..08344a83 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/spider.png differ diff --git a/src/main/resources/assets/minecraft/mob/spider_eyes.png b/src/main/resources/assets/minecraft/mob/spider_eyes.png new file mode 100644 index 00000000..2a7734f9 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/spider_eyes.png differ diff --git a/src/main/resources/assets/minecraft/mob/zombie.png b/src/main/resources/assets/minecraft/mob/zombie.png new file mode 100644 index 00000000..0ab70895 Binary files /dev/null and b/src/main/resources/assets/minecraft/mob/zombie.png differ diff --git a/src/main/resources/assets/minecraft/pack.png b/src/main/resources/assets/minecraft/pack.png new file mode 100644 index 00000000..973a7cf2 Binary files /dev/null and b/src/main/resources/assets/minecraft/pack.png differ diff --git a/src/main/resources/assets/minecraft/pack.txt b/src/main/resources/assets/minecraft/pack.txt new file mode 100644 index 00000000..c14bb3b4 --- /dev/null +++ b/src/main/resources/assets/minecraft/pack.txt @@ -0,0 +1,2 @@ +The default look of Minecraft + diff --git a/src/main/resources/assets/minecraft/particles.png b/src/main/resources/assets/minecraft/particles.png new file mode 100644 index 00000000..892ca98f Binary files /dev/null and b/src/main/resources/assets/minecraft/particles.png differ diff --git a/src/main/resources/assets/minecraft/terrain.png b/src/main/resources/assets/minecraft/terrain.png new file mode 100644 index 00000000..8a102d05 Binary files /dev/null and b/src/main/resources/assets/minecraft/terrain.png differ diff --git a/src/main/resources/assets/minecraft/terrain/moon.png b/src/main/resources/assets/minecraft/terrain/moon.png new file mode 100644 index 00000000..61cebbc7 Binary files /dev/null and b/src/main/resources/assets/minecraft/terrain/moon.png differ diff --git a/src/main/resources/assets/minecraft/terrain/sun.png b/src/main/resources/assets/minecraft/terrain/sun.png new file mode 100644 index 00000000..d3433441 Binary files /dev/null and b/src/main/resources/assets/minecraft/terrain/sun.png differ diff --git a/src/main/resources/assets/test/gui/test_gui.json b/src/main/resources/assets/test/gui/test_gui.json new file mode 100644 index 00000000..4eb7c3f9 --- /dev/null +++ b/src/main/resources/assets/test/gui/test_gui.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/src/main/resources/assets/test/splash.jpg b/src/main/resources/assets/test/splash.jpg new file mode 100644 index 00000000..3e60db7d Binary files /dev/null and b/src/main/resources/assets/test/splash.jpg differ diff --git a/src/test/java/xueli/awacoder/AboutQTDialog.java b/src/test/java/xueli/awacoder/AboutQTDialog.java new file mode 100644 index 00000000..325af16c --- /dev/null +++ b/src/test/java/xueli/awacoder/AboutQTDialog.java @@ -0,0 +1,57 @@ +package xueli.awacoder; + +import module java.desktop; + +public class AboutQTDialog extends JDialog { + + public static final AboutQTDialog INSTANCE = new AboutQTDialog(); + + private static final long serialVersionUID = 6844474224353883237L; + + /** + * Create the dialog. + */ + public AboutQTDialog() { +// setType(Type.POPUP); + setTitle("About Qt"); + setResizable(false); + setPreferredSize(new Dimension(600, 546)); + setLocation(100, 100); + getContentPane().setLayout(new BorderLayout(0, 0)); + + JPanel panelBottom = new JPanel(); + panelBottom.setBackground(UIManager.getColor("Button.light")); + getContentPane().add(panelBottom, BorderLayout.SOUTH); + panelBottom.setLayout(new FlowLayout(FlowLayout.RIGHT, 5, 5)); + + JButton btnOK = new JButton("OK"); + btnOK.addActionListener(e -> this.dispose()); + btnOK.setAlignmentX(Component.RIGHT_ALIGNMENT); + panelBottom.add(btnOK); + + JPanel panelLeft = new JPanel(); + FlowLayout flowLayout_1 = (FlowLayout) panelLeft.getLayout(); + flowLayout_1.setVgap(10); + flowLayout_1.setHgap(10); + getContentPane().add(panelLeft, BorderLayout.WEST); + + JLabel lblIcon = new JLabel(""); + lblIcon.setIcon(new ImageIcon(AboutQTDialog.class.getResource("/assets/awacoder/about.png"))); + panelLeft.add(lblIcon); + + JPanel panelCenter = new JPanel(); + panelCenter.setBorder(new EmptyBorder(10, 10, 10, 10)); + getContentPane().add(panelCenter, BorderLayout.CENTER); + panelCenter.setLayout(new BorderLayout(0, 0)); + + JEditorPane lblAboutContent = new JEditorPane( + "text/html","\r\nAbout Qt\r\n

\r\n

This program uses Qt version 6.4.0.

\r\n

\r\n

Qt is a C++ toolkit for cross-platform application development.

\r\n

\r\n

Qt provides single-source portability across all major desktop operating systems. I is also available for embedded Linux and other embedded and mobile operating systems.

\r\n

\r\n

Qt is available under multiple licensing options designed to accommodate the needs of our various users.

\r\n

\r\n

Qt licensed under our commercial license agreement is appropriate for development of proprietary/commercial software where you do not want to share any source code with third parties or otherwise cannot comply with the terms of GNU (L)GPL.

\r\n

\r\n

Qt licensed under GNU (L)GPL is appropriate for the development of Qt applications provided you can comply with the terms and conditions of the respective licenses.

\r\n

\r\n

Please see qt.io/licensing for an overview of Qt licensing.

\r\n

\r\n

Copyright (C) 2022 The Qt Company Ltd and other contributors.

\r\n

\r\n

Qt and the Qt logo are trademarks of the Qt Company Ltd.

\r\n

\r\n

Qt is The Qt Company Ltd product developed as an open source project. See qt.io for more information.

\r\n"); +// lblAboutContent.setVerticalAlignment(SwingConstants.TOP); +// lblAboutContent.setHorizontalAlignment(SwingConstants.LEFT); + panelCenter.add(lblAboutContent); + + this.pack(); + + } + +} diff --git a/src/test/java/xueli/awacoder/AwaCoderSwing.java b/src/test/java/xueli/awacoder/AwaCoderSwing.java new file mode 100644 index 00000000..206d73c7 --- /dev/null +++ b/src/test/java/xueli/awacoder/AwaCoderSwing.java @@ -0,0 +1,108 @@ +package xueli.awacoder; + +import java.awt.EventQueue; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import com.formdev.flatlaf.FlatDarculaLaf; + +public class AwaCoderSwing { + + private JFrame frmAwacoder; + + /** + * Create the application. + */ + public AwaCoderSwing() { + initialize(); + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + frmAwacoder = new JFrame(); + frmAwacoder.setTitle("AwaCoder"); + frmAwacoder.setBounds(100, 100, 584, 493); + frmAwacoder.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JMenuBar menuBar = new JMenuBar(); + frmAwacoder.setJMenuBar(menuBar); + + JMenu mnFile = new JMenu("File"); + menuBar.add(mnFile); + + JMenuItem mntmFileNew = new JMenuItem("New"); + mntmFileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); + mnFile.add(mntmFileNew); + + JMenuItem mntmFileOpen = new JMenuItem("Open"); + mntmFileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); + mnFile.add(mntmFileOpen); + + JMenuItem mntmFileSave = new JMenuItem("Save"); + mntmFileSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + mnFile.add(mntmFileSave); + + JMenuItem mntmFileClose = new JMenuItem("Close"); + mntmFileClose.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK)); + mnFile.add(mntmFileClose); + + JMenuItem mntmFileQuit = new JMenuItem("Quit"); + mnFile.add(mntmFileQuit); + + JMenu mnEdit = new JMenu("Edit"); + menuBar.add(mnEdit); + + JMenu mnTool = new JMenu("Tool"); + menuBar.add(mnTool); + + JMenuItem mntmToolOptions = new JMenuItem("Options"); + mnTool.add(mntmToolOptions); + + JMenuItem mntmToolWelcome = new JMenuItem("Welcome"); + mnTool.add(mntmToolWelcome); + + JMenuItem mntmToolPlugins = new JMenuItem("Plugins"); + mnTool.add(mntmToolPlugins); + + JMenu mnHelp = new JMenu("Help"); + menuBar.add(mnHelp); + + JMenuItem mntmHelpAboutQT = new JMenuItem("About QT"); + mntmHelpAboutQT.addActionListener(e -> AboutQTDialog.INSTANCE.setVisible(true)); + mnHelp.add(mntmHelpAboutQT); + + } + + /** + * Launch the application. + */ + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(new FlatDarculaLaf()); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + AwaCoderSwing window = new AwaCoderSwing(); + window.frmAwacoder.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + +} diff --git a/src/test/java/xueli/awacoder/bean/AwaCoderBean.java b/src/test/java/xueli/awacoder/bean/AwaCoderBean.java new file mode 100644 index 00000000..b7f53b66 --- /dev/null +++ b/src/test/java/xueli/awacoder/bean/AwaCoderBean.java @@ -0,0 +1,18 @@ +package xueli.awacoder.bean; + +import java.beans.PropertyChangeListener; + +import javax.swing.event.SwingPropertyChangeSupport; + +public class AwaCoderBean { + + private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); + + public AwaCoderBean() { + } + + public void addPropertyChangeListener(String name, PropertyChangeListener listener) { + pcs.addPropertyChangeListener(name, listener); + } + +} diff --git a/src/test/java/xueli/awacoder/services/AwaCoderServices.java b/src/test/java/xueli/awacoder/services/AwaCoderServices.java new file mode 100644 index 00000000..4c25fc80 --- /dev/null +++ b/src/test/java/xueli/awacoder/services/AwaCoderServices.java @@ -0,0 +1,5 @@ +package xueli.awacoder.services; + +public class AwaCoderServices { + +} diff --git a/src/test/java/xueli/clock/ClockFrame.java b/src/test/java/xueli/clock/ClockFrame.java new file mode 100644 index 00000000..cb227704 --- /dev/null +++ b/src/test/java/xueli/clock/ClockFrame.java @@ -0,0 +1,270 @@ +package xueli.clock; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JLayer; +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import com.formdev.flatlaf.FlatDarkLaf; + +import xueli.animation.AnimationBinding; +import xueli.animation.AnimationBindingBuilder; +import xueli.animation.AnimationManager; +import xueli.animation.Curves; +import xueli.animation.IntValueAnimationBinding; +import xueli.animation.TransitionBinding; +import xueli.animation.TransitionManager; +import xueli.clock.bean.ClockBean; +import xueli.clock.component.UserInfoPanel; +import xueli.clock.service.ClockService; +import xueli.game2.resource.provider.ClassLoaderResourceProvider; +import xueli.registry.Identifier; +import xueli.swingx.component.ImageView; +import xueli.swingx.component.SimpleLayerUI; +import xueli.swingx.layout.CoverAllLayout; +import xueli.swingx.layout.HorizontalFilledLayout; +import xueli.swingx.layout.HorizontalFilledLayout.HorizontalAlign; +import xueli.swingx.layout.HorizontalFilledLayout.VerticalAlign; +import xueli.swingx.layout.OffsetLayout; +import xueli.swingx.responsive.PropertyAccessible; +import xueli.swingx.responsive.TransitionBindings; +import xueli.swingx.responsive.ValueProvider; + +// Swing搴æ’ļæšĄé¨å‹ĢåˇąæžļīŋŊ æļ“īŋŊᐛåąŧå”Ŧ鐎äŊˇčĸąį›åސug įåŋ“įšŦ鐎äŊ¸å•˜é‘ī¸ŊäŧĄæļ“åļ†īŋŊīŋŊ LovelyZeeiamᔝī¸Ŋ垜鍑哄åŊ‚~ +public class ClockFrame { + + public static final AnimationManager M_ANIMATION_MANAGER = new AnimationManager(() -> System.currentTimeMillis()); + public static final TransitionManager M_TRANSITION_MANAGER = new TransitionManager(M_ANIMATION_MANAGER); + public static final ClassLoaderResourceProvider RESOURCE_PROVIDER = new ClassLoaderResourceProvider(); + + private JFrame frmMain; + private ClockBean bean; + + private ClockService service; + + /** + * Create the application. + */ + public ClockFrame() { + initialize(); + + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + frmMain = new JFrame(); + frmMain.setTitle("Li.Clock"); + frmMain.setBounds(100, 100, 664, 467); + frmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frmMain.getContentPane().setLayout(new BorderLayout()); + Dimension frameLastDimension = new Dimension(); // TODO: Make a memory class + + JPanel appPanel = new JPanel(); + appPanel.setDoubleBuffered(true); + appPanel.setLayout(new CoverAllLayout()); + frmMain.getContentPane().add(appPanel, BorderLayout.CENTER); + + JPanel userInfoContainer = new JPanel(); + userInfoContainer.setOpaque(false); + FlowLayout userInfoFlowLayout = new FlowLayout(); + userInfoFlowLayout.setVgap(10); + userInfoFlowLayout.setHgap(10); + userInfoFlowLayout.setAlignment(FlowLayout.RIGHT); + OffsetLayout userInfoOffsetLayout = new OffsetLayout(userInfoFlowLayout); + userInfoContainer.setLayout(userInfoOffsetLayout); + appPanel.add(userInfoContainer); + + UserInfoPanel userInfoPanel = new UserInfoPanel(); + userInfoContainer.add(userInfoPanel); + + JPanel timeDateContainer = new JPanel(); + timeDateContainer.setOpaque(false); + OffsetLayout timeDateOffsetLayout = new OffsetLayout( + new HorizontalFilledLayout(VerticalAlign.ALIGN_CENTER, HorizontalAlign.CENTER)); + timeDateContainer.setLayout(timeDateOffsetLayout); + + SimpleLayerUI timeDateLayerUI = new SimpleLayerUI(); + JLayer timeDateLayer = new JLayer<>(timeDateContainer, timeDateLayerUI); + timeDateLayer.setOpaque(false); + + appPanel.add(timeDateLayer); + + JPanel timeDatePanel = new JPanel(); + timeDatePanel.setOpaque(false); + timeDatePanel.setLayout(new BorderLayout()); + timeDateContainer.add(timeDatePanel); + + JLabel lblTime = new JLabel("LABEL FOR TIME"); + lblTime.setFont(new Font("Cascadia Mono", Font.PLAIN, 25)); + timeDatePanel.add(lblTime, BorderLayout.CENTER); + + JPanel dateContainer = new JPanel(); + dateContainer.setOpaque(false); + FlowLayout dateContainerFlowLayout = new FlowLayout(); + dateContainerFlowLayout.setVgap(0); + dateContainerFlowLayout.setHgap(0); + dateContainerFlowLayout.setAlignment(FlowLayout.RIGHT); + OffsetLayout dateContainerOffsetLayout = new OffsetLayout(dateContainerFlowLayout); + dateContainer.setLayout(dateContainerOffsetLayout); + timeDatePanel.add(dateContainer, BorderLayout.SOUTH); + + JLabel lblDate = new JLabel("LABEL FOR DATE"); + lblDate.setFont(new Font("Cascadia Mono", Font.PLAIN, 14)); + dateContainer.add(lblDate); + +// SystemInfoPanel sysInfoPanel = new SystemInfoPanel(); +// sysInfoPanel.setOpaque(false); +// appPanel.add(sysInfoPanel); + + JPanel backgroundContainer = new JPanel(); + backgroundContainer.setOpaque(false); + HorizontalFilledLayout backgroundLayout = new HorizontalFilledLayout(VerticalAlign.ALIGN_BOTTOM, + HorizontalAlign.ALIGN_RIGHT); + backgroundContainer.setLayout(backgroundLayout); + appPanel.add(backgroundContainer); + + ImageView backgroundImage; + try { + backgroundImage = new ImageView(ImageIO.read( + RESOURCE_PROVIDER.getResource(new Identifier("clock", "images/background.png")).openInputStream())); + } catch (IOException e) { + throw new RuntimeException(e); + } + backgroundImage.setPreferredSize(new Dimension(0, 0)); + backgroundContainer.add(backgroundImage); + + this.bean = new ClockBean(); + this.bean.addPropertyChangeListener(ClockBean.PROPERTY_TIME, e -> { + lblTime.setText((String) e.getNewValue()); + }); + this.bean.addPropertyChangeListener(ClockBean.PROPERTY_DATE, e -> { + lblDate.setText((String) e.getNewValue()); + }); + + this.service = new ClockService(bean); + + // TODO: JLabel Animation running lag + var backgroundImageTransitionCaller = M_TRANSITION_MANAGER + .registerNewTransition( + TransitionBindings.newBindingDimension(backgroundImage, + ValueProvider.newProviderRatioVMin(i -> (int) (i * 0.5), 1.0)), + Curves.easeOutExpo, 1000); + var timeFontSizeTransitionCaller = M_TRANSITION_MANAGER + .registerNewTransition(TransitionBindings.newBindingNumber(lblTime, + ValueProvider.vminForDouble(10.0, appPanel), PropertyAccessible.fontAccessible(lblTime))); + var dateFontSizeTransitionCaller = M_TRANSITION_MANAGER + .registerNewTransition(TransitionBindings.newBindingNumber(lblDate, + ValueProvider.vminForDouble(3.0, appPanel), PropertyAccessible.fontAccessible(lblDate))); + + var timeDatePanelTransitionValueProvider = ValueProvider.vminForDouble(100.0, null); + var timeDatePanelSizeTransitionCaller = M_TRANSITION_MANAGER.registerNewTransition(new TransitionBinding() { + private double startValue; + + public void animStart() { + double oldValue = timeDatePanelTransitionValueProvider.get(frameLastDimension); + double newValue = timeDatePanelTransitionValueProvider.get(frmMain.getSize()); + + startValue = oldValue / newValue; +// System.out.println(oldValue + ", " + newValue + ", " + startValue); + + }; + + @Override + public void animProgress(double timeProgress) { + timeDateLayerUI.setScale((float) (startValue + (1.0 - startValue) * timeProgress)); + timeDateLayer.repaint(); + } + }, Curves.easeOutExpo, 1000); + +// var realPanelTransitionCaller = M_TRANSITION_MANAGER.registerNewStateChangingTransition( +// new TransitionBinding() { +// @Override +// public void animProgress(double timeProgress) { +// +// } +// }, +// Curves.easeOutQuint, +// 500 +// ); + + AnimationBinding startAnimBinding = AnimationBindingBuilder.newBuilder() + .add(new IntValueAnimationBinding(() -> -100, () -> 0, true) { + @Override + protected void progress(int val) { + dateContainerOffsetLayout.setOffset(lblDate, val, 0); + userInfoOffsetLayout.setOffset(userInfoPanel, 0, val); + } + }, 1.0).build(); + + frmMain.addWindowListener(new WindowAdapter() { + public void windowOpened(WindowEvent e) { + System.out.println("started"); + service.start(); + M_ANIMATION_MANAGER.start(startAnimBinding, Curves.easeOutQuint, 2500); + + } + + public void windowClosing(WindowEvent e) { + System.out.println("hidden"); + service.stop(); + + } + }); + + frmMain.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + backgroundImageTransitionCaller.announceTransition(); + timeFontSizeTransitionCaller.announceTransition(); + dateFontSizeTransitionCaller.announceTransition(); + timeDatePanelSizeTransitionCaller.announceTransition(); + + frameLastDimension.setSize(frmMain.getSize()); + + } + + }); + + } + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + UIManager.setLookAndFeel(new FlatDarkLaf()); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + + try { + ClockFrame window = new ClockFrame(); + window.frmMain.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + + } + }); + } + +} diff --git a/src/test/java/xueli/clock/bean/ClockBean.java b/src/test/java/xueli/clock/bean/ClockBean.java new file mode 100644 index 00000000..a0d90a04 --- /dev/null +++ b/src/test/java/xueli/clock/bean/ClockBean.java @@ -0,0 +1,53 @@ +package xueli.clock.bean; + +import java.beans.PropertyChangeListener; + +import javax.swing.event.SwingPropertyChangeSupport; + +public class ClockBean { + + public static final String PROPERTY_TIME = "1"; + public static final String PROPERTY_DATE = "2"; + public static final String PROPERTY_SHOW_FUNATIONAL_PANEL = "3"; + + private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); + + private String timeStr, dateStr; + private boolean showFunctionalPanel = false; + + public ClockBean() { + + } + + public void setTimeString(String newTimeStr) { + this.pcs.firePropertyChange(PROPERTY_TIME, this.timeStr, newTimeStr); + this.timeStr = newTimeStr; + } + + public String getTimeString() { + return timeStr; + } + + public void setDateString(String newDateStr) { + this.pcs.firePropertyChange(PROPERTY_DATE, this.dateStr, newDateStr); + this.dateStr = newDateStr; + } + + public String getDateString() { + return dateStr; + } + + public void setShowFunctionalPanel(boolean showFunctionalPanel) { + this.pcs.firePropertyChange(PROPERTY_SHOW_FUNATIONAL_PANEL, this.showFunctionalPanel, showFunctionalPanel); + this.showFunctionalPanel = showFunctionalPanel; + } + + public boolean isShowFunctionalPanel() { + return showFunctionalPanel; + } + + public void addPropertyChangeListener(String name, PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(name, listener); + } + +} diff --git a/src/test/java/xueli/clock/bean/SystemInfoBean.java b/src/test/java/xueli/clock/bean/SystemInfoBean.java new file mode 100755 index 00000000..57908ece --- /dev/null +++ b/src/test/java/xueli/clock/bean/SystemInfoBean.java @@ -0,0 +1,106 @@ +package xueli.clock.bean; + +import java.beans.PropertyChangeListener; + +import javax.swing.event.SwingPropertyChangeSupport; + +public class SystemInfoBean { + + public static final String PROPERTY_SYSTEM_INFO = "0"; + public static final String PROPERTY_CPU_ENABLED = "3"; + public static final String PROPERTY_CPU = "4"; + public static final String PROPERTY_POWER = "5"; + public static final String PROPERTY_MEMORY = "6"; + + private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); + + private String systemInfo = ""; + + private double memoryUsedPercentage = 0.0; + private String memoryInfo = ""; + + private boolean cpuInfoEnabled = true; + private double cpuLoad = 0.0; + private double cpuTemperature = 0.0; + + private boolean isPowerCharging = true; + private double powerRemaining = 100.0; + private String powerUsageRateString = ""; + + public SystemInfoBean() { + } + + public void setSystemInfo(String systemInfo) { + pcs.firePropertyChange(PROPERTY_SYSTEM_INFO, this.systemInfo, systemInfo); + this.systemInfo = systemInfo; + } + + public String getSystemInfo() { + return systemInfo; + } + + public void setMemoryInfo(double usedPercentage, String memoryInfo) { + this.memoryInfo = memoryInfo; + this.memoryUsedPercentage = usedPercentage; + pcs.firePropertyChange(PROPERTY_MEMORY, null, null); + } + + public double getMemoryUsedPercentage() { + return memoryUsedPercentage; + } + + public String getMemoryInfo() { + return memoryInfo; + } + + public void setCpuInfoEnabled(boolean cpuInfoEnabled) { + pcs.firePropertyChange(PROPERTY_CPU_ENABLED, this.cpuInfoEnabled, cpuInfoEnabled); + this.cpuInfoEnabled = cpuInfoEnabled; + } + + public boolean isCpuInfoEnabled() { + return cpuInfoEnabled; + } + + public void setCpuInfo(double cpuLoad, double cpuTemperature) { + this.cpuLoad = cpuLoad; + this.cpuTemperature = cpuTemperature; + pcs.firePropertyChange(PROPERTY_CPU, null, null); + } + + public double getCpuLoad() { + return cpuLoad; + } + + public double getCpuTemperature() { + return cpuTemperature; + } + + public void setPowerCharging(boolean isCharging, double remain, String usageRateStr) { + this.isPowerCharging = isCharging; + this.powerRemaining = remain; + this.powerUsageRateString = usageRateStr; + pcs.firePropertyChange(PROPERTY_POWER, null, null); + } + + public boolean isPowerCharging() { + return isPowerCharging; + } + + public double getPowerRemaining() { + return powerRemaining; + } + + public String getPowerUsageRateString() { + return powerUsageRateString; + } + + public void addPropertyChangeListener(String name, PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(name, listener); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(listener); + } + +} diff --git a/src/test/java/xueli/clock/bean/SystemInfoTest.java b/src/test/java/xueli/clock/bean/SystemInfoTest.java new file mode 100755 index 00000000..ad6748c0 --- /dev/null +++ b/src/test/java/xueli/clock/bean/SystemInfoTest.java @@ -0,0 +1,370 @@ +package xueli.clock.bean; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +class SystemInfoTest { + + static List oshi = new ArrayList<>(); + + public static void main(String[] args) { +// SystemInfo si = new SystemInfo(); +// +// HardwareAbstractionLayer hal = si.getHardware(); +// OperatingSystem os = si.getOperatingSystem(); + +// System.out.println(os.getManufacturer()); +// System.out.println(os.getFamily()); + +// OSVersionInfo osVersion = os.getVersionInfo(); +// System.out.println(osVersion.getVersion()); +// System.out.println(osVersion.getCodeName()); +// System.out.println(osVersion.getBuildNumber()); + +// printOperatingSystem(os); +// printComputerSystem(hal.getComputerSystem()); +// printProcessor(hal.getProcessor()); +// printMemory(hal.getMemory()); +// printCpu(hal.getProcessor()); +// printProcesses(os, hal.getMemory()); +// printServices(os); +// printSensors(hal.getSensors()); +// printPowerSources(hal.getPowerSources()); +// printDisks(hal.getDiskStores()); +// printLVgroups(hal.getLogicalVolumeGroups()); +// printFileSystem(os.getFileSystem()); +// printNetworkInterfaces(hal.getNetworkIFs()); +// printNetworkParameters(os.getNetworkParams()); +// printInternetProtocolStats(os.getInternetProtocolStats()); +// printDisplays(hal.getDisplays()); +// printUsbDevices(hal.getUsbDevices(true)); +// printSoundCards(hal.getSoundCards()); +// printGraphicsCards(hal.getGraphicsCards()); +// +// StringBuilder output = new StringBuilder(); +// for (String line : oshi) { +// output.append(line); +// if (line != null && !line.endsWith("\n")) { +// output.append('\n'); +// } +// } +// System.out.println(output); + + } + +// private static void printOperatingSystem(final OperatingSystem os) { +// oshi.add(String.valueOf(os)); +// oshi.add("Booted: " + Instant.ofEpochSecond(os.getSystemBootTime())); +// oshi.add("Uptime: " + FormatUtil.formatElapsedSecs(os.getSystemUptime())); +// oshi.add("Running with" + (os.isElevated() ? "" : "out") + " elevated permissions."); +// oshi.add("Sessions:"); +// for (OSSession s : os.getSessions()) { +// oshi.add(" " + s.toString()); +// } +// } +// +// private static void printComputerSystem(final ComputerSystem computerSystem) { +// oshi.add("System: " + computerSystem.toString()); +// oshi.add(" Firmware: " + computerSystem.getFirmware().toString()); +// oshi.add(" Baseboard: " + computerSystem.getBaseboard().toString()); +// } +// +// private static void printProcessor(CentralProcessor processor) { +// oshi.add(processor.toString()); +// +// Map efficiencyCount = new HashMap<>(); +// int maxEfficiency = 0; +// for (PhysicalProcessor cpu : processor.getPhysicalProcessors()) { +// int eff = cpu.getEfficiency(); +// efficiencyCount.merge(eff, 1, Integer::sum); +// if (eff > maxEfficiency) { +// maxEfficiency = eff; +// } +// } +// oshi.add(" Topology:"); +// oshi.add(String.format(" %7s %4s %4s %4s %4s %4s", "LogProc", "P/E", "Proc", "Pkg", "NUMA", "PGrp")); +// for (PhysicalProcessor cpu : processor.getPhysicalProcessors()) { +// oshi.add(String.format(" %7s %4s %4d %4s %4d %4d", +// processor.getLogicalProcessors().stream() +// .filter(p -> p.getPhysicalProcessorNumber() == cpu.getPhysicalProcessorNumber()) +// .filter(p -> p.getPhysicalPackageNumber() == cpu.getPhysicalPackageNumber()) +// .map(p -> Integer.toString(p.getProcessorNumber())).collect(Collectors.joining(",")), +// cpu.getEfficiency() == maxEfficiency ? "P" : "E", cpu.getPhysicalProcessorNumber(), +// cpu.getPhysicalPackageNumber(), +// processor.getLogicalProcessors().stream() +// .filter(p -> p.getPhysicalProcessorNumber() == cpu.getPhysicalProcessorNumber()) +// .filter(p -> p.getPhysicalPackageNumber() == cpu.getPhysicalPackageNumber()) +// .mapToInt(p -> p.getNumaNode()).findFirst().orElse(0), +// processor.getLogicalProcessors().stream() +// .filter(p -> p.getPhysicalProcessorNumber() == cpu.getPhysicalProcessorNumber()) +// .filter(p -> p.getPhysicalPackageNumber() == cpu.getPhysicalPackageNumber()) +// .mapToInt(p -> p.getProcessorGroup()).findFirst().orElse(0))); +// } +// List caches = processor.getProcessorCaches(); +// if (!caches.isEmpty()) { +// oshi.add(" Caches:"); +// } +// for (int i = 0; i < caches.size(); i++) { +// ProcessorCache cache = caches.get(i); +// boolean perCore = cache.getLevel() < 3; +// boolean pCore = perCore && i < caches.size() - 1 && cache.getLevel() == caches.get(i + 1).getLevel() +// && cache.getType() == caches.get(i + 1).getType(); +// boolean eCore = perCore && i > 0 && cache.getLevel() == caches.get(i - 1).getLevel() +// && cache.getType() == caches.get(i - 1).getType(); +// StringBuilder sb = new StringBuilder(" ").append(cache); +// if (perCore) { +// sb.append(" (per "); +// if (pCore) { +// sb.append("P-"); +// } else if (eCore) { +// sb.append("E-"); +// } +// sb.append("core)"); +// } +// oshi.add(sb.toString()); +// } +// } +// +// private static void printMemory(GlobalMemory memory) { +// oshi.add("Physical Memory: \n " + memory.toString()); +// VirtualMemory vm = memory.getVirtualMemory(); +// oshi.add("Virtual Memory: \n " + vm.toString()); +// List pmList = memory.getPhysicalMemory(); +// if (!pmList.isEmpty()) { +// oshi.add("Physical Memory: "); +// for (PhysicalMemory pm : pmList) { +// oshi.add(" " + pm.toString()); +// } +// } +// } +// +// private static void printCpu(CentralProcessor processor) { +// oshi.add("Context Switches/Interrupts: " + processor.getContextSwitches() + " / " + processor.getInterrupts()); +// +// long[] prevTicks = processor.getSystemCpuLoadTicks(); +// long[][] prevProcTicks = processor.getProcessorCpuLoadTicks(); +// oshi.add("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks)); +// // Wait a second... +// Util.sleep(1000); +// long[] ticks = processor.getSystemCpuLoadTicks(); +// oshi.add("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks)); +// long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; +// long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; +// long sys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; +// long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; +// long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; +// long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; +// long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; +// long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; +// long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; +// +// oshi.add(String.format( +// "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%", +// 100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu, +// 100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu)); +// oshi.add(String.format("CPU load: %.1f%%", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100)); +// double[] loadAverage = processor.getSystemLoadAverage(3); +// oshi.add("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0])) +// + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1])) +// + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2]))); +// // per core CPU +// StringBuilder procCpu = new StringBuilder("CPU load per processor:"); +// double[] load = processor.getProcessorCpuLoadBetweenTicks(prevProcTicks); +// for (double avg : load) { +// procCpu.append(String.format(" %.1f%%", avg * 100)); +// } +// oshi.add(procCpu.toString()); +// long freq = processor.getProcessorIdentifier().getVendorFreq(); +// if (freq > 0) { +// oshi.add("Vendor Frequency: " + FormatUtil.formatHertz(freq)); +// } +// freq = processor.getMaxFreq(); +// if (freq > 0) { +// oshi.add("Max Frequency: " + FormatUtil.formatHertz(freq)); +// } +// long[] freqs = processor.getCurrentFreq(); +// if (freqs[0] > 0) { +// StringBuilder sb = new StringBuilder("Current Frequencies: "); +// for (int i = 0; i < freqs.length; i++) { +// if (i > 0) { +// sb.append(", "); +// } +// sb.append(FormatUtil.formatHertz(freqs[i])); +// } +// oshi.add(sb.toString()); +// } +// } +// +// private static void printProcesses(OperatingSystem os, GlobalMemory memory) { +// OSProcess myProc = os.getProcess(os.getProcessId()); +// // current process will never be null. Other code should check for null here +// oshi.add( +// "My PID: " + myProc.getProcessID() + " with affinity " + Long.toBinaryString(myProc.getAffinityMask())); +// oshi.add("My TID: " + os.getThreadId() + " with details " + os.getCurrentThread()); +// +// oshi.add("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount()); +// // Sort by highest CPU +// List procs = os.getProcesses(ProcessFiltering.ALL_PROCESSES, ProcessSorting.CPU_DESC, 5); +// oshi.add(" PID %CPU %MEM VSZ RSS Name"); +// for (int i = 0; i < procs.size(); i++) { +// OSProcess p = procs.get(i); +// oshi.add(String.format(" %5d %5.1f %4.1f %9s %9s %s", p.getProcessID(), +// 100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(), +// 100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()), +// FormatUtil.formatBytes(p.getResidentSetSize()), p.getName())); +// } +// OSProcess p = os.getProcess(os.getProcessId()); +// oshi.add("Current process arguments: "); +// for (String s : p.getArguments()) { +// oshi.add(" " + s); +// } +// oshi.add("Current process environment: "); +// for (Entry e : p.getEnvironmentVariables().entrySet()) { +// oshi.add(" " + e.getKey() + "=" + e.getValue()); +// } +// } +// +// private static void printServices(OperatingSystem os) { +// oshi.add("Services: "); +// oshi.add(" PID State Name"); +// // DO 5 each of running and stopped +// int i = 0; +// for (OSService s : os.getServices()) { +// if (s.getState().equals(OSService.State.RUNNING) && i++ < 5) { +// oshi.add(String.format(" %5d %7s %s", s.getProcessID(), s.getState(), s.getName())); +// } +// } +// i = 0; +// for (OSService s : os.getServices()) { +// if (s.getState().equals(OSService.State.STOPPED) && i++ < 5) { +// oshi.add(String.format(" %5d %7s %s", s.getProcessID(), s.getState(), s.getName())); +// } +// } +// } +// +// private static void printSensors(Sensors sensors) { +// oshi.add("Sensors: " + sensors.toString()); +// } +// +// private static void printPowerSources(List list) { +// StringBuilder sb = new StringBuilder("Power Sources: "); +// if (list.isEmpty()) { +// sb.append("Unknown"); +// } +// for (PowerSource powerSource : list) { +//// sb.append("\n ").append(powerSource.hashCode()).append(" ").append(powerSource.toString()); +// sb.append(Double.valueOf(powerSource.getPowerUsageRate()).intValue()); +// } +// oshi.add(sb.toString()); +// } +// +// private static void printDisks(List list) { +// oshi.add("Disks:"); +// for (HWDiskStore disk : list) { +// oshi.add(" " + disk.toString()); +// +// List partitions = disk.getPartitions(); +// for (HWPartition part : partitions) { +// oshi.add(" |-- " + part.toString()); +// } +// } +// +// } +// +// private static void printLVgroups(List list) { +// if (!list.isEmpty()) { +// oshi.add("Logical Volume Groups:"); +// for (LogicalVolumeGroup lvg : list) { +// oshi.add(" " + lvg.toString()); +// } +// } +// } +// +// private static void printFileSystem(FileSystem fileSystem) { +// oshi.add("File System:"); +// +// oshi.add(String.format(" File Descriptors: %d/%d", fileSystem.getOpenFileDescriptors(), +// fileSystem.getMaxFileDescriptors())); +// +// for (OSFileStore fs : fileSystem.getFileStores()) { +// long usable = fs.getUsableSpace(); +// long total = fs.getTotalSpace(); +// oshi.add(String.format( +// " %s (%s) [%s] %s of %s free (%.1f%%), %s of %s files free (%.1f%%) is %s " +// + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s") +// + " and is mounted at %s", +// fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(), +// FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total, +// FormatUtil.formatValue(fs.getFreeInodes(), ""), FormatUtil.formatValue(fs.getTotalInodes(), ""), +// 100d * fs.getFreeInodes() / fs.getTotalInodes(), fs.getVolume(), fs.getLogicalVolume(), +// fs.getMount())); +// } +// } +// +// private static void printNetworkInterfaces(List list) { +// StringBuilder sb = new StringBuilder("Network Interfaces:"); +// if (list.isEmpty()) { +// sb.append(" Unknown"); +// } else { +// for (NetworkIF net : list) { +// sb.append("\n ").append(net.toString()); +// } +// } +// oshi.add(sb.toString()); +// } +// +// private static void printNetworkParameters(NetworkParams networkParams) { +// oshi.add("Network parameters:\n " + networkParams.toString()); +// } +// +// private static void printInternetProtocolStats(InternetProtocolStats ip) { +// oshi.add("Internet Protocol statistics:"); +// oshi.add(" TCPv4: " + ip.getTCPv4Stats()); +// oshi.add(" TCPv6: " + ip.getTCPv6Stats()); +// oshi.add(" UDPv4: " + ip.getUDPv4Stats()); +// oshi.add(" UDPv6: " + ip.getUDPv6Stats()); +// } +// +// private static void printDisplays(List list) { +// oshi.add("Displays:"); +// int i = 0; +// for (Display display : list) { +// oshi.add(" Display " + i + ":"); +// oshi.add(String.valueOf(display)); +// i++; +// } +// } +// +// private static void printUsbDevices(List list) { +// oshi.add("USB Devices:"); +// for (UsbDevice usbDevice : list) { +// oshi.add(String.valueOf(usbDevice)); +// } +// } +// +// private static void printSoundCards(List list) { +// oshi.add("Sound Cards:"); +// for (SoundCard card : list) { +// oshi.add(" " + String.valueOf(card)); +// } +// } +// +// private static void printGraphicsCards(List list) { +// oshi.add("Graphics Cards:"); +// if (list.isEmpty()) { +// oshi.add(" None detected."); +// } else { +// for (GraphicsCard card : list) { +// oshi.add(" " + String.valueOf(card)); +// } +// } +// } + +} diff --git a/src/test/java/xueli/clock/component/BatteryInfoPanel.java b/src/test/java/xueli/clock/component/BatteryInfoPanel.java new file mode 100755 index 00000000..29a72239 --- /dev/null +++ b/src/test/java/xueli/clock/component/BatteryInfoPanel.java @@ -0,0 +1,199 @@ +package xueli.clock.component; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeListener; + +import javax.swing.JPanel; +import javax.swing.event.SwingPropertyChangeSupport; + +public class BatteryInfoPanel extends JPanel { + + private static final long serialVersionUID = -438587967007006520L; + + private BatteryInfoBean bean = new BatteryInfoBean(); + + /** + * Create the panel. + */ + public BatteryInfoPanel() { + setLayout(new BorderLayout(0, 0)); + + BatteryIconPanel iconPanel = new BatteryIconPanel(bean); + iconPanel.setFont(new Font("Cascadia Code", Font.PLAIN, 12)); + add(iconPanel, BorderLayout.CENTER); + + } + + public void setBean(boolean isCharging, double remainPercentage) { + this.bean.setPowerCharging(isCharging); + this.bean.setPowerRemain(remainPercentage); + this.revalidate(); + this.repaint(); + + } + + public void setColor(Color color) { + this.bean.setColor(color); + this.revalidate(); + this.repaint(); + + } + +} + +class BatteryInfoBean { + + private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); + + private boolean isPowerCharging = true; + private double powerRemain = 100.0; + + private Color color = new Color(0.6f, 0.6f, 0.6f); + + public void setPowerCharging(boolean isPowerCharging) { + pcs.firePropertyChange("charge", this.isPowerCharging, isPowerCharging); + this.isPowerCharging = isPowerCharging; + } + + public boolean isPowerCharging() { + return isPowerCharging; + } + + public void setPowerRemain(double powerRemain) { + pcs.firePropertyChange("remain", this.powerRemain, powerRemain); + this.powerRemain = powerRemain; + } + + public double getPowerRemain() { + return powerRemain; + } + + public void setColor(Color color) { + pcs.firePropertyChange("color", this.color, color); + this.color = color; + } + + public Color getColor() { + return color; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.pcs.addPropertyChangeListener(listener); + } + +} + +class BatteryIconPanel extends Component { + + private static final long serialVersionUID = 5139235281076605426L; +// private static final double magicNumber = Math.log(2); + + private final BatteryInfoBean bean; + + public BatteryIconPanel(BatteryInfoBean bean) { + this.bean = bean; + + } + + @Override + public void paint(Graphics g) { + int width = this.getWidth(); + int height = this.getHeight(); + + int min = Math.min(width, height); + int borderSize = (int) Math.log(min); + int borderSpace = (int) (borderSize); + + int fontSize = (int) (0.6 * min); + String contentString = String.format("%d%%", (int) bean.getPowerRemain()); + if (bean.isPowerCharging()) { + contentString = contentString + " ⚡"; + } + + Font font = getFont().deriveFont((float) fontSize); + g.setFont(font); + FontMetrics metrics = g.getFontMetrics(font); + Rectangle2D strBounds = metrics.getStringBounds(contentString, g); + int fontHeight = (int) strBounds.getHeight(); + int fontWidth = (int) strBounds.getWidth(); + int baseLineYOffset = metrics.getAscent(); + +// System.out.println(fontSize + ", " + fontHeight); + + g.setColor(bean.getColor()); + if (width > height) { + if (borderSize > 0) { + int insideBarStart = borderSpace + borderSize; + int insideBarHeight = height - insideBarStart * 2; + int insideBarWidth = (int) ((width - borderSize - insideBarStart * 2) * bean.getPowerRemain() / 100.0); + + int temp, temp2; + g.fillRect(0, 0, temp = width - 2 * borderSize, borderSize); + g.fillRect(temp, 0, borderSize, temp2 = height - borderSize); + g.fillRect(borderSize, temp2, temp, borderSize); + g.fillRect(0, borderSize, borderSize, temp2); + + int rightLittleThingHeight = (int) Math.ceil(height * 0.314); + int rightLittleThingSpace; + do { + rightLittleThingHeight--; + rightLittleThingSpace = (height - rightLittleThingHeight); + } while (rightLittleThingSpace % 2 != 0); + int rightLittleThingYStart = rightLittleThingSpace / 2; + g.fillRect(width - borderSize, rightLittleThingYStart, borderSize, rightLittleThingHeight); + + g.fillRect(insideBarStart, insideBarStart, insideBarWidth, insideBarHeight); + + g.setColor(bean.getColor().brighter()); + int fontStartX = (width - borderSize - fontWidth) / 2; + int fontStartY = (height - fontHeight) / 2 + baseLineYOffset; + g.drawString(contentString, fontStartX, fontStartY); + + } else { + int insideBarWidth = (int) (width * bean.getPowerRemain() / 100.0); + g.fillRect(0, 0, insideBarWidth, height); + } + } else { + if (borderSize > 0) { + int insideBarStart = borderSpace + borderSize; + int insideBarWidth = width - insideBarStart * 2; + int insideBarHeight = (int) ((height - borderSize - insideBarStart * 2) * bean.getPowerRemain() + / 100.0); + + int temp, temp2; + g.fillRect(0, borderSize, temp = width - borderSize, borderSize); + g.fillRect(temp, borderSize, borderSize, temp2 = height - borderSize * 2); + g.fillRect(borderSize, height - borderSize, temp, borderSize); + g.fillRect(0, borderSize * 2, borderSize, temp2); + + int topLittleThingWidth = (int) Math.ceil(width * 0.314); + int rightLittleThingSpace; + do { + topLittleThingWidth--; + rightLittleThingSpace = (width - topLittleThingWidth); + } while (rightLittleThingSpace % 2 != 0); + int rightLittleThingXStart = rightLittleThingSpace / 2; + g.fillRect(rightLittleThingXStart, 0, topLittleThingWidth, borderSize); + + g.fillRect(insideBarStart, insideBarStart + borderSize, insideBarWidth, insideBarHeight); + + g.setColor(bean.getColor().brighter()); + int fontStartX = (width - fontWidth) / 2; + int fontStartY = (height + borderSize - fontHeight) / 2 + baseLineYOffset; + g.drawString(contentString, fontStartX, fontStartY); + + } else { + int insideBarHeight = (int) (height * bean.getPowerRemain() / 100.0); + g.fillRect(0, 0, width, insideBarHeight); + } + } + + } + +} diff --git a/src/test/java/xueli/clock/component/SystemInfoPanel.java b/src/test/java/xueli/clock/component/SystemInfoPanel.java new file mode 100755 index 00000000..8ecb1e57 --- /dev/null +++ b/src/test/java/xueli/clock/component/SystemInfoPanel.java @@ -0,0 +1,119 @@ +package xueli.clock.component; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +import xueli.clock.bean.SystemInfoBean; +import xueli.clock.service.SystemInfoService; +import xueli.swingx.layout.VerticalFilledLayout; +import xueli.swingx.layout.VerticalFilledLayout.HorizontalAlign; +import xueli.swingx.layout.VerticalFilledLayout.VerticalAlign; + +public class SystemInfoPanel extends JPanel { + + private static final long serialVersionUID = 1297687095446027141L; + + private final SystemInfoBean bean = new SystemInfoBean(); + + private final SystemInfoService service; + + /** + * Create the panel. + */ + public SystemInfoPanel() { + setLayout(new BorderLayout(0, 0)); + + JPanel topPanel = new JPanel(); + topPanel.setOpaque(false); + FlowLayout flowLayout = (FlowLayout) topPanel.getLayout(); + flowLayout.setAlignment(FlowLayout.LEFT); + flowLayout.setVgap(0); + flowLayout.setHgap(0); + add(topPanel, BorderLayout.NORTH); + + JPanel topLeftPanel = new JPanel(); + topPanel.add(topLeftPanel); + topLeftPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 3, 3)); + + JPanel powerInfoPanel = new JPanel(); + powerInfoPanel.setBorder(new EmptyBorder(0, 2, 0, 2)); + FlowLayout flowLayout_1 = (FlowLayout) powerInfoPanel.getLayout(); + flowLayout_1.setVgap(0); + flowLayout_1.setHgap(0); + topLeftPanel.add(powerInfoPanel); + + BatteryInfoPanel powerIndicatorPanel = new BatteryInfoPanel(); + powerInfoPanel.add(powerIndicatorPanel); + powerIndicatorPanel.setPreferredSize(new Dimension(45, 18)); + + JLabel powerUsageRateLabel = new JLabel(""); + powerUsageRateLabel.setBorder(new EmptyBorder(0, 3, 0, 0)); + powerInfoPanel.add(powerUsageRateLabel); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setOpaque(false); + FlowLayout flowLayout_2 = (FlowLayout) bottomPanel.getLayout(); + flowLayout_2.setAlignment(FlowLayout.LEFT); + flowLayout_2.setVgap(0); + flowLayout_2.setHgap(0); + add(bottomPanel, BorderLayout.SOUTH); + + JPanel bottomLeftPanel = new JPanel(); + bottomLeftPanel.setLayout(new VerticalFilledLayout(HorizontalAlign.FILL, VerticalAlign.CENTER)); + bottomPanel.add(bottomLeftPanel); + + JLabel systemInfoLabel = new JLabel(""); + systemInfoLabel.setFont(systemInfoLabel.getFont().deriveFont(systemInfoLabel.getFont().getSize() - 2f)); + bottomLeftPanel.add(systemInfoLabel); + + JLabel cpuInfoLabel = new JLabel(""); + bottomLeftPanel.add(cpuInfoLabel); + + JLabel memInfoLabel = new JLabel(""); + bottomLeftPanel.add(memInfoLabel); + + bean.addPropertyChangeListener(SystemInfoBean.PROPERTY_POWER, e -> { + powerIndicatorPanel.setBean(bean.isPowerCharging(), bean.getPowerRemaining()); + powerUsageRateLabel.setText(bean.getPowerUsageRateString()); + }); + bean.addPropertyChangeListener(SystemInfoBean.PROPERTY_SYSTEM_INFO, e -> { + systemInfoLabel.setText((String) e.getNewValue()); + }); + bean.addPropertyChangeListener(SystemInfoBean.PROPERTY_CPU_ENABLED, e -> { + boolean enabled = (boolean) e.getNewValue(); + cpuInfoLabel.setVisible(enabled); + cpuInfoLabel.setEnabled(enabled); + }); + bean.addPropertyChangeListener(SystemInfoBean.PROPERTY_CPU, e -> { + cpuInfoLabel.setText(String.format("CPU: %.1f%% %.1f℃", bean.getCpuLoad(), bean.getCpuTemperature())); + }); + bean.addPropertyChangeListener(SystemInfoBean.PROPERTY_MEMORY, e -> { + memInfoLabel + .setText(String.format("Memory: %s %.1f%%", bean.getMemoryInfo(), bean.getMemoryUsedPercentage())); + }); + + service = new SystemInfoService(bean); + this.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + service.start(); + } + + @Override + public void componentHidden(ComponentEvent e) { + service.stop(); + } + }); + + service.start(); + + } + +} diff --git a/src/test/java/xueli/clock/component/UserInfoPanel.java b/src/test/java/xueli/clock/component/UserInfoPanel.java new file mode 100644 index 00000000..6e2bd90a --- /dev/null +++ b/src/test/java/xueli/clock/component/UserInfoPanel.java @@ -0,0 +1,44 @@ +package xueli.clock.component; + +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import xueli.clock.ClockFrame; +import xueli.registry.Identifier; +import xueli.swingx.component.ImageView; + +public class UserInfoPanel extends JPanel { + + private static final long serialVersionUID = -2141828020915382762L; + + /** + * Create the panel. + * + * @throws IOException + */ + public UserInfoPanel() { +// setBackground(Color.BLACK); + setOpaque(false); + + ImageView lblUserIcon = null; + try { + lblUserIcon = new ImageView(ImageIO.read(ClockFrame.RESOURCE_PROVIDER + .getResource(new Identifier("clock", "images/user_icon.jpg")).openInputStream()), true); + } catch (IOException e) { + throw new RuntimeException(e); + } + setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5)); + lblUserIcon.setPreferredSize(new Dimension(30, 30)); + add(lblUserIcon); + + JLabel label = new JLabel("LovelyZeeiam"); + add(label); + + } + +} diff --git a/src/test/java/xueli/clock/service/CentralProcessorLoadTicker.java b/src/test/java/xueli/clock/service/CentralProcessorLoadTicker.java new file mode 100755 index 00000000..e0078822 --- /dev/null +++ b/src/test/java/xueli/clock/service/CentralProcessorLoadTicker.java @@ -0,0 +1,33 @@ +package xueli.clock.service; + +public class CentralProcessorLoadTicker { + +// private final CentralProcessor cpuInfoBean; +// +// public CentralProcessorLoadTicker(CentralProcessor cpuInfo) { +// this.cpuInfoBean = cpuInfo; +// } +// +// private long[] prevTicks; +// +// public double getLoadPercentage() { +// long[] ticks = cpuInfoBean.getSystemCpuLoadTicks(); +// if (prevTicks == null) +// return 0; +// +// long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; +// long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; +// long sys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; +// long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; +// long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; +// long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; +// long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; +// long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; +// long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal; +// double result = 1 - (double) idle / totalCpu; +// +// this.prevTicks = ticks; +// return result; +// } + +} diff --git a/src/test/java/xueli/clock/service/ClockService.java b/src/test/java/xueli/clock/service/ClockService.java new file mode 100644 index 00000000..ae04669b --- /dev/null +++ b/src/test/java/xueli/clock/service/ClockService.java @@ -0,0 +1,33 @@ +package xueli.clock.service; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import xueli.clock.ClockFrame; +import xueli.clock.bean.ClockBean; +import xueli.swingx.Service; + +public class ClockService extends Service { + + private static final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd"); + + public ClockService(ClockBean bean) { + super(bean, 20); + } + + @Override + protected void updateTimer() { +// long start = System.currentTimeMillis(); + ClockFrame.M_ANIMATION_MANAGER.tick(); + + Date date = new Date(); + bean.setTimeString(TIME_FORMAT.format(date)); + bean.setDateString(DATE_FORMAT.format(date)); + +// System.out.println(System.currentTimeMillis() - start); + + } + +} diff --git a/src/test/java/xueli/clock/service/SystemInfoService.java b/src/test/java/xueli/clock/service/SystemInfoService.java new file mode 100755 index 00000000..940f2058 --- /dev/null +++ b/src/test/java/xueli/clock/service/SystemInfoService.java @@ -0,0 +1,84 @@ +package xueli.clock.service; + +import xueli.clock.bean.SystemInfoBean; +import xueli.swingx.Service; + +public class SystemInfoService extends Service { + +// private static final SystemInfo sysInfoBean = new SystemInfo(); +// private static final OperatingSystem osInfoBean = sysInfoBean.getOperatingSystem(); +// private static final HardwareAbstractionLayer harderLayerBean = sysInfoBean.getHardware(); +// +// private static final String sysInfoStr = osInfoBean.getFamily().trim() + " " +// + osInfoBean.getVersionInfo().getVersion().trim(); +// private static final boolean isWindows; +// private static final boolean elevated = osInfoBean.isElevated(); +// +// private static final GlobalMemory memoryInfoBean = harderLayerBean.getMemory(); +// private static final CentralProcessorLoadTicker cpuLoadBean = new CentralProcessorLoadTicker( +// harderLayerBean.getProcessor()); +// +// private static final Sensors sensorsBean = harderLayerBean.getSensors(); +// +// private static final PowerSource powerSourceBean; +// static { +// List powerSources = harderLayerBean.getPowerSources(); +// if (powerSources.size() > 0) { +// powerSourceBean = powerSources.get(0); +// } else { +// powerSourceBean = null; +// } +// +// String osName = System.getProperty("os.name"); +// isWindows = osName.contains("Windows"); +// +// } +// + public SystemInfoService(SystemInfoBean bean) { + super(bean, 1000); + } +// +// public void start() { +// super.start(); +// this.initBean(); +// } +// +// private void initBean() { +// bean.setSystemInfo(sysInfoStr); +// +// if (!elevated && isWindows) { +// bean.setCpuInfoEnabled(false); +// } +// +// } +// +// @Override + protected void updateTimer() { +// long memoryTotal = memoryInfoBean.getTotal(); +// long memoryUsed = memoryTotal - memoryInfoBean.getAvailable(); +// double memoryUsedPercentage = (double) memoryUsed / memoryTotal * 100.0; +// bean.setMemoryInfo(memoryUsedPercentage, +// String.format("%s / %s", FormatUtil.formatBytes(memoryUsed), FormatUtil.formatBytes(memoryTotal))); +// +// bean.setCpuInfo(cpuLoadBean.getLoadPercentage(), sensorsBean.getCpuTemperature()); +// +// if (powerSourceBean != null) { +// powerSourceBean.updateAttributes(); +// +// // The unit in Ubuntu and Windows differs! Why... +// double curCapacity = powerSourceBean.getCurrentCapacity(); +// double maxCapacity = powerSourceBean.getMaxCapacity(); +// bean.setPowerCharging(powerSourceBean.isCharging(), 100.0 * curCapacity / maxCapacity, +// formatEfficiency(powerSourceBean.getPowerUsageRate() / 1000)); +// +// } +// + } +// +// private String formatEfficiency(double mw) { +// if (mw < 1000) +// return String.format("%.1fmW", mw); +// return String.format("%.1fW", mw / 1000); +// } + +} diff --git a/src/test/java/xueli/daw/driver/ALDriverTest.java b/src/test/java/xueli/daw/driver/ALDriverTest.java new file mode 100644 index 00000000..a36f602f --- /dev/null +++ b/src/test/java/xueli/daw/driver/ALDriverTest.java @@ -0,0 +1,53 @@ +package xueli.daw.driver; + +import xueli.daw.synthesizer.SynthesizerUtils; +import xueli.utils.buffer.LotsOfByteBuffer; + +//@SuppressWarnings("unused") +public class ALDriverTest { + + // Why does JUnit Test annotation not working here? "TestEngine with ID + // 'junit-jupiter' failed to discover tests" + public static void main(String[] args) { + ALDriver driver = new ALDriver(null); + + // Create a simple sine wave + final int sampleRate = 44100; + LotsOfByteBuffer sinWaveAudioBuffer = new LotsOfByteBuffer(sampleRate); + for (int i = 0; i < sampleRate; i++) { + double src = SynthesizerUtils.triangle(440, (double) i / sampleRate); + src = Math.max(-1.0, src); + src = Math.min(1.0, src); + short sample = (short) (src * 32767.0); +// System.out.println(sample); + + sinWaveAudioBuffer.putShort(sample); + } + sinWaveAudioBuffer.setReadWrite(true); + + // Create Buffer + var buffer = driver.createBuffer(); + buffer.setBuffer(sinWaveAudioBuffer, BufferFormat.MONO16, sampleRate); // MONO8 can create big distortion? + buffer.doingSyncIfNecessary(); + + // Create Speaker + var speaker = driver.createSpeaker(); + speaker.queueBuffer(buffer); +// System.out.println(speaker.queryProcessedBufferCount()); + speaker.play(); +// System.out.println(speaker.queryProcessedBufferCount()); + + synchronized (Thread.currentThread()) { + try { + Thread.currentThread().wait(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + sinWaveAudioBuffer.release(); + driver.release(); + + } + +} diff --git a/src/test/java/xueli/daw/driver/ALSynthTest.java b/src/test/java/xueli/daw/driver/ALSynthTest.java new file mode 100644 index 00000000..d3e1a52d --- /dev/null +++ b/src/test/java/xueli/daw/driver/ALSynthTest.java @@ -0,0 +1,231 @@ +package xueli.daw.driver; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.lwjgl.openal.AL11; + +import xueli.daw.nbs.NBSInputStream; +import xueli.daw.nbs.NBSSequencer; +import xueli.daw.nbs.NoteBlock; +import xueli.daw.synthesizer.SynthesizerUtils; +import xueli.utils.buffer.BufferPool; +import xueli.utils.buffer.BufferPoolListener; +import xueli.utils.buffer.LotsOfByteBuffer; +import xueli.utils.buffer.MemoryHandler; + +// It is LovelyZeeiam's first Synthesizer and buffered streaming note player!!! +public class ALSynthTest { + + public static final int SAMPLE_RATE = 44100; + public static final double PER_SAMPLE_LENGTH = 1.0 / SAMPLE_RATE; + public static final int BUFFER_SAMPLE_COUNT = 8192; + +// public static final int LIMITER_CACHE_SAMPLE_COUNT = 2000; + + public static void main(String[] args) throws FileNotFoundException, IOException { + // Read Note File + var nbsIn = new NBSInputStream(new FileInputStream("res/music/lucky_star.nbs")); + List notes = nbsIn.readNoteBlocks(); + double tickPerSecond = nbsIn.getTempo() / 100.0; + double musicLength = nbsIn.getSongLength() / tickPerSecond; + nbsIn.close(); + + // Create Driver + var driver = new ALDriver(null); + var streamSpeaker = driver.createSpeaker(); + + // Create Memory Pool + // Each memory buffer in the pool can be mapped to a buffer in OpenAL + BufferPool pool = new BufferPool(BUFFER_SAMPLE_COUNT * 2); + HashMap bufferMap = new HashMap<>(); + pool.addListener(new BufferPoolListener() { + @Override + public void onNewBufferAllocated(LotsOfByteBuffer memory) { + var buffer = driver.createBuffer(); + bufferMap.put(memory, buffer); + + } + + @Override + public void beforeBufferReleased(LotsOfByteBuffer memory) { + var buffer = bufferMap.remove(memory); + driver.releaseBuffer(buffer); + + } + }); + pool.initialSpare(16); + + HashMap bufferHandlerMap = new HashMap<>(); + + // Synthesizer Variables +// double[] limiter_temp = new double[LIMITER_CACHE_SAMPLE_COUNT]; +// int limiter_index = 0; +// double limiter_volumeScale = 1.0; + + // Create Sequencer + record PlayingNote(double startTime, NoteBlock block) {} + ArrayList playingNotes = new ArrayList<>(); + + ArrayList sequencerDest = new ArrayList<>(); + NBSSequencer sequencer = new NBSSequencer(tickPerSecond, notes); + + // Play Notes + final int endBufferCount = (int) ((musicLength + 5) * SAMPLE_RATE / BUFFER_SAMPLE_COUNT); + int bufferedCount = 0; + double bufferTime = 0.0; + + while(true) { +// long startTimestamp = System.currentTimeMillis(); + + // free used buffer + { + int usedBufferCount = streamSpeaker.queryProcessedBufferCount(); +// if(usedBufferCount != 0) { +// System.out.println(usedBufferCount); +// } + var usedBuffers = new ALBuffer[usedBufferCount]; + streamSpeaker.unqueueBuffer(usedBuffers); + + for(var buf : usedBuffers) { + if(buf == null) continue; + var handler = bufferHandlerMap.remove(buf); + handler.release(); +// System.out.println("[FREE] " + handler.toString()); + } + + } + +// if(pool.getFreeCount() != 0) { +// System.out.println(pool.getAllocatedCount() + ", " + pool.getFreeCount()); +// } + +// System.out.println(pool.getAllocatedCount() + ", " + pool.getFreeCount()); + + // store buffer + while(pool.getAllocatedCount() < 20) { + var handler = pool.spare(); +// System.out.println("[ALLOC] " + handler.toString()); + var memory = handler.getBuffer(); + var alBuffer = bufferMap.get(memory); + bufferHandlerMap.put(alBuffer, handler); + + if(bufferedCount % 10 == 0) { + System.out.println("==== [BufferTime] " + bufferedCount + "," + bufferTime); + } + + for(int i = 0; i < BUFFER_SAMPLE_COUNT; i++) { + bufferTime = (bufferedCount * BUFFER_SAMPLE_COUNT + i) * PER_SAMPLE_LENGTH; + + // do sequencer + { + sequencer.progress((int)(bufferTime * 1000), sequencerDest); + for(var event : sequencerDest) { + System.out.println("[note] " + event.toString()); + playingNotes.add(new PlayingNote(bufferTime, event)); + } + sequencerDest.clear(); + + } + + // write buffer + { + double thisValue = 0.0; + + var noteIterator = playingNotes.iterator(); + while(noteIterator.hasNext()) { + var note = noteIterator.next(); + double sustain = bufferTime - note.startTime; + if(sustain > 1.0) noteIterator.remove(); +// System.out.println(sustain); + + double frequency = (440.0 / 32.0) * Math.pow(2, (note.block.getKey() + 24 - 9.0) / 12.0); +// System.out.println(frequency); + double gain = sustain < 0.05 ? (sustain * 20) : (1 - (sustain - 0.05) / 0.95); + + double originValue = 0.2 * gain * SynthesizerUtils.triangle(frequency, sustain) * (1.0 - sustain); + thisValue += originValue; + + // write to limiter +// { +// limiter_index++; +// limiter_index %= LIMITER_CACHE_SAMPLE_COUNT; +// limiter_temp[limiter_index] = thisValue; +// +// } + + } + + // do limiter +// { +// double max = 0; +// for(int j = 0; j < LIMITER_CACHE_SAMPLE_COUNT; j++) { +// max = Math.max(max, Math.abs(limiter_temp[j])); +// } +// +// if(max > 1.0) { +// limiter_volumeScale = 1.0 / max; +// } +// +// } + +// if(thisValue > 1.0) thisValue = 1.0; +// if(thisValue < -1.0) thisValue = -1.0; +// thisValue *= limiter_volumeScale; + memory.putShort((short) (thisValue * 32767.0)); + } + + } + + bufferedCount++; + + memory.setReadWrite(true); + alBuffer.setBuffer(memory, BufferFormat.MONO16, SAMPLE_RATE); + alBuffer.doingSyncIfNecessary(); + streamSpeaker.queueBuffer(alBuffer); + + } + + int error = AL11.alGetError(); + if(error != 0) { + System.out.println("[ERROR] " + error); + } + + // check playing + SourceState state = streamSpeaker.getState(); + if(state == SourceState.STOPPED || state == SourceState.INITIAL) { + streamSpeaker.play(); + } + + // check exit + if(bufferedCount > endBufferCount) break; + + try { + Thread.sleep(16); + } catch (InterruptedException e) { + e.printStackTrace(); + } + +// System.out.println(bufferedCount + ", " + endBufferCount); + + } + + // Release Driver + driver.releaseSpeaker(streamSpeaker); + driver.release(); + + // Release Memory Pool + try { + pool.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + + } + +} diff --git a/src/test/java/xueli/daw/nbs/Layer.java b/src/test/java/xueli/daw/nbs/Layer.java new file mode 100644 index 00000000..096557a9 --- /dev/null +++ b/src/test/java/xueli/daw/nbs/Layer.java @@ -0,0 +1,40 @@ +package xueli.daw.nbs; + +public class Layer { + + private int layerId; + private String name; + private boolean isLocked; + private float volume; + private float panning; + + public Layer(int layerId, String name, boolean isLocked, float volume, float panning) { + this.layerId = layerId; + this.isLocked = isLocked; + this.volume = volume; + this.panning = panning; + this.name = name; + + } + + public int getLayerId() { + return layerId; + } + + public String getName() { + return name; + } + + public boolean isLocked() { + return isLocked; + } + + public float getVolume() { + return volume; + } + + public float getPanning() { + return panning; + } + +} diff --git a/src/test/java/xueli/daw/nbs/NBSInputStream.java b/src/test/java/xueli/daw/nbs/NBSInputStream.java new file mode 100644 index 00000000..d578f926 --- /dev/null +++ b/src/test/java/xueli/daw/nbs/NBSInputStream.java @@ -0,0 +1,192 @@ +package xueli.daw.nbs; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +// I don't know why someone uses "extends" here! +public class NBSInputStream extends DataInputStream { + + private final short songLength; + private final short songHeight; + private final String name; + private final String author; + private final String originAuthor; + private final String desc; + private final short tempo; + private final byte autoSaving; + private final byte autoSavingDuration; + private final byte timeSign; + private final int minSpent; + private final int leftClicks; + private final int rightClicks; + private final int blocksAdded; + private final int blocksRemoved; + private final String filename; + private final boolean loopOn; + private final byte maxLoopCount; + private final short loopStartTick; + + public NBSInputStream(InputStream in) throws IOException { + super(in); + + readInt(); + + songLength = Short.reverseBytes(readShort()); + songHeight = Short.reverseBytes(readShort()); + name = readString(); + author = readString(); + originAuthor = readString(); + desc = readString(); + tempo = Short.reverseBytes(readShort()); + autoSaving = readByte(); + autoSavingDuration = readByte(); + timeSign = readByte(); + minSpent = Integer.reverseBytes(readInt()); + leftClicks = Integer.reverseBytes(readInt()); + rightClicks = Integer.reverseBytes(readInt()); + blocksAdded = Integer.reverseBytes(readInt()); + blocksRemoved = Integer.reverseBytes(readInt()); + filename = readString(); + loopOn = readBoolean(); + maxLoopCount = readByte(); + loopStartTick = Short.reverseBytes(readShort()); + + } + + @SuppressWarnings("unused") + public List readNoteBlocks() throws IOException { + ArrayList blocks = new ArrayList<>(); + + short maxlayer = 0; + + short tick = -1; + short jumps = 0; + while (true) { + jumps = Short.reverseBytes(readShort()); + if (jumps == 0) break; + tick += jumps; + short layer = -1; + while (true) { + jumps = Short.reverseBytes(readShort()); + if (jumps == 0) break; + layer += jumps; + + byte inst = readByte(); + byte key = readByte(); + byte velocity = readByte(); + byte panning = readByte(); + short pitch = Short.reverseBytes(readShort()); + + if (layer > maxlayer) + maxlayer = layer; + + blocks.add(new NoteBlock((short) tick, layer, inst, key, (float) velocity / 100.0f)); + + } + } + + // layers + Layer[] layers = new Layer[maxlayer + 1]; + for (int i = 0; i < layers.length; i++) { + String name = readString(); + boolean locked = readBoolean(); + float volume = readByte() / 100.0f; + float panning = (readByte() - 100.0f) / 100.0f; + layers[i] = new Layer(i, name, locked, volume, panning); + + } + + blocks.forEach(b -> b.setVolume(b.getVolume() * layers[b.getLayer()].getVolume())); + + return blocks; + } + + private String readString() throws IOException { + int length = Integer.reverseBytes(readInt()); +// System.out.println(length); + byte[] strBytes = new byte[length]; + read(strBytes, 0, length); + String str = new String(strBytes); + return str; + } + + public short getSongLength() { + return songLength; + } + + public short getSongHeight() { + return songHeight; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getOriginAuthor() { + return originAuthor; + } + + public String getDesc() { + return desc; + } + + public short getTempo() { + return tempo; + } + + public byte getAutoSaving() { + return autoSaving; + } + + public byte getAutoSavingDuration() { + return autoSavingDuration; + } + + public byte getTimeSign() { + return timeSign; + } + + public int getMinSpent() { + return minSpent; + } + + public int getLeftClicks() { + return leftClicks; + } + + public int getRightClicks() { + return rightClicks; + } + + public int getBlocksAdded() { + return blocksAdded; + } + + public int getBlocksRemoved() { + return blocksRemoved; + } + + public String getFilename() { + return filename; + } + + public boolean isLoopOn() { + return loopOn; + } + + public byte getMaxLoopCount() { + return maxLoopCount; + } + + public short getLoopStartTick() { + return loopStartTick; + } + +} diff --git a/src/test/java/xueli/daw/nbs/NBSSequencer.java b/src/test/java/xueli/daw/nbs/NBSSequencer.java new file mode 100644 index 00000000..63dcdacf --- /dev/null +++ b/src/test/java/xueli/daw/nbs/NBSSequencer.java @@ -0,0 +1,51 @@ +package xueli.daw.nbs; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +// TODO: Add start time! +// Maybe this object can be constructed from a NBS object! +// May be an Sequencer interface! +public class NBSSequencer { + + private final double tickPerSecond; + private final TreeMap> timeline = new TreeMap<>(); + + public NBSSequencer(double tickPerSecond, List src) { + this.tickPerSecond = tickPerSecond; + this.buildTimeline(src); + + } + + private void buildTimeline(List src) { + src.forEach(n -> { + this.addTimelineItem((long)(n.getTick() * 1000 / tickPerSecond), n); + }); + } + + private void addTimelineItem(long time, NoteBlock item) { + timeline.computeIfAbsent(time, t -> new ArrayList<>()).add(item); + } + + private long lastTime = 0; + + // The return value indicated whether the music reaches the end. + public boolean progress(long currentTimeMills, List dest) { + Long lowerKey = currentTimeMills; + boolean exists = false; + while((lowerKey = this.timeline.lowerKey(lowerKey)) != null) { + if(lowerKey.longValue() < lastTime) break; + exists = true; + dest.addAll(timeline.get(lowerKey)); + } + + lastTime = currentTimeMills; + return exists; + } + + public long getCurrentTime() { + return this.lastTime; + } + +} diff --git a/src/test/java/xueli/daw/nbs/NoteBlock.java b/src/test/java/xueli/daw/nbs/NoteBlock.java new file mode 100644 index 00000000..d2ee99d1 --- /dev/null +++ b/src/test/java/xueli/daw/nbs/NoteBlock.java @@ -0,0 +1,91 @@ +package xueli.daw.nbs; + +public class NoteBlock { + + private final short tick; + private final short layer; + private final byte key; + private final byte inst; + private float volume; + + public NoteBlock(short tick, short layer, byte inst, byte key, float volume) { + this.tick = tick; + this.layer = layer; + this.key = key; + this.inst = inst; + this.volume = volume; + + } + + void setVolume(float volume) { + this.volume = volume; + } + + public short getTick() { + return tick; + } + + public short getLayer() { + return layer; + } + + public byte getInst() { + return inst; + } + + public byte getKey() { + return key; + } + + public String getStringFromNoteType() { + switch (inst) { + default: + return "harp"; + case 1: + return "bassattack"; + case 2: + return "bd"; + case 3: + return "snare"; + case 4: + return "hat"; + case 5: + return "guitar"; + case 6: + return "flute"; + case 7: + return "bell"; + case 8: + return "chime"; + case 9: + return "xylophone"; + case 10: + return "iron_xylophone"; + case 11: + return "cow_bell"; + case 12: + return "didgeridoo"; + case 13: + return "bit"; + case 14: + return "banjo"; + case 15: + return "pling"; + } + } + + @Override + public String toString() { + return "NoteBlock{" + + "tick=" + tick + + ", layer=" + layer + + ", key=" + key + + ", inst='" + inst + '\'' + + '}'; + } + + public float getVolume() { + return volume; + } + +} diff --git a/src/test/java/xueli/game2/ecs/ComponentListTest.java b/src/test/java/xueli/game2/ecs/ComponentListTest.java new file mode 100644 index 00000000..13a14d10 --- /dev/null +++ b/src/test/java/xueli/game2/ecs/ComponentListTest.java @@ -0,0 +1,25 @@ +package xueli.game2.ecs; + +import org.lwjgl.utils.vector.Vector2d; +import org.lwjgl.utils.vector.Vector3b; +import org.lwjgl.utils.vector.Vector3f; +import org.lwjgl.utils.vector.Vector3i; + +public class ComponentListTest { + + public static void main(String[] args) { + ResourceList list = new ResourceListImpl<>(); + list.add(new Vector3b((byte) 2, (byte) 3, (byte) 3)); + list.add(new Vector3f(2, 3, 3)); + list.add(new Vector3i(2, 3, 3)); + System.out.println(list); + + System.out.println(list.get(Vector3b.class)); + System.out.println(list.get(Vector2d.class)); + + list.remove(Vector3b.class); + System.out.println(list); + + } + +} diff --git a/src/test/java/xueli/game2/ecs/ECSTest.java b/src/test/java/xueli/game2/ecs/ECSTest.java new file mode 100644 index 00000000..9566afb3 --- /dev/null +++ b/src/test/java/xueli/game2/ecs/ECSTest.java @@ -0,0 +1,58 @@ +package xueli.game2.ecs; + +import org.lwjgl.utils.vector.Vector2d; +import org.lwjgl.utils.vector.Vector3f; + +public class ECSTest { + + private final ECSContext ctx = new ECSContextImpl(); + + ECSTest() { + } + + public void run() { + ctx.addSystem(new TestSystem()); + ctx.addResource(new Vector2d(0.2333, 0.23333)); + + ctx.startUp(); + ctx.update(); + ctx.shutdown(); + + } + + private class TestSystem implements ECSSystem { + + private long entity; + + @Override + public void start(ECSContext ctx) { + this.entity = ctx.spawn(); + ctx.addComponent(entity, new Vector3f(2, 3, 3)); + + } + + @Override + public void update(ECSContext ctx) { + Vector3f v = ctx.getComponent(entity, Vector3f.class); + v.y += 1; + System.out.println(v); + + Vector2d v2 = ctx.getResource(Vector2d.class); + v2.x += 1; + System.out.println(v2); + + } + + @Override + public void shutdown(ECSContext ctx) { + ctx.removeComponent(entity, Vector3f.class); + + } + + } + + public static void main(String[] args) { + new ECSTest().run(); + } + +} diff --git a/src/test/java/xueli/game2/ecs/SparseSetTest.java b/src/test/java/xueli/game2/ecs/SparseSetTest.java new file mode 100644 index 00000000..cf7cef18 --- /dev/null +++ b/src/test/java/xueli/game2/ecs/SparseSetTest.java @@ -0,0 +1,36 @@ +package xueli.game2.ecs; + +public class SparseSetTest { + + public static void main(String[] args) { + SparseSet set = new SparseSet(); + set.add(1); + set.debugPrint(); + set.add(3); + set.debugPrint(); + set.add(5); + set.debugPrint(); + + set.remove(3); + set.debugPrint(); + set.add(80); + set.debugPrint(); + set.remove(70); + set.debugPrint(); + set.add(75); + set.debugPrint(); + set.remove(80); + set.debugPrint(); + + set.remove(1); + set.debugPrint(); + + set.remove(3); + set.debugPrint(); + + set.remove(5); + set.debugPrint(); + + } + +} diff --git a/src/test/java/xueli/game2/memory/DirectArrayListTest.java b/src/test/java/xueli/game2/memory/DirectArrayListTest.java new file mode 100644 index 00000000..14db7103 --- /dev/null +++ b/src/test/java/xueli/game2/memory/DirectArrayListTest.java @@ -0,0 +1,56 @@ +package xueli.game2.memory; + +import org.junit.jupiter.api.Test; + +import xueli.utils.collection.DirectArrayList; + +public class DirectArrayListTest { + + @Test + public void addTest() { + DirectArrayList vector = new DirectArrayList(1000000, 1000000); + for (int i = 0; i < 99999999; i++) { + vector.putByte((byte) 0); + if (i % 10000000 == 0) { + System.out.println(vector); + } + } + vector.release(); + } + + @Test + public void releaseTest() { + for (int j = 0; j < 1000; j++) { + DirectArrayList vector2 = new DirectArrayList(10000, 100); + vector2.release(); + + if (j % 1000 == 0) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + } + + @Test + public void leakTest() { +// for(int j = 0; j < 100; j++) { +// new CVector(100000, 100); +// +// if(j % 10 == 0) { +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// +// } + System.out.println("Just for development test, so I disabled it."); + + } + +} diff --git a/src/test/java/xueli/game2/renderer/legacy/buffer/BufferTest.java b/src/test/java/xueli/game2/renderer/legacy/buffer/BufferTest.java new file mode 100644 index 00000000..36e28012 --- /dev/null +++ b/src/test/java/xueli/game2/renderer/legacy/buffer/BufferTest.java @@ -0,0 +1,24 @@ +package xueli.game2.renderer.legacy.buffer; + +import org.junit.jupiter.api.Test; + +import xueli.utils.buffer.LotsOfByteBuffer; + +public class BufferTest { + + @Test + public void releaseTest() { + for (int i = 0; i < 10; i++) { + LotsOfByteBuffer l = new LotsOfByteBuffer(1000000000); + l.release(); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + +} diff --git a/src/test/java/xueli/gui/UITest.java b/src/test/java/xueli/gui/UITest.java new file mode 100644 index 00000000..e3e39134 --- /dev/null +++ b/src/test/java/xueli/gui/UITest.java @@ -0,0 +1,79 @@ +package xueli.gui; + +import xueli.game2.display.GameDisplay; +import xueli.game2.math.TriFuncMap; +import xueli.game2.renderer.ui.NanoGui; +import xueli.gui.widget.Rectangle; + +public class UITest extends GameDisplay { + + private NanoGui uiDriver; + private UIContext context; + + public UITest() { + super(800, 600, "UITest"); + } + + Rectangle testWidget; + + @Override + protected void renderInit() { + this.uiDriver = new NanoGui(); + this.uiDriver.reload(); + + this.context = new UIContext(uiDriver, this); + var rootWidget = context.getRootWidget(); +// rootWidget.setUseImmediateMode(true); + + var layer1Widget = new WidgetGroup(context); + layer1Widget.setUseImmediateMode(true); + layer1Widget.setBounds(10, 10, 100, 100); + rootWidget.add(layer1Widget); + +// var layer2Widget = new WidgetGroup(context); +// layer2Widget.setUseImmediateMode(true); +// layer2Widget.setBounds(10, 10, 20, 20); +// layer1Widget.add(layer2Widget); + +// var testWidget = new Rectangle(context); +// testWidget.setBounds(10, 10, 100, 100); +// layer2Widget.add(testWidget); + + var test2Widget = new Rectangle(context); + test2Widget.setBounds(50, 50, 500, 500); + layer1Widget.add(test2Widget); + + this.display.setMouseGrabbed(false); + + } + + @Override + protected void render() { +// GL11.glClearColor(1, 1, 1, 1); + + + +// long time = System.currentTimeMillis(); +// testWidget.setBounds(10, (float) (200 + 100 * TriFuncMap.sin((time % 1500) * 360.0 / 1500.0)), +// (float) (500 + 100 * TriFuncMap.sin((time % 2000) * 360.0 / 2000.0)), +// (float) (300 + 100 * TriFuncMap.sin((time % 2500) * 360.0 / 2500.0))); + +// System.out.println("=== render start ==="); + this.context.tick(); +// System.out.println("*** render end ***"); + + } + + @Override + protected void renderRelease() { + this.context.release(); + this.uiDriver.release(); + + } + + public static void main(String[] args) { + new UITest().run(); + + } + +} diff --git a/src/test/java/xueli/mcremake/FontTest.java b/src/test/java/xueli/mcremake/FontTest.java new file mode 100644 index 00000000..01eca66a --- /dev/null +++ b/src/test/java/xueli/mcremake/FontTest.java @@ -0,0 +1,54 @@ +package xueli.mcremake; + +import java.awt.Color; + +import xueli.game2.display.GameDisplay; +import xueli.game2.math.TriFuncMap; +import xueli.mcremake.registry.MojanglesFont; + +public class FontTest extends GameDisplay { + + private MojanglesFont font; + + public FontTest() { + super(800, 600, "Font Test"); + + } + + @Override + protected void renderInit() { + this.font = new MojanglesFont(this); + this.font.reload(); +// this.resourceManager.addResourceHolder(this.font); + + this.display.setMouseGrabbed(false); + + } + + @Override + protected void render() { + font.tick(); + + long time = System.currentTimeMillis(); + font.drawFont(100, 80, (float) (40 + 5 * TriFuncMap.sin((time % 1500) * 360.0 / 1500.0)), 0.2f, "FONT TEST", + new Color((float) (0.7 + 0.3 * TriFuncMap.sin((time % 1600) * 360.0 / 1600.0)), + (float) (0.7 + 0.3 * TriFuncMap.sin((time % 900) * 360.0 / 900.0)), + (float) (0.7 + 0.3 * TriFuncMap.sin((time % 2500) * 360.0 / 2500.0)))); + + font.drawFont(100, 132, 18.0f, 0.25f, "FPS: " + fps.getFps(), Color.LIGHT_GRAY); + font.drawFont(100, 155, 18.0f, 0.125f, "Date!", Color.DARK_GRAY); + + } + + @Override + protected void renderRelease() { + this.font.release(); + + } + + public static void main(String[] args) { + new FontTest().run(); + + } + +} diff --git a/src/test/java/xueli/mcremake/server/ClientTest.java b/src/test/java/xueli/mcremake/server/ClientTest.java new file mode 100755 index 00000000..c1dcab16 --- /dev/null +++ b/src/test/java/xueli/mcremake/server/ClientTest.java @@ -0,0 +1,22 @@ +package xueli.mcremake.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.UUID; + +import xueli.game2.network.ClientConnection; +import xueli.mcremake.network.PacketSourceSide; +import xueli.mcremake.network.protocol.C00HelloPacket; + +public class ClientTest { + + public static void main(String[] args) throws IOException { + ClientConnection conn = ClientConnection.connectToServer(PacketSourceSide.FROM_SERVER.getProtocol(), + PacketSourceSide.FROM_CLIENT.getProtocol(), new InetSocketAddress(8000)); + conn.writeAndFlush(new C00HelloPacket("lovelizeeiam", UUID.randomUUID()), () -> { + conn.close(); + }); + + } + +} diff --git a/src/test/java/xueli/mcremake/server/ServerTest.java b/src/test/java/xueli/mcremake/server/ServerTest.java new file mode 100755 index 00000000..059104ba --- /dev/null +++ b/src/test/java/xueli/mcremake/server/ServerTest.java @@ -0,0 +1,11 @@ +package xueli.mcremake.server; + +public class ServerTest { + + public static void main(String[] args) { + CraftGameServer server = new CraftGameServer(8000); + server.run(); + + } + +} diff --git a/src/test/resources/assets/atlastest/images/acacia_trapdoor.png b/src/test/resources/assets/atlastest/images/acacia_trapdoor.png new file mode 100644 index 00000000..e8eeb9c1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/acacia_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/anvil_base.png b/src/test/resources/assets/atlastest/images/anvil_base.png new file mode 100644 index 00000000..fdac9fb8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/anvil_base.png differ diff --git a/src/test/resources/assets/atlastest/images/anvil_top_damaged_0.png b/src/test/resources/assets/atlastest/images/anvil_top_damaged_0.png new file mode 100644 index 00000000..7cde91a6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/anvil_top_damaged_0.png differ diff --git a/src/test/resources/assets/atlastest/images/anvil_top_damaged_1.png b/src/test/resources/assets/atlastest/images/anvil_top_damaged_1.png new file mode 100644 index 00000000..cee8c4e0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/anvil_top_damaged_1.png differ diff --git a/src/test/resources/assets/atlastest/images/anvil_top_damaged_2.png b/src/test/resources/assets/atlastest/images/anvil_top_damaged_2.png new file mode 100644 index 00000000..27793e7a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/anvil_top_damaged_2.png differ diff --git a/src/test/resources/assets/atlastest/images/bamboo_leaf.png b/src/test/resources/assets/atlastest/images/bamboo_leaf.png new file mode 100644 index 00000000..5ce005ad Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bamboo_leaf.png differ diff --git a/src/test/resources/assets/atlastest/images/bamboo_sapling.png b/src/test/resources/assets/atlastest/images/bamboo_sapling.png new file mode 100644 index 00000000..150c199a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bamboo_sapling.png differ diff --git a/src/test/resources/assets/atlastest/images/bamboo_singleleaf.png b/src/test/resources/assets/atlastest/images/bamboo_singleleaf.png new file mode 100644 index 00000000..37e55d11 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bamboo_singleleaf.png differ diff --git a/src/test/resources/assets/atlastest/images/bamboo_small_leaf.png b/src/test/resources/assets/atlastest/images/bamboo_small_leaf.png new file mode 100644 index 00000000..a58951d2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bamboo_small_leaf.png differ diff --git a/src/test/resources/assets/atlastest/images/bamboo_stem.png b/src/test/resources/assets/atlastest/images/bamboo_stem.png new file mode 100644 index 00000000..b9a9a00d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bamboo_stem.png differ diff --git a/src/test/resources/assets/atlastest/images/barrel_bottom.png b/src/test/resources/assets/atlastest/images/barrel_bottom.png new file mode 100644 index 00000000..cd058fc2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/barrel_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/barrel_side.png b/src/test/resources/assets/atlastest/images/barrel_side.png new file mode 100644 index 00000000..f2d4b7f5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/barrel_side.png differ diff --git a/src/test/resources/assets/atlastest/images/barrel_top.png b/src/test/resources/assets/atlastest/images/barrel_top.png new file mode 100644 index 00000000..cb1229f5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/barrel_top.png differ diff --git a/src/test/resources/assets/atlastest/images/barrel_top_open.png b/src/test/resources/assets/atlastest/images/barrel_top_open.png new file mode 100644 index 00000000..3c77c53a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/barrel_top_open.png differ diff --git a/src/test/resources/assets/atlastest/images/barrier.png b/src/test/resources/assets/atlastest/images/barrier.png new file mode 100644 index 00000000..49cd1f96 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/barrier.png differ diff --git a/src/test/resources/assets/atlastest/images/beacon.png b/src/test/resources/assets/atlastest/images/beacon.png new file mode 100644 index 00000000..ba857f62 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/beacon.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_feet_end.png b/src/test/resources/assets/atlastest/images/bed_feet_end.png new file mode 100644 index 00000000..41838cf3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_feet_end.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_feet_side.png b/src/test/resources/assets/atlastest/images/bed_feet_side.png new file mode 100644 index 00000000..69627219 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_feet_side.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_feet_top.png b/src/test/resources/assets/atlastest/images/bed_feet_top.png new file mode 100644 index 00000000..43ba6a36 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_feet_top.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_head_end.png b/src/test/resources/assets/atlastest/images/bed_head_end.png new file mode 100644 index 00000000..95888bf1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_head_end.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_head_side.png b/src/test/resources/assets/atlastest/images/bed_head_side.png new file mode 100644 index 00000000..4a49da31 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_head_side.png differ diff --git a/src/test/resources/assets/atlastest/images/bed_head_top.png b/src/test/resources/assets/atlastest/images/bed_head_top.png new file mode 100644 index 00000000..024640f0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bed_head_top.png differ diff --git a/src/test/resources/assets/atlastest/images/bedrock.png b/src/test/resources/assets/atlastest/images/bedrock.png new file mode 100644 index 00000000..e2df190e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bedrock.png differ diff --git a/src/test/resources/assets/atlastest/images/beetroots_stage_0.png b/src/test/resources/assets/atlastest/images/beetroots_stage_0.png new file mode 100644 index 00000000..19a20ac1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/beetroots_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/beetroots_stage_1.png b/src/test/resources/assets/atlastest/images/beetroots_stage_1.png new file mode 100644 index 00000000..edd1159b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/beetroots_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/beetroots_stage_2.png b/src/test/resources/assets/atlastest/images/beetroots_stage_2.png new file mode 100644 index 00000000..4ad51f27 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/beetroots_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/beetroots_stage_3.png b/src/test/resources/assets/atlastest/images/beetroots_stage_3.png new file mode 100644 index 00000000..8f7bb4e5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/beetroots_stage_3.png differ diff --git a/src/test/resources/assets/atlastest/images/bell_bottom.png b/src/test/resources/assets/atlastest/images/bell_bottom.png new file mode 100644 index 00000000..f87e9caf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bell_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/bell_side.png b/src/test/resources/assets/atlastest/images/bell_side.png new file mode 100644 index 00000000..b2ad30d9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bell_side.png differ diff --git a/src/test/resources/assets/atlastest/images/bell_top.png b/src/test/resources/assets/atlastest/images/bell_top.png new file mode 100644 index 00000000..44bb0f35 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bell_top.png differ diff --git a/src/test/resources/assets/atlastest/images/birch_trapdoor.png b/src/test/resources/assets/atlastest/images/birch_trapdoor.png new file mode 100644 index 00000000..01255fe5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/birch_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/blast_furnace_front_off.png b/src/test/resources/assets/atlastest/images/blast_furnace_front_off.png new file mode 100644 index 00000000..537a8588 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/blast_furnace_front_off.png differ diff --git a/src/test/resources/assets/atlastest/images/blast_furnace_top.png b/src/test/resources/assets/atlastest/images/blast_furnace_top.png new file mode 100644 index 00000000..234fc812 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/blast_furnace_top.png differ diff --git a/src/test/resources/assets/atlastest/images/blue_ice.png b/src/test/resources/assets/atlastest/images/blue_ice.png new file mode 100644 index 00000000..d299f3ce Binary files /dev/null and b/src/test/resources/assets/atlastest/images/blue_ice.png differ diff --git a/src/test/resources/assets/atlastest/images/bone_block_side.png b/src/test/resources/assets/atlastest/images/bone_block_side.png new file mode 100644 index 00000000..5b98c625 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bone_block_side.png differ diff --git a/src/test/resources/assets/atlastest/images/bone_block_top.png b/src/test/resources/assets/atlastest/images/bone_block_top.png new file mode 100644 index 00000000..b2695802 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bone_block_top.png differ diff --git a/src/test/resources/assets/atlastest/images/brewing_stand.png b/src/test/resources/assets/atlastest/images/brewing_stand.png new file mode 100644 index 00000000..e41c4b1d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/brewing_stand.png differ diff --git a/src/test/resources/assets/atlastest/images/brewing_stand_base.png b/src/test/resources/assets/atlastest/images/brewing_stand_base.png new file mode 100644 index 00000000..b6443c1f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/brewing_stand_base.png differ diff --git a/src/test/resources/assets/atlastest/images/brick.png b/src/test/resources/assets/atlastest/images/brick.png new file mode 100644 index 00000000..2b60267b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/brick.png differ diff --git a/src/test/resources/assets/atlastest/images/bubble_column_inner_a.png b/src/test/resources/assets/atlastest/images/bubble_column_inner_a.png new file mode 100644 index 00000000..4a6d4375 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bubble_column_inner_a.png differ diff --git a/src/test/resources/assets/atlastest/images/bubble_column_inner_b.png b/src/test/resources/assets/atlastest/images/bubble_column_inner_b.png new file mode 100644 index 00000000..4a6d4375 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/bubble_column_inner_b.png differ diff --git a/src/test/resources/assets/atlastest/images/build_allow.png b/src/test/resources/assets/atlastest/images/build_allow.png new file mode 100644 index 00000000..8d7156d4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/build_allow.png differ diff --git a/src/test/resources/assets/atlastest/images/cake_bottom.png b/src/test/resources/assets/atlastest/images/cake_bottom.png new file mode 100644 index 00000000..21f88727 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cake_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/cake_inner.png b/src/test/resources/assets/atlastest/images/cake_inner.png new file mode 100644 index 00000000..2de770a7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cake_inner.png differ diff --git a/src/test/resources/assets/atlastest/images/cake_side.png b/src/test/resources/assets/atlastest/images/cake_side.png new file mode 100644 index 00000000..4897a663 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cake_side.png differ diff --git a/src/test/resources/assets/atlastest/images/cake_top.png b/src/test/resources/assets/atlastest/images/cake_top.png new file mode 100644 index 00000000..1b68a52d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cake_top.png differ diff --git a/src/test/resources/assets/atlastest/images/camera_back.png b/src/test/resources/assets/atlastest/images/camera_back.png new file mode 100644 index 00000000..bfd4456f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/camera_back.png differ diff --git a/src/test/resources/assets/atlastest/images/camera_front.png b/src/test/resources/assets/atlastest/images/camera_front.png new file mode 100644 index 00000000..0c797311 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/camera_front.png differ diff --git a/src/test/resources/assets/atlastest/images/camera_side.png b/src/test/resources/assets/atlastest/images/camera_side.png new file mode 100644 index 00000000..9b4fd767 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/camera_side.png differ diff --git a/src/test/resources/assets/atlastest/images/camera_top.png b/src/test/resources/assets/atlastest/images/camera_top.png new file mode 100644 index 00000000..bd034d13 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/camera_top.png differ diff --git a/src/test/resources/assets/atlastest/images/campfire_log.png b/src/test/resources/assets/atlastest/images/campfire_log.png new file mode 100644 index 00000000..6f5eddc6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/campfire_log.png differ diff --git a/src/test/resources/assets/atlastest/images/carried_waterlily.png b/src/test/resources/assets/atlastest/images/carried_waterlily.png new file mode 100644 index 00000000..48145290 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/carried_waterlily.png differ diff --git a/src/test/resources/assets/atlastest/images/carrots_stage_0.png b/src/test/resources/assets/atlastest/images/carrots_stage_0.png new file mode 100644 index 00000000..d5c713f8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/carrots_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/carrots_stage_1.png b/src/test/resources/assets/atlastest/images/carrots_stage_1.png new file mode 100644 index 00000000..5b96d93d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/carrots_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/carrots_stage_2.png b/src/test/resources/assets/atlastest/images/carrots_stage_2.png new file mode 100644 index 00000000..6fb9e884 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/carrots_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/carrots_stage_3.png b/src/test/resources/assets/atlastest/images/carrots_stage_3.png new file mode 100644 index 00000000..d3e5cd86 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/carrots_stage_3.png differ diff --git a/src/test/resources/assets/atlastest/images/cartography_table_side1.png b/src/test/resources/assets/atlastest/images/cartography_table_side1.png new file mode 100644 index 00000000..9bf57927 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cartography_table_side1.png differ diff --git a/src/test/resources/assets/atlastest/images/cartography_table_side2.png b/src/test/resources/assets/atlastest/images/cartography_table_side2.png new file mode 100644 index 00000000..18878b5f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cartography_table_side2.png differ diff --git a/src/test/resources/assets/atlastest/images/cartography_table_side3.png b/src/test/resources/assets/atlastest/images/cartography_table_side3.png new file mode 100644 index 00000000..7c31c0e0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cartography_table_side3.png differ diff --git a/src/test/resources/assets/atlastest/images/cartography_table_top.png b/src/test/resources/assets/atlastest/images/cartography_table_top.png new file mode 100644 index 00000000..0dbc3255 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cartography_table_top.png differ diff --git a/src/test/resources/assets/atlastest/images/cauldron_bottom.png b/src/test/resources/assets/atlastest/images/cauldron_bottom.png new file mode 100644 index 00000000..1656e3a6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cauldron_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/cauldron_inner.png b/src/test/resources/assets/atlastest/images/cauldron_inner.png new file mode 100644 index 00000000..29e141ba Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cauldron_inner.png differ diff --git a/src/test/resources/assets/atlastest/images/cauldron_side.png b/src/test/resources/assets/atlastest/images/cauldron_side.png new file mode 100644 index 00000000..32c7e63b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cauldron_side.png differ diff --git a/src/test/resources/assets/atlastest/images/cauldron_top.png b/src/test/resources/assets/atlastest/images/cauldron_top.png new file mode 100644 index 00000000..71d903bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cauldron_top.png differ diff --git a/src/test/resources/assets/atlastest/images/cauldron_water_placeholder.png b/src/test/resources/assets/atlastest/images/cauldron_water_placeholder.png new file mode 100644 index 00000000..f6ef971d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cauldron_water_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/chain_command_block_back_mipmap.png b/src/test/resources/assets/atlastest/images/chain_command_block_back_mipmap.png new file mode 100644 index 00000000..e364c4ee Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chain_command_block_back_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/chain_command_block_conditional_mipmap.png b/src/test/resources/assets/atlastest/images/chain_command_block_conditional_mipmap.png new file mode 100644 index 00000000..052fe571 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chain_command_block_conditional_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/chain_command_block_front_mipmap.png b/src/test/resources/assets/atlastest/images/chain_command_block_front_mipmap.png new file mode 100644 index 00000000..4fe6f3a0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chain_command_block_front_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/chain_command_block_side_mipmap.png b/src/test/resources/assets/atlastest/images/chain_command_block_side_mipmap.png new file mode 100644 index 00000000..d63b6a7b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chain_command_block_side_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/chest_side.png b/src/test/resources/assets/atlastest/images/chest_side.png new file mode 100644 index 00000000..9614a49a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chest_side.png differ diff --git a/src/test/resources/assets/atlastest/images/chest_top.png b/src/test/resources/assets/atlastest/images/chest_top.png new file mode 100644 index 00000000..f6b17469 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chest_top.png differ diff --git a/src/test/resources/assets/atlastest/images/chorus_flower.png b/src/test/resources/assets/atlastest/images/chorus_flower.png new file mode 100644 index 00000000..ce000a88 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chorus_flower.png differ diff --git a/src/test/resources/assets/atlastest/images/chorus_flower_dead.png b/src/test/resources/assets/atlastest/images/chorus_flower_dead.png new file mode 100644 index 00000000..eec0f9d3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chorus_flower_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/chorus_plant.png b/src/test/resources/assets/atlastest/images/chorus_plant.png new file mode 100644 index 00000000..418bbed2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/chorus_plant.png differ diff --git a/src/test/resources/assets/atlastest/images/clay.png b/src/test/resources/assets/atlastest/images/clay.png new file mode 100644 index 00000000..b350cefc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/clay.png differ diff --git a/src/test/resources/assets/atlastest/images/coal_block.png b/src/test/resources/assets/atlastest/images/coal_block.png new file mode 100644 index 00000000..383748a9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coal_block.png differ diff --git a/src/test/resources/assets/atlastest/images/coal_ore.png b/src/test/resources/assets/atlastest/images/coal_ore.png new file mode 100644 index 00000000..c5250f0e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coal_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/coarse_dirt.png b/src/test/resources/assets/atlastest/images/coarse_dirt.png new file mode 100644 index 00000000..39211f12 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coarse_dirt.png differ diff --git a/src/test/resources/assets/atlastest/images/cobblestone.png b/src/test/resources/assets/atlastest/images/cobblestone.png new file mode 100644 index 00000000..7b9837af Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cobblestone.png differ diff --git a/src/test/resources/assets/atlastest/images/cobblestone_mossy.png b/src/test/resources/assets/atlastest/images/cobblestone_mossy.png new file mode 100644 index 00000000..153397ab Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cobblestone_mossy.png differ diff --git a/src/test/resources/assets/atlastest/images/cocoa_stage_0.png b/src/test/resources/assets/atlastest/images/cocoa_stage_0.png new file mode 100644 index 00000000..0b53b5ea Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cocoa_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/cocoa_stage_1.png b/src/test/resources/assets/atlastest/images/cocoa_stage_1.png new file mode 100644 index 00000000..a8c51a70 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cocoa_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/cocoa_stage_2.png b/src/test/resources/assets/atlastest/images/cocoa_stage_2.png new file mode 100644 index 00000000..677f3969 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/cocoa_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/command_block.png b/src/test/resources/assets/atlastest/images/command_block.png new file mode 100644 index 00000000..3efe9221 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/command_block.png differ diff --git a/src/test/resources/assets/atlastest/images/command_block_back_mipmap.png b/src/test/resources/assets/atlastest/images/command_block_back_mipmap.png new file mode 100644 index 00000000..3d9ec3a6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/command_block_back_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/command_block_conditional_mipmap.png b/src/test/resources/assets/atlastest/images/command_block_conditional_mipmap.png new file mode 100644 index 00000000..f78370bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/command_block_conditional_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/command_block_front_mipmap.png b/src/test/resources/assets/atlastest/images/command_block_front_mipmap.png new file mode 100644 index 00000000..d1f88229 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/command_block_front_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/command_block_side_mipmap.png b/src/test/resources/assets/atlastest/images/command_block_side_mipmap.png new file mode 100644 index 00000000..840f89ef Binary files /dev/null and b/src/test/resources/assets/atlastest/images/command_block_side_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/comparator_off.png b/src/test/resources/assets/atlastest/images/comparator_off.png new file mode 100644 index 00000000..cad9479f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/comparator_off.png differ diff --git a/src/test/resources/assets/atlastest/images/comparator_on.png b/src/test/resources/assets/atlastest/images/comparator_on.png new file mode 100644 index 00000000..daad7cc4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/comparator_on.png differ diff --git a/src/test/resources/assets/atlastest/images/compost.png b/src/test/resources/assets/atlastest/images/compost.png new file mode 100644 index 00000000..4ed1313d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/compost.png differ diff --git a/src/test/resources/assets/atlastest/images/compost_ready.png b/src/test/resources/assets/atlastest/images/compost_ready.png new file mode 100644 index 00000000..ab3b1492 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/compost_ready.png differ diff --git a/src/test/resources/assets/atlastest/images/composter_bottom.png b/src/test/resources/assets/atlastest/images/composter_bottom.png new file mode 100644 index 00000000..840a094a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/composter_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/composter_side.png b/src/test/resources/assets/atlastest/images/composter_side.png new file mode 100644 index 00000000..5bdbf0df Binary files /dev/null and b/src/test/resources/assets/atlastest/images/composter_side.png differ diff --git a/src/test/resources/assets/atlastest/images/composter_top.png b/src/test/resources/assets/atlastest/images/composter_top.png new file mode 100644 index 00000000..52a069c8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/composter_top.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_black.png b/src/test/resources/assets/atlastest/images/concrete_black.png new file mode 100644 index 00000000..8413b9c6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_black.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_blue.png b/src/test/resources/assets/atlastest/images/concrete_blue.png new file mode 100644 index 00000000..e1f68fc8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_brown.png b/src/test/resources/assets/atlastest/images/concrete_brown.png new file mode 100644 index 00000000..1ee67164 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_cyan.png b/src/test/resources/assets/atlastest/images/concrete_cyan.png new file mode 100644 index 00000000..ec3936cb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_gray.png b/src/test/resources/assets/atlastest/images/concrete_gray.png new file mode 100644 index 00000000..ee95a153 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_green.png b/src/test/resources/assets/atlastest/images/concrete_green.png new file mode 100644 index 00000000..87188983 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_green.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_light_blue.png b/src/test/resources/assets/atlastest/images/concrete_light_blue.png new file mode 100644 index 00000000..d5a46fe2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_lime.png b/src/test/resources/assets/atlastest/images/concrete_lime.png new file mode 100644 index 00000000..c4df61ec Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_magenta.png b/src/test/resources/assets/atlastest/images/concrete_magenta.png new file mode 100644 index 00000000..35326207 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_orange.png b/src/test/resources/assets/atlastest/images/concrete_orange.png new file mode 100644 index 00000000..243bbc82 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_pink.png b/src/test/resources/assets/atlastest/images/concrete_pink.png new file mode 100644 index 00000000..dd1950f7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_blue.png b/src/test/resources/assets/atlastest/images/concrete_powder_blue.png new file mode 100644 index 00000000..e78597e1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_brown.png b/src/test/resources/assets/atlastest/images/concrete_powder_brown.png new file mode 100644 index 00000000..14487d7d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_cyan.png b/src/test/resources/assets/atlastest/images/concrete_powder_cyan.png new file mode 100644 index 00000000..3a23a958 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_green.png b/src/test/resources/assets/atlastest/images/concrete_powder_green.png new file mode 100644 index 00000000..c3c78ec9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_green.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_light_blue.png b/src/test/resources/assets/atlastest/images/concrete_powder_light_blue.png new file mode 100644 index 00000000..fe8b7796 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_lime.png b/src/test/resources/assets/atlastest/images/concrete_powder_lime.png new file mode 100644 index 00000000..780aaeef Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_magenta.png b/src/test/resources/assets/atlastest/images/concrete_powder_magenta.png new file mode 100644 index 00000000..054553d3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_orange.png b/src/test/resources/assets/atlastest/images/concrete_powder_orange.png new file mode 100644 index 00000000..2fb6f15f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_pink.png b/src/test/resources/assets/atlastest/images/concrete_powder_pink.png new file mode 100644 index 00000000..9f6a6bb7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_purple.png b/src/test/resources/assets/atlastest/images/concrete_powder_purple.png new file mode 100644 index 00000000..aa1b1ac3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_red.png b/src/test/resources/assets/atlastest/images/concrete_powder_red.png new file mode 100644 index 00000000..7612112e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_red.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_silver.png b/src/test/resources/assets/atlastest/images/concrete_powder_silver.png new file mode 100644 index 00000000..18ebf59a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_white.png b/src/test/resources/assets/atlastest/images/concrete_powder_white.png new file mode 100644 index 00000000..35ae8ae4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_white.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_powder_yellow.png b/src/test/resources/assets/atlastest/images/concrete_powder_yellow.png new file mode 100644 index 00000000..74429838 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_powder_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_purple.png b/src/test/resources/assets/atlastest/images/concrete_purple.png new file mode 100644 index 00000000..bf90f745 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_red.png b/src/test/resources/assets/atlastest/images/concrete_red.png new file mode 100644 index 00000000..7f978842 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_red.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_silver.png b/src/test/resources/assets/atlastest/images/concrete_silver.png new file mode 100644 index 00000000..21d57aa1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_white.png b/src/test/resources/assets/atlastest/images/concrete_white.png new file mode 100644 index 00000000..e2f7f41d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_white.png differ diff --git a/src/test/resources/assets/atlastest/images/concrete_yellow.png b/src/test/resources/assets/atlastest/images/concrete_yellow.png new file mode 100644 index 00000000..8bc83432 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/concrete_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/conduit_base.png b/src/test/resources/assets/atlastest/images/conduit_base.png new file mode 100644 index 00000000..e763f777 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/conduit_base.png differ diff --git a/src/test/resources/assets/atlastest/images/conduit_closed.png b/src/test/resources/assets/atlastest/images/conduit_closed.png new file mode 100644 index 00000000..316a2c59 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/conduit_closed.png differ diff --git a/src/test/resources/assets/atlastest/images/conduit_open.png b/src/test/resources/assets/atlastest/images/conduit_open.png new file mode 100644 index 00000000..8be3e2e6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/conduit_open.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_blue.png b/src/test/resources/assets/atlastest/images/coral_blue.png new file mode 100644 index 00000000..3c476694 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_blue.png b/src/test/resources/assets/atlastest/images/coral_fan_blue.png new file mode 100644 index 00000000..0cfb038f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_pink.png b/src/test/resources/assets/atlastest/images/coral_fan_pink.png new file mode 100644 index 00000000..6ffd3451 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_purple.png b/src/test/resources/assets/atlastest/images/coral_fan_purple.png new file mode 100644 index 00000000..a399e191 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_purple_dead.png b/src/test/resources/assets/atlastest/images/coral_fan_purple_dead.png new file mode 100644 index 00000000..76d7e5a8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_purple_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_red.png b/src/test/resources/assets/atlastest/images/coral_fan_red.png new file mode 100644 index 00000000..f440ebdc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_red.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_yellow.png b/src/test/resources/assets/atlastest/images/coral_fan_yellow.png new file mode 100644 index 00000000..9ad363f3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_fan_yellow_dead.png b/src/test/resources/assets/atlastest/images/coral_fan_yellow_dead.png new file mode 100644 index 00000000..af4b7296 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_fan_yellow_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_pink.png b/src/test/resources/assets/atlastest/images/coral_pink.png new file mode 100644 index 00000000..8183affb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_pink_dead.png b/src/test/resources/assets/atlastest/images/coral_pink_dead.png new file mode 100644 index 00000000..c9a3966f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_pink_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_blue_dead.png b/src/test/resources/assets/atlastest/images/coral_plant_blue_dead.png new file mode 100644 index 00000000..8ac8ed12 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_blue_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_pink.png b/src/test/resources/assets/atlastest/images/coral_plant_pink.png new file mode 100644 index 00000000..eac0dc88 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_pink_dead.png b/src/test/resources/assets/atlastest/images/coral_plant_pink_dead.png new file mode 100644 index 00000000..29c082cb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_pink_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_purple.png b/src/test/resources/assets/atlastest/images/coral_plant_purple.png new file mode 100644 index 00000000..ddb0bbea Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_purple_dead.png b/src/test/resources/assets/atlastest/images/coral_plant_purple_dead.png new file mode 100644 index 00000000..bb9e259b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_purple_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_red.png b/src/test/resources/assets/atlastest/images/coral_plant_red.png new file mode 100644 index 00000000..78991636 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_red.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_red_dead.png b/src/test/resources/assets/atlastest/images/coral_plant_red_dead.png new file mode 100644 index 00000000..95c33ad9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_red_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_yellow.png b/src/test/resources/assets/atlastest/images/coral_plant_yellow.png new file mode 100644 index 00000000..c7d5ee3a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_plant_yellow_dead.png b/src/test/resources/assets/atlastest/images/coral_plant_yellow_dead.png new file mode 100644 index 00000000..3c6744f7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_plant_yellow_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_yellow.png b/src/test/resources/assets/atlastest/images/coral_yellow.png new file mode 100644 index 00000000..45bccc59 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/coral_yellow_dead.png b/src/test/resources/assets/atlastest/images/coral_yellow_dead.png new file mode 100644 index 00000000..21fce5ea Binary files /dev/null and b/src/test/resources/assets/atlastest/images/coral_yellow_dead.png differ diff --git a/src/test/resources/assets/atlastest/images/crafting_table_front.png b/src/test/resources/assets/atlastest/images/crafting_table_front.png new file mode 100644 index 00000000..3876be1a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/crafting_table_front.png differ diff --git a/src/test/resources/assets/atlastest/images/crafting_table_side.png b/src/test/resources/assets/atlastest/images/crafting_table_side.png new file mode 100644 index 00000000..905a7571 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/crafting_table_side.png differ diff --git a/src/test/resources/assets/atlastest/images/crafting_table_top.png b/src/test/resources/assets/atlastest/images/crafting_table_top.png new file mode 100644 index 00000000..d69a0b00 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/crafting_table_top.png differ diff --git a/src/test/resources/assets/atlastest/images/dark_oak_trapdoor.png b/src/test/resources/assets/atlastest/images/dark_oak_trapdoor.png new file mode 100644 index 00000000..55fc96ae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dark_oak_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/daylight_detector_inverted_top.png b/src/test/resources/assets/atlastest/images/daylight_detector_inverted_top.png new file mode 100644 index 00000000..ce5bedfe Binary files /dev/null and b/src/test/resources/assets/atlastest/images/daylight_detector_inverted_top.png differ diff --git a/src/test/resources/assets/atlastest/images/daylight_detector_side.png b/src/test/resources/assets/atlastest/images/daylight_detector_side.png new file mode 100644 index 00000000..05f7e83a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/daylight_detector_side.png differ diff --git a/src/test/resources/assets/atlastest/images/daylight_detector_top.png b/src/test/resources/assets/atlastest/images/daylight_detector_top.png new file mode 100644 index 00000000..9a408dc7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/daylight_detector_top.png differ diff --git a/src/test/resources/assets/atlastest/images/deadbush.png b/src/test/resources/assets/atlastest/images/deadbush.png new file mode 100644 index 00000000..5126d3bc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/deadbush.png differ diff --git a/src/test/resources/assets/atlastest/images/diamond_block.png b/src/test/resources/assets/atlastest/images/diamond_block.png new file mode 100644 index 00000000..be413394 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/diamond_block.png differ diff --git a/src/test/resources/assets/atlastest/images/diamond_ore.png b/src/test/resources/assets/atlastest/images/diamond_ore.png new file mode 100644 index 00000000..bd8b4bcf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/diamond_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/dirt.png b/src/test/resources/assets/atlastest/images/dirt.png new file mode 100644 index 00000000..2af99586 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dirt.png differ diff --git a/src/test/resources/assets/atlastest/images/dirt_podzol_side.png b/src/test/resources/assets/atlastest/images/dirt_podzol_side.png new file mode 100644 index 00000000..89a328b2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dirt_podzol_side.png differ diff --git a/src/test/resources/assets/atlastest/images/dirt_podzol_top.png b/src/test/resources/assets/atlastest/images/dirt_podzol_top.png new file mode 100644 index 00000000..b015daa6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dirt_podzol_top.png differ diff --git a/src/test/resources/assets/atlastest/images/dispenser_front_horizontal.png b/src/test/resources/assets/atlastest/images/dispenser_front_horizontal.png new file mode 100644 index 00000000..f4b225ab Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dispenser_front_horizontal.png differ diff --git a/src/test/resources/assets/atlastest/images/dispenser_front_vertical.png b/src/test/resources/assets/atlastest/images/dispenser_front_vertical.png new file mode 100644 index 00000000..9e9aebf8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dispenser_front_vertical.png differ diff --git a/src/test/resources/assets/atlastest/images/door_acacia_lower.png b/src/test/resources/assets/atlastest/images/door_acacia_lower.png new file mode 100644 index 00000000..9650e198 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_acacia_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_acacia_upper.png b/src/test/resources/assets/atlastest/images/door_acacia_upper.png new file mode 100644 index 00000000..98fc843f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_acacia_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_birch_lower.png b/src/test/resources/assets/atlastest/images/door_birch_lower.png new file mode 100644 index 00000000..836c5dfd Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_birch_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_birch_upper.png b/src/test/resources/assets/atlastest/images/door_birch_upper.png new file mode 100644 index 00000000..d290423b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_birch_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_dark_oak_lower.png b/src/test/resources/assets/atlastest/images/door_dark_oak_lower.png new file mode 100644 index 00000000..e2fbd966 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_dark_oak_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_dark_oak_upper.png b/src/test/resources/assets/atlastest/images/door_dark_oak_upper.png new file mode 100644 index 00000000..f4297870 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_dark_oak_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_iron_lower.png b/src/test/resources/assets/atlastest/images/door_iron_lower.png new file mode 100644 index 00000000..9e5cadc7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_iron_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_iron_upper.png b/src/test/resources/assets/atlastest/images/door_iron_upper.png new file mode 100644 index 00000000..4b404ac1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_iron_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_jungle_lower.png b/src/test/resources/assets/atlastest/images/door_jungle_lower.png new file mode 100644 index 00000000..8027c2ef Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_jungle_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_jungle_upper.png b/src/test/resources/assets/atlastest/images/door_jungle_upper.png new file mode 100644 index 00000000..0bfcb6f0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_jungle_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_spruce_lower.png b/src/test/resources/assets/atlastest/images/door_spruce_lower.png new file mode 100644 index 00000000..629f4a21 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_spruce_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_spruce_upper.png b/src/test/resources/assets/atlastest/images/door_spruce_upper.png new file mode 100644 index 00000000..444dc5cd Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_spruce_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/door_wood_lower.png b/src/test/resources/assets/atlastest/images/door_wood_lower.png new file mode 100644 index 00000000..419ef7e0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_wood_lower.png differ diff --git a/src/test/resources/assets/atlastest/images/door_wood_upper.png b/src/test/resources/assets/atlastest/images/door_wood_upper.png new file mode 100644 index 00000000..7d09bc9b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/door_wood_upper.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_fern_carried.png b/src/test/resources/assets/atlastest/images/double_plant_fern_carried.png new file mode 100644 index 00000000..069a03bc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_fern_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_grass_carried.png b/src/test/resources/assets/atlastest/images/double_plant_grass_carried.png new file mode 100644 index 00000000..cd68adb4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_grass_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_paeonia_bottom.png b/src/test/resources/assets/atlastest/images/double_plant_paeonia_bottom.png new file mode 100644 index 00000000..38a91d97 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_paeonia_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_paeonia_top.png b/src/test/resources/assets/atlastest/images/double_plant_paeonia_top.png new file mode 100644 index 00000000..c26fc050 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_paeonia_top.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_rose_bottom.png b/src/test/resources/assets/atlastest/images/double_plant_rose_bottom.png new file mode 100644 index 00000000..47411b3c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_rose_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_rose_top.png b/src/test/resources/assets/atlastest/images/double_plant_rose_top.png new file mode 100644 index 00000000..ea23dae7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_rose_top.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_sunflower_back.png b/src/test/resources/assets/atlastest/images/double_plant_sunflower_back.png new file mode 100644 index 00000000..5d2ccdbb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_sunflower_back.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_sunflower_bottom.png b/src/test/resources/assets/atlastest/images/double_plant_sunflower_bottom.png new file mode 100644 index 00000000..7ffcf41b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_sunflower_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_sunflower_front.png b/src/test/resources/assets/atlastest/images/double_plant_sunflower_front.png new file mode 100644 index 00000000..638f5db1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_sunflower_front.png differ diff --git a/src/test/resources/assets/atlastest/images/double_plant_sunflower_top.png b/src/test/resources/assets/atlastest/images/double_plant_sunflower_top.png new file mode 100644 index 00000000..bfbd929e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/double_plant_sunflower_top.png differ diff --git a/src/test/resources/assets/atlastest/images/dragon_egg.png b/src/test/resources/assets/atlastest/images/dragon_egg.png new file mode 100644 index 00000000..548f466b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dragon_egg.png differ diff --git a/src/test/resources/assets/atlastest/images/dried_kelp_side_a.png b/src/test/resources/assets/atlastest/images/dried_kelp_side_a.png new file mode 100644 index 00000000..6824f1ae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dried_kelp_side_a.png differ diff --git a/src/test/resources/assets/atlastest/images/dried_kelp_side_b.png b/src/test/resources/assets/atlastest/images/dried_kelp_side_b.png new file mode 100644 index 00000000..7948c17e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dried_kelp_side_b.png differ diff --git a/src/test/resources/assets/atlastest/images/dropper_front_horizontal.png b/src/test/resources/assets/atlastest/images/dropper_front_horizontal.png new file mode 100644 index 00000000..58365f78 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dropper_front_horizontal.png differ diff --git a/src/test/resources/assets/atlastest/images/dropper_front_vertical.png b/src/test/resources/assets/atlastest/images/dropper_front_vertical.png new file mode 100644 index 00000000..e1eb6b5d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/dropper_front_vertical.png differ diff --git a/src/test/resources/assets/atlastest/images/emerald_block.png b/src/test/resources/assets/atlastest/images/emerald_block.png new file mode 100644 index 00000000..80e9c00f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/emerald_block.png differ diff --git a/src/test/resources/assets/atlastest/images/emerald_ore.png b/src/test/resources/assets/atlastest/images/emerald_ore.png new file mode 100644 index 00000000..ae4c8354 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/emerald_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/enchanting_table_bottom.png b/src/test/resources/assets/atlastest/images/enchanting_table_bottom.png new file mode 100644 index 00000000..9ebf440e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/enchanting_table_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/enchanting_table_side.png b/src/test/resources/assets/atlastest/images/enchanting_table_side.png new file mode 100644 index 00000000..933841ce Binary files /dev/null and b/src/test/resources/assets/atlastest/images/enchanting_table_side.png differ diff --git a/src/test/resources/assets/atlastest/images/enchanting_table_top.png b/src/test/resources/assets/atlastest/images/enchanting_table_top.png new file mode 100644 index 00000000..8ac60da8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/enchanting_table_top.png differ diff --git a/src/test/resources/assets/atlastest/images/end_bricks.png b/src/test/resources/assets/atlastest/images/end_bricks.png new file mode 100644 index 00000000..295d3525 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/end_bricks.png differ diff --git a/src/test/resources/assets/atlastest/images/end_gateway.png b/src/test/resources/assets/atlastest/images/end_gateway.png new file mode 100644 index 00000000..beab3f4e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/end_gateway.png differ diff --git a/src/test/resources/assets/atlastest/images/end_portal.png b/src/test/resources/assets/atlastest/images/end_portal.png new file mode 100644 index 00000000..7b4dbaf5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/end_portal.png differ diff --git a/src/test/resources/assets/atlastest/images/end_rod.png b/src/test/resources/assets/atlastest/images/end_rod.png new file mode 100644 index 00000000..2d37a6c7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/end_rod.png differ diff --git a/src/test/resources/assets/atlastest/images/end_stone.png b/src/test/resources/assets/atlastest/images/end_stone.png new file mode 100644 index 00000000..4825c91a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/end_stone.png differ diff --git a/src/test/resources/assets/atlastest/images/ender_chest_front.png b/src/test/resources/assets/atlastest/images/ender_chest_front.png new file mode 100644 index 00000000..08776b31 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/ender_chest_front.png differ diff --git a/src/test/resources/assets/atlastest/images/ender_chest_side.png b/src/test/resources/assets/atlastest/images/ender_chest_side.png new file mode 100644 index 00000000..ad2476bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/ender_chest_side.png differ diff --git a/src/test/resources/assets/atlastest/images/endframe_eye.png b/src/test/resources/assets/atlastest/images/endframe_eye.png new file mode 100644 index 00000000..816191bf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/endframe_eye.png differ diff --git a/src/test/resources/assets/atlastest/images/endframe_side.png b/src/test/resources/assets/atlastest/images/endframe_side.png new file mode 100644 index 00000000..2a6f9a82 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/endframe_side.png differ diff --git a/src/test/resources/assets/atlastest/images/endframe_top.png b/src/test/resources/assets/atlastest/images/endframe_top.png new file mode 100644 index 00000000..1c22b141 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/endframe_top.png differ diff --git a/src/test/resources/assets/atlastest/images/farmland_dry.png b/src/test/resources/assets/atlastest/images/farmland_dry.png new file mode 100644 index 00000000..92fea441 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/farmland_dry.png differ diff --git a/src/test/resources/assets/atlastest/images/farmland_wet.png b/src/test/resources/assets/atlastest/images/farmland_wet.png new file mode 100644 index 00000000..3215fd52 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/farmland_wet.png differ diff --git a/src/test/resources/assets/atlastest/images/fire_0_placeholder.png b/src/test/resources/assets/atlastest/images/fire_0_placeholder.png new file mode 100644 index 00000000..120672fb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/fire_0_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/fire_1_placeholder.png b/src/test/resources/assets/atlastest/images/fire_1_placeholder.png new file mode 100644 index 00000000..d46f1333 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/fire_1_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/fletcher_table_side1.png b/src/test/resources/assets/atlastest/images/fletcher_table_side1.png new file mode 100644 index 00000000..070b660e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/fletcher_table_side1.png differ diff --git a/src/test/resources/assets/atlastest/images/fletcher_table_side2.png b/src/test/resources/assets/atlastest/images/fletcher_table_side2.png new file mode 100644 index 00000000..07302dfb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/fletcher_table_side2.png differ diff --git a/src/test/resources/assets/atlastest/images/fletcher_table_top.png b/src/test/resources/assets/atlastest/images/fletcher_table_top.png new file mode 100644 index 00000000..94f6369b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/fletcher_table_top.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_allium.png b/src/test/resources/assets/atlastest/images/flower_allium.png new file mode 100644 index 00000000..ada55ad1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_allium.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_blue_orchid.png b/src/test/resources/assets/atlastest/images/flower_blue_orchid.png new file mode 100644 index 00000000..1440ceb1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_blue_orchid.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_cornflower.png b/src/test/resources/assets/atlastest/images/flower_cornflower.png new file mode 100644 index 00000000..103c9c64 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_cornflower.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_dandelion.png b/src/test/resources/assets/atlastest/images/flower_dandelion.png new file mode 100644 index 00000000..17af3d89 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_dandelion.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_houstonia.png b/src/test/resources/assets/atlastest/images/flower_houstonia.png new file mode 100644 index 00000000..2b9f9489 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_houstonia.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_lily_of_the_valley.png b/src/test/resources/assets/atlastest/images/flower_lily_of_the_valley.png new file mode 100644 index 00000000..73231441 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_lily_of_the_valley.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_oxeye_daisy.png b/src/test/resources/assets/atlastest/images/flower_oxeye_daisy.png new file mode 100644 index 00000000..3e2977ec Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_oxeye_daisy.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_paeonia.png b/src/test/resources/assets/atlastest/images/flower_paeonia.png new file mode 100644 index 00000000..fc14bde4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_paeonia.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_pot.png b/src/test/resources/assets/atlastest/images/flower_pot.png new file mode 100644 index 00000000..15195a45 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_pot.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_rose.png b/src/test/resources/assets/atlastest/images/flower_rose.png new file mode 100644 index 00000000..d8b6663c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_rose.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_rose_blue.png b/src/test/resources/assets/atlastest/images/flower_rose_blue.png new file mode 100644 index 00000000..0dd1f412 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_rose_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_tulip_orange.png b/src/test/resources/assets/atlastest/images/flower_tulip_orange.png new file mode 100644 index 00000000..bd115399 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_tulip_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_tulip_pink.png b/src/test/resources/assets/atlastest/images/flower_tulip_pink.png new file mode 100644 index 00000000..e5a97e1a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_tulip_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_tulip_red.png b/src/test/resources/assets/atlastest/images/flower_tulip_red.png new file mode 100644 index 00000000..61228999 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_tulip_red.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_tulip_white.png b/src/test/resources/assets/atlastest/images/flower_tulip_white.png new file mode 100644 index 00000000..5fa342f8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_tulip_white.png differ diff --git a/src/test/resources/assets/atlastest/images/flower_wither_rose.png b/src/test/resources/assets/atlastest/images/flower_wither_rose.png new file mode 100644 index 00000000..a2d83da6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/flower_wither_rose.png differ diff --git a/src/test/resources/assets/atlastest/images/frosted_ice_0.png b/src/test/resources/assets/atlastest/images/frosted_ice_0.png new file mode 100644 index 00000000..1b0f46fa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/frosted_ice_0.png differ diff --git a/src/test/resources/assets/atlastest/images/frosted_ice_1.png b/src/test/resources/assets/atlastest/images/frosted_ice_1.png new file mode 100644 index 00000000..eba18b73 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/frosted_ice_1.png differ diff --git a/src/test/resources/assets/atlastest/images/frosted_ice_2.png b/src/test/resources/assets/atlastest/images/frosted_ice_2.png new file mode 100644 index 00000000..14ff4149 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/frosted_ice_2.png differ diff --git a/src/test/resources/assets/atlastest/images/frosted_ice_3.png b/src/test/resources/assets/atlastest/images/frosted_ice_3.png new file mode 100644 index 00000000..6cfe9af6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/frosted_ice_3.png differ diff --git a/src/test/resources/assets/atlastest/images/furnace_front_off.png b/src/test/resources/assets/atlastest/images/furnace_front_off.png new file mode 100644 index 00000000..a38d68ee Binary files /dev/null and b/src/test/resources/assets/atlastest/images/furnace_front_off.png differ diff --git a/src/test/resources/assets/atlastest/images/furnace_front_on.png b/src/test/resources/assets/atlastest/images/furnace_front_on.png new file mode 100644 index 00000000..7b1781bc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/furnace_front_on.png differ diff --git a/src/test/resources/assets/atlastest/images/furnace_side.png b/src/test/resources/assets/atlastest/images/furnace_side.png new file mode 100644 index 00000000..5f3ed87c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/furnace_side.png differ diff --git a/src/test/resources/assets/atlastest/images/furnace_top.png b/src/test/resources/assets/atlastest/images/furnace_top.png new file mode 100644 index 00000000..b23be46e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/furnace_top.png differ diff --git a/src/test/resources/assets/atlastest/images/glass.png b/src/test/resources/assets/atlastest/images/glass.png new file mode 100644 index 00000000..a1a88255 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_black.png b/src/test/resources/assets/atlastest/images/glass_black.png new file mode 100644 index 00000000..4e71d058 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_black.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_blue.png b/src/test/resources/assets/atlastest/images/glass_blue.png new file mode 100644 index 00000000..5ad448f9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_brown.png b/src/test/resources/assets/atlastest/images/glass_brown.png new file mode 100644 index 00000000..e762b321 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_cyan.png b/src/test/resources/assets/atlastest/images/glass_cyan.png new file mode 100644 index 00000000..be44de95 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_gray.png b/src/test/resources/assets/atlastest/images/glass_gray.png new file mode 100644 index 00000000..e838238b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_green.png b/src/test/resources/assets/atlastest/images/glass_green.png new file mode 100644 index 00000000..06927769 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_green.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_light_blue.png b/src/test/resources/assets/atlastest/images/glass_light_blue.png new file mode 100644 index 00000000..f2fcb296 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_lime.png b/src/test/resources/assets/atlastest/images/glass_lime.png new file mode 100644 index 00000000..b5c904e1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_magenta.png b/src/test/resources/assets/atlastest/images/glass_magenta.png new file mode 100644 index 00000000..4b7a6359 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_orange.png b/src/test/resources/assets/atlastest/images/glass_orange.png new file mode 100644 index 00000000..9bb8cea7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top.png b/src/test/resources/assets/atlastest/images/glass_pane_top.png new file mode 100644 index 00000000..fd1ecb84 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_black.png b/src/test/resources/assets/atlastest/images/glass_pane_top_black.png new file mode 100644 index 00000000..182e13bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_black.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_blue.png b/src/test/resources/assets/atlastest/images/glass_pane_top_blue.png new file mode 100644 index 00000000..e1b849c3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_brown.png b/src/test/resources/assets/atlastest/images/glass_pane_top_brown.png new file mode 100644 index 00000000..8b0dea89 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_cyan.png b/src/test/resources/assets/atlastest/images/glass_pane_top_cyan.png new file mode 100644 index 00000000..4c7e5d02 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_gray.png b/src/test/resources/assets/atlastest/images/glass_pane_top_gray.png new file mode 100644 index 00000000..f16658ea Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_green.png b/src/test/resources/assets/atlastest/images/glass_pane_top_green.png new file mode 100644 index 00000000..fdfcf2f5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_green.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_light_blue.png b/src/test/resources/assets/atlastest/images/glass_pane_top_light_blue.png new file mode 100644 index 00000000..1139525a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_lime.png b/src/test/resources/assets/atlastest/images/glass_pane_top_lime.png new file mode 100644 index 00000000..c771400b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_magenta.png b/src/test/resources/assets/atlastest/images/glass_pane_top_magenta.png new file mode 100644 index 00000000..ad2fc9b9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_orange.png b/src/test/resources/assets/atlastest/images/glass_pane_top_orange.png new file mode 100644 index 00000000..67d4b325 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_pink.png b/src/test/resources/assets/atlastest/images/glass_pane_top_pink.png new file mode 100644 index 00000000..9c43ed2f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_purple.png b/src/test/resources/assets/atlastest/images/glass_pane_top_purple.png new file mode 100644 index 00000000..1fe57970 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_red.png b/src/test/resources/assets/atlastest/images/glass_pane_top_red.png new file mode 100644 index 00000000..df919e69 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_red.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_silver.png b/src/test/resources/assets/atlastest/images/glass_pane_top_silver.png new file mode 100644 index 00000000..ffe1a042 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_white.png b/src/test/resources/assets/atlastest/images/glass_pane_top_white.png new file mode 100644 index 00000000..3993e379 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_white.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pane_top_yellow.png b/src/test/resources/assets/atlastest/images/glass_pane_top_yellow.png new file mode 100644 index 00000000..d3f4a92b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pane_top_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_pink.png b/src/test/resources/assets/atlastest/images/glass_pink.png new file mode 100644 index 00000000..393325a8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_purple.png b/src/test/resources/assets/atlastest/images/glass_purple.png new file mode 100644 index 00000000..deaa3539 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_red.png b/src/test/resources/assets/atlastest/images/glass_red.png new file mode 100644 index 00000000..10d79aca Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_red.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_silver.png b/src/test/resources/assets/atlastest/images/glass_silver.png new file mode 100644 index 00000000..72fa980b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_white.png b/src/test/resources/assets/atlastest/images/glass_white.png new file mode 100644 index 00000000..b8186822 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_white.png differ diff --git a/src/test/resources/assets/atlastest/images/glass_yellow.png b/src/test/resources/assets/atlastest/images/glass_yellow.png new file mode 100644 index 00000000..279baafa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glass_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_black.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_black.png new file mode 100644 index 00000000..18acd927 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_black.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_blue.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_blue.png new file mode 100644 index 00000000..8ced312b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_brown.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_brown.png new file mode 100644 index 00000000..17bd427b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_cyan.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_cyan.png new file mode 100644 index 00000000..5802ea90 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_gray.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_gray.png new file mode 100644 index 00000000..8eab4edc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_green.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_green.png new file mode 100644 index 00000000..c1f81deb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_green.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_light_blue.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_light_blue.png new file mode 100644 index 00000000..516fb7ed Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_lime.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_lime.png new file mode 100644 index 00000000..d5ad2b60 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_magenta.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_magenta.png new file mode 100644 index 00000000..b1f4729f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_orange.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_orange.png new file mode 100644 index 00000000..a6e1f56a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_pink.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_pink.png new file mode 100644 index 00000000..30a1975d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_purple.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_purple.png new file mode 100644 index 00000000..d0f9e68f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_red.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_red.png new file mode 100644 index 00000000..ee46623b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_red.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_silver.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_silver.png new file mode 100644 index 00000000..34787109 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_white.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_white.png new file mode 100644 index 00000000..0262ba6e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_white.png differ diff --git a/src/test/resources/assets/atlastest/images/glazed_terracotta_yellow.png b/src/test/resources/assets/atlastest/images/glazed_terracotta_yellow.png new file mode 100644 index 00000000..3d20abfa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glazed_terracotta_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/glowing_obsidian.png b/src/test/resources/assets/atlastest/images/glowing_obsidian.png new file mode 100644 index 00000000..88e1cfbc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glowing_obsidian.png differ diff --git a/src/test/resources/assets/atlastest/images/glowstone.png b/src/test/resources/assets/atlastest/images/glowstone.png new file mode 100644 index 00000000..3ff68e28 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/glowstone.png differ diff --git a/src/test/resources/assets/atlastest/images/gold_block.png b/src/test/resources/assets/atlastest/images/gold_block.png new file mode 100644 index 00000000..c74092ae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/gold_block.png differ diff --git a/src/test/resources/assets/atlastest/images/gold_ore.png b/src/test/resources/assets/atlastest/images/gold_ore.png new file mode 100644 index 00000000..9a965019 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/gold_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_carried.png b/src/test/resources/assets/atlastest/images/grass_carried.png new file mode 100644 index 00000000..619a1d6e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_path_side.png b/src/test/resources/assets/atlastest/images/grass_path_side.png new file mode 100644 index 00000000..c9de1352 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_path_side.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_path_top.png b/src/test/resources/assets/atlastest/images/grass_path_top.png new file mode 100644 index 00000000..6d5a74a3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_path_top.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_side_carried.png b/src/test/resources/assets/atlastest/images/grass_side_carried.png new file mode 100644 index 00000000..30663bf2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_side_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_side_snowed.png b/src/test/resources/assets/atlastest/images/grass_side_snowed.png new file mode 100644 index 00000000..0213e2b6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_side_snowed.png differ diff --git a/src/test/resources/assets/atlastest/images/grass_top.png b/src/test/resources/assets/atlastest/images/grass_top.png new file mode 100644 index 00000000..2ba30259 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/grass_top.png differ diff --git a/src/test/resources/assets/atlastest/images/gravel.png b/src/test/resources/assets/atlastest/images/gravel.png new file mode 100644 index 00000000..dd006d4a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/gravel.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay.png b/src/test/resources/assets/atlastest/images/hardened_clay.png new file mode 100644 index 00000000..edaa3431 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_black.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_black.png new file mode 100644 index 00000000..dffc0b6e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_black.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_blue.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_blue.png new file mode 100644 index 00000000..18576085 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_brown.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_brown.png new file mode 100644 index 00000000..1f15029c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_gray.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_gray.png new file mode 100644 index 00000000..eabe8de9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_green.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_green.png new file mode 100644 index 00000000..1c50ec48 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_green.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_light_blue.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_light_blue.png new file mode 100644 index 00000000..86598bb8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_silver.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_silver.png new file mode 100644 index 00000000..120adf7c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/hardened_clay_stained_white.png b/src/test/resources/assets/atlastest/images/hardened_clay_stained_white.png new file mode 100644 index 00000000..e4cb5b85 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hardened_clay_stained_white.png differ diff --git a/src/test/resources/assets/atlastest/images/hay_block_side.png b/src/test/resources/assets/atlastest/images/hay_block_side.png new file mode 100644 index 00000000..ed5d70ae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hay_block_side.png differ diff --git a/src/test/resources/assets/atlastest/images/hay_block_top.png b/src/test/resources/assets/atlastest/images/hay_block_top.png new file mode 100644 index 00000000..a33a073f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hay_block_top.png differ diff --git a/src/test/resources/assets/atlastest/images/hopper_inside.png b/src/test/resources/assets/atlastest/images/hopper_inside.png new file mode 100644 index 00000000..29e141ba Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hopper_inside.png differ diff --git a/src/test/resources/assets/atlastest/images/hopper_outside.png b/src/test/resources/assets/atlastest/images/hopper_outside.png new file mode 100644 index 00000000..4f9ae07f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hopper_outside.png differ diff --git a/src/test/resources/assets/atlastest/images/hopper_top.png b/src/test/resources/assets/atlastest/images/hopper_top.png new file mode 100644 index 00000000..6c2ad11c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/hopper_top.png differ diff --git a/src/test/resources/assets/atlastest/images/ice.png b/src/test/resources/assets/atlastest/images/ice.png new file mode 100644 index 00000000..9b086686 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/ice.png differ diff --git a/src/test/resources/assets/atlastest/images/ice_packed.png b/src/test/resources/assets/atlastest/images/ice_packed.png new file mode 100644 index 00000000..9aa4da62 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/ice_packed.png differ diff --git a/src/test/resources/assets/atlastest/images/iron_bars.png b/src/test/resources/assets/atlastest/images/iron_bars.png new file mode 100644 index 00000000..dce24199 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/iron_bars.png differ diff --git a/src/test/resources/assets/atlastest/images/iron_block.png b/src/test/resources/assets/atlastest/images/iron_block.png new file mode 100644 index 00000000..b4d5e534 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/iron_block.png differ diff --git a/src/test/resources/assets/atlastest/images/iron_ore.png b/src/test/resources/assets/atlastest/images/iron_ore.png new file mode 100644 index 00000000..64841777 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/iron_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/iron_trapdoor.png b/src/test/resources/assets/atlastest/images/iron_trapdoor.png new file mode 100644 index 00000000..764a97f9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/iron_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/itemframe_background.png b/src/test/resources/assets/atlastest/images/itemframe_background.png new file mode 100644 index 00000000..6ff4b19e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/itemframe_background.png differ diff --git a/src/test/resources/assets/atlastest/images/jigsaw_back.png b/src/test/resources/assets/atlastest/images/jigsaw_back.png new file mode 100644 index 00000000..04206c63 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jigsaw_back.png differ diff --git a/src/test/resources/assets/atlastest/images/jigsaw_front.png b/src/test/resources/assets/atlastest/images/jigsaw_front.png new file mode 100644 index 00000000..5ce7672f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jigsaw_front.png differ diff --git a/src/test/resources/assets/atlastest/images/jigsaw_side.png b/src/test/resources/assets/atlastest/images/jigsaw_side.png new file mode 100644 index 00000000..cf55f422 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jigsaw_side.png differ diff --git a/src/test/resources/assets/atlastest/images/jukebox_side.png b/src/test/resources/assets/atlastest/images/jukebox_side.png new file mode 100644 index 00000000..f3b947b5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jukebox_side.png differ diff --git a/src/test/resources/assets/atlastest/images/jukebox_top.png b/src/test/resources/assets/atlastest/images/jukebox_top.png new file mode 100644 index 00000000..7a2b38bd Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jukebox_top.png differ diff --git a/src/test/resources/assets/atlastest/images/jungle_trapdoor.png b/src/test/resources/assets/atlastest/images/jungle_trapdoor.png new file mode 100644 index 00000000..477a599a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/jungle_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/ladder.png b/src/test/resources/assets/atlastest/images/ladder.png new file mode 100644 index 00000000..031d97e4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/ladder.png differ diff --git a/src/test/resources/assets/atlastest/images/lapis_block.png b/src/test/resources/assets/atlastest/images/lapis_block.png new file mode 100644 index 00000000..f2fc093a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lapis_block.png differ diff --git a/src/test/resources/assets/atlastest/images/lapis_ore.png b/src/test/resources/assets/atlastest/images/lapis_ore.png new file mode 100644 index 00000000..24ffcba6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lapis_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/lava_placeholder.png b/src/test/resources/assets/atlastest/images/lava_placeholder.png new file mode 100644 index 00000000..8045e580 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lava_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_acacia_opaque.png b/src/test/resources/assets/atlastest/images/leaves_acacia_opaque.png new file mode 100644 index 00000000..3fbc0b5c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_acacia_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_big_oak_opaque.png b/src/test/resources/assets/atlastest/images/leaves_big_oak_opaque.png new file mode 100644 index 00000000..27b94efc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_big_oak_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_birch_opaque.png b/src/test/resources/assets/atlastest/images/leaves_birch_opaque.png new file mode 100644 index 00000000..882c0cb1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_birch_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_jungle_opaque.png b/src/test/resources/assets/atlastest/images/leaves_jungle_opaque.png new file mode 100644 index 00000000..c266412a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_jungle_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_oak_opaque.png b/src/test/resources/assets/atlastest/images/leaves_oak_opaque.png new file mode 100644 index 00000000..83fa37cf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_oak_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/leaves_spruce_opaque.png b/src/test/resources/assets/atlastest/images/leaves_spruce_opaque.png new file mode 100644 index 00000000..e91fbc32 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/leaves_spruce_opaque.png differ diff --git a/src/test/resources/assets/atlastest/images/lectern_base.png b/src/test/resources/assets/atlastest/images/lectern_base.png new file mode 100644 index 00000000..6f7a6aae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lectern_base.png differ diff --git a/src/test/resources/assets/atlastest/images/lectern_front.png b/src/test/resources/assets/atlastest/images/lectern_front.png new file mode 100644 index 00000000..6073d128 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lectern_front.png differ diff --git a/src/test/resources/assets/atlastest/images/lectern_sides.png b/src/test/resources/assets/atlastest/images/lectern_sides.png new file mode 100644 index 00000000..5e6955ca Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lectern_sides.png differ diff --git a/src/test/resources/assets/atlastest/images/lectern_top.png b/src/test/resources/assets/atlastest/images/lectern_top.png new file mode 100644 index 00000000..b81566aa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lectern_top.png differ diff --git a/src/test/resources/assets/atlastest/images/lever.png b/src/test/resources/assets/atlastest/images/lever.png new file mode 100644 index 00000000..d7c46572 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/lever.png differ diff --git a/src/test/resources/assets/atlastest/images/log_acacia.png b/src/test/resources/assets/atlastest/images/log_acacia.png new file mode 100644 index 00000000..d639d384 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_acacia.png differ diff --git a/src/test/resources/assets/atlastest/images/log_acacia_top.png b/src/test/resources/assets/atlastest/images/log_acacia_top.png new file mode 100644 index 00000000..b8e2c0b4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_acacia_top.png differ diff --git a/src/test/resources/assets/atlastest/images/log_big_oak.png b/src/test/resources/assets/atlastest/images/log_big_oak.png new file mode 100644 index 00000000..21fec5b4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_big_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/log_big_oak_top.png b/src/test/resources/assets/atlastest/images/log_big_oak_top.png new file mode 100644 index 00000000..1b08d524 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_big_oak_top.png differ diff --git a/src/test/resources/assets/atlastest/images/log_birch.png b/src/test/resources/assets/atlastest/images/log_birch.png new file mode 100644 index 00000000..f740fb4a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_birch.png differ diff --git a/src/test/resources/assets/atlastest/images/log_birch_top.png b/src/test/resources/assets/atlastest/images/log_birch_top.png new file mode 100644 index 00000000..da59cd77 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_birch_top.png differ diff --git a/src/test/resources/assets/atlastest/images/log_jungle.png b/src/test/resources/assets/atlastest/images/log_jungle.png new file mode 100644 index 00000000..5c5f94aa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_jungle.png differ diff --git a/src/test/resources/assets/atlastest/images/log_jungle_top.png b/src/test/resources/assets/atlastest/images/log_jungle_top.png new file mode 100644 index 00000000..84cc13f3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_jungle_top.png differ diff --git a/src/test/resources/assets/atlastest/images/log_oak.png b/src/test/resources/assets/atlastest/images/log_oak.png new file mode 100644 index 00000000..146b27dc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/log_oak_top.png b/src/test/resources/assets/atlastest/images/log_oak_top.png new file mode 100644 index 00000000..afa1c4f2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_oak_top.png differ diff --git a/src/test/resources/assets/atlastest/images/log_spruce.png b/src/test/resources/assets/atlastest/images/log_spruce.png new file mode 100644 index 00000000..031c8512 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_spruce.png differ diff --git a/src/test/resources/assets/atlastest/images/log_spruce_top.png b/src/test/resources/assets/atlastest/images/log_spruce_top.png new file mode 100644 index 00000000..e34b615f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/log_spruce_top.png differ diff --git a/src/test/resources/assets/atlastest/images/loom_bottom.png b/src/test/resources/assets/atlastest/images/loom_bottom.png new file mode 100644 index 00000000..7a372acf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/loom_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/loom_front.png b/src/test/resources/assets/atlastest/images/loom_front.png new file mode 100644 index 00000000..22eb6eae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/loom_front.png differ diff --git a/src/test/resources/assets/atlastest/images/loom_side.png b/src/test/resources/assets/atlastest/images/loom_side.png new file mode 100644 index 00000000..4123b6bd Binary files /dev/null and b/src/test/resources/assets/atlastest/images/loom_side.png differ diff --git a/src/test/resources/assets/atlastest/images/loom_top.png b/src/test/resources/assets/atlastest/images/loom_top.png new file mode 100644 index 00000000..3e4c843d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/loom_top.png differ diff --git a/src/test/resources/assets/atlastest/images/melon_side.png b/src/test/resources/assets/atlastest/images/melon_side.png new file mode 100644 index 00000000..4975cf64 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/melon_side.png differ diff --git a/src/test/resources/assets/atlastest/images/melon_stem_connected.png b/src/test/resources/assets/atlastest/images/melon_stem_connected.png new file mode 100644 index 00000000..e6fc012a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/melon_stem_connected.png differ diff --git a/src/test/resources/assets/atlastest/images/melon_stem_disconnected.png b/src/test/resources/assets/atlastest/images/melon_stem_disconnected.png new file mode 100644 index 00000000..fbfabb41 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/melon_stem_disconnected.png differ diff --git a/src/test/resources/assets/atlastest/images/melon_top.png b/src/test/resources/assets/atlastest/images/melon_top.png new file mode 100644 index 00000000..ccf0b2e0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/melon_top.png differ diff --git a/src/test/resources/assets/atlastest/images/missing_tile.png b/src/test/resources/assets/atlastest/images/missing_tile.png new file mode 100644 index 00000000..6e47a344 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/missing_tile.png differ diff --git a/src/test/resources/assets/atlastest/images/mob_spawner.png b/src/test/resources/assets/atlastest/images/mob_spawner.png new file mode 100644 index 00000000..a7809a75 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mob_spawner.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_block_inside.png b/src/test/resources/assets/atlastest/images/mushroom_block_inside.png new file mode 100644 index 00000000..974f143c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_block_inside.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_block_skin_brown.png b/src/test/resources/assets/atlastest/images/mushroom_block_skin_brown.png new file mode 100644 index 00000000..eede931b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_block_skin_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_block_skin_red.png b/src/test/resources/assets/atlastest/images/mushroom_block_skin_red.png new file mode 100644 index 00000000..50e7009f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_block_skin_red.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_block_skin_stem.png b/src/test/resources/assets/atlastest/images/mushroom_block_skin_stem.png new file mode 100644 index 00000000..4208a140 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_block_skin_stem.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_brown.png b/src/test/resources/assets/atlastest/images/mushroom_brown.png new file mode 100644 index 00000000..566b3eb3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/mushroom_red.png b/src/test/resources/assets/atlastest/images/mushroom_red.png new file mode 100644 index 00000000..23507649 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mushroom_red.png differ diff --git a/src/test/resources/assets/atlastest/images/mycelium_side.png b/src/test/resources/assets/atlastest/images/mycelium_side.png new file mode 100644 index 00000000..4396893b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mycelium_side.png differ diff --git a/src/test/resources/assets/atlastest/images/mycelium_top.png b/src/test/resources/assets/atlastest/images/mycelium_top.png new file mode 100644 index 00000000..0ccac059 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/mycelium_top.png differ diff --git a/src/test/resources/assets/atlastest/images/nether_brick.png b/src/test/resources/assets/atlastest/images/nether_brick.png new file mode 100644 index 00000000..8d41410c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/nether_brick.png differ diff --git a/src/test/resources/assets/atlastest/images/nether_wart_block.png b/src/test/resources/assets/atlastest/images/nether_wart_block.png new file mode 100644 index 00000000..27052eab Binary files /dev/null and b/src/test/resources/assets/atlastest/images/nether_wart_block.png differ diff --git a/src/test/resources/assets/atlastest/images/nether_wart_stage_0.png b/src/test/resources/assets/atlastest/images/nether_wart_stage_0.png new file mode 100644 index 00000000..0d5c8539 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/nether_wart_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/nether_wart_stage_1.png b/src/test/resources/assets/atlastest/images/nether_wart_stage_1.png new file mode 100644 index 00000000..9d13631e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/nether_wart_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/nether_wart_stage_2.png b/src/test/resources/assets/atlastest/images/nether_wart_stage_2.png new file mode 100644 index 00000000..4457dbd6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/nether_wart_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/netherrack.png b/src/test/resources/assets/atlastest/images/netherrack.png new file mode 100644 index 00000000..d324e2ff Binary files /dev/null and b/src/test/resources/assets/atlastest/images/netherrack.png differ diff --git a/src/test/resources/assets/atlastest/images/noteblock.png b/src/test/resources/assets/atlastest/images/noteblock.png new file mode 100644 index 00000000..f3b947b5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/noteblock.png differ diff --git a/src/test/resources/assets/atlastest/images/observer_back.png b/src/test/resources/assets/atlastest/images/observer_back.png new file mode 100644 index 00000000..a3c884a8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/observer_back.png differ diff --git a/src/test/resources/assets/atlastest/images/observer_back_lit.png b/src/test/resources/assets/atlastest/images/observer_back_lit.png new file mode 100644 index 00000000..927ae9d0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/observer_back_lit.png differ diff --git a/src/test/resources/assets/atlastest/images/observer_front.png b/src/test/resources/assets/atlastest/images/observer_front.png new file mode 100644 index 00000000..0db55e24 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/observer_front.png differ diff --git a/src/test/resources/assets/atlastest/images/observer_side.png b/src/test/resources/assets/atlastest/images/observer_side.png new file mode 100644 index 00000000..b8ac6168 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/observer_side.png differ diff --git a/src/test/resources/assets/atlastest/images/observer_top.png b/src/test/resources/assets/atlastest/images/observer_top.png new file mode 100644 index 00000000..da6d6d23 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/observer_top.png differ diff --git a/src/test/resources/assets/atlastest/images/obsidian.png b/src/test/resources/assets/atlastest/images/obsidian.png new file mode 100644 index 00000000..9ebf440e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/obsidian.png differ diff --git a/src/test/resources/assets/atlastest/images/piston_bottom.png b/src/test/resources/assets/atlastest/images/piston_bottom.png new file mode 100644 index 00000000..646debaf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/piston_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/piston_inner.png b/src/test/resources/assets/atlastest/images/piston_inner.png new file mode 100644 index 00000000..a7e81915 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/piston_inner.png differ diff --git a/src/test/resources/assets/atlastest/images/piston_side.png b/src/test/resources/assets/atlastest/images/piston_side.png new file mode 100644 index 00000000..08fa4866 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/piston_side.png differ diff --git a/src/test/resources/assets/atlastest/images/piston_top_normal.png b/src/test/resources/assets/atlastest/images/piston_top_normal.png new file mode 100644 index 00000000..21d79794 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/piston_top_normal.png differ diff --git a/src/test/resources/assets/atlastest/images/piston_top_sticky.png b/src/test/resources/assets/atlastest/images/piston_top_sticky.png new file mode 100644 index 00000000..29e724fc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/piston_top_sticky.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_acacia.png b/src/test/resources/assets/atlastest/images/planks_acacia.png new file mode 100644 index 00000000..583f9a19 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_acacia.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_big_oak.png b/src/test/resources/assets/atlastest/images/planks_big_oak.png new file mode 100644 index 00000000..43547e90 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_big_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_birch.png b/src/test/resources/assets/atlastest/images/planks_birch.png new file mode 100644 index 00000000..be886b2d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_birch.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_jungle.png b/src/test/resources/assets/atlastest/images/planks_jungle.png new file mode 100644 index 00000000..89e2beb9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_jungle.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_oak.png b/src/test/resources/assets/atlastest/images/planks_oak.png new file mode 100644 index 00000000..ebe737f4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/planks_spruce.png b/src/test/resources/assets/atlastest/images/planks_spruce.png new file mode 100644 index 00000000..09007751 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/planks_spruce.png differ diff --git a/src/test/resources/assets/atlastest/images/portal_placeholder.png b/src/test/resources/assets/atlastest/images/portal_placeholder.png new file mode 100644 index 00000000..831a122d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/portal_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/potatoes_stage_0.png b/src/test/resources/assets/atlastest/images/potatoes_stage_0.png new file mode 100644 index 00000000..705ae608 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/potatoes_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/potatoes_stage_1.png b/src/test/resources/assets/atlastest/images/potatoes_stage_1.png new file mode 100644 index 00000000..f5c64c26 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/potatoes_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/potatoes_stage_2.png b/src/test/resources/assets/atlastest/images/potatoes_stage_2.png new file mode 100644 index 00000000..c97baaa5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/potatoes_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/potatoes_stage_3.png b/src/test/resources/assets/atlastest/images/potatoes_stage_3.png new file mode 100644 index 00000000..b31884aa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/potatoes_stage_3.png differ diff --git a/src/test/resources/assets/atlastest/images/prismarine_bricks.png b/src/test/resources/assets/atlastest/images/prismarine_bricks.png new file mode 100644 index 00000000..b1a3da2d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/prismarine_bricks.png differ diff --git a/src/test/resources/assets/atlastest/images/prismarine_dark.png b/src/test/resources/assets/atlastest/images/prismarine_dark.png new file mode 100644 index 00000000..64f44ad9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/prismarine_dark.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_face_off.png b/src/test/resources/assets/atlastest/images/pumpkin_face_off.png new file mode 100644 index 00000000..ab5e2176 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_face_off.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_face_on.png b/src/test/resources/assets/atlastest/images/pumpkin_face_on.png new file mode 100644 index 00000000..c693f8be Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_face_on.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_side.png b/src/test/resources/assets/atlastest/images/pumpkin_side.png new file mode 100644 index 00000000..8c406b58 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_side.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_stem_connected.png b/src/test/resources/assets/atlastest/images/pumpkin_stem_connected.png new file mode 100644 index 00000000..5abb9b29 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_stem_connected.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_stem_disconnected.png b/src/test/resources/assets/atlastest/images/pumpkin_stem_disconnected.png new file mode 100644 index 00000000..1fab6fe5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_stem_disconnected.png differ diff --git a/src/test/resources/assets/atlastest/images/pumpkin_top.png b/src/test/resources/assets/atlastest/images/pumpkin_top.png new file mode 100644 index 00000000..eef7830e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/pumpkin_top.png differ diff --git a/src/test/resources/assets/atlastest/images/purpur_block.png b/src/test/resources/assets/atlastest/images/purpur_block.png new file mode 100644 index 00000000..cf07b74f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/purpur_block.png differ diff --git a/src/test/resources/assets/atlastest/images/purpur_pillar.png b/src/test/resources/assets/atlastest/images/purpur_pillar.png new file mode 100644 index 00000000..68081ddd Binary files /dev/null and b/src/test/resources/assets/atlastest/images/purpur_pillar.png differ diff --git a/src/test/resources/assets/atlastest/images/purpur_pillar_top.png b/src/test/resources/assets/atlastest/images/purpur_pillar_top.png new file mode 100644 index 00000000..33f6fac7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/purpur_pillar_top.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_bottom.png b/src/test/resources/assets/atlastest/images/quartz_block_bottom.png new file mode 100644 index 00000000..a63a1fa4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_chiseled.png b/src/test/resources/assets/atlastest/images/quartz_block_chiseled.png new file mode 100644 index 00000000..9daf66ac Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_chiseled.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_chiseled_top.png b/src/test/resources/assets/atlastest/images/quartz_block_chiseled_top.png new file mode 100644 index 00000000..edbceac2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_chiseled_top.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_lines.png b/src/test/resources/assets/atlastest/images/quartz_block_lines.png new file mode 100644 index 00000000..ea191f66 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_lines.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_lines_top.png b/src/test/resources/assets/atlastest/images/quartz_block_lines_top.png new file mode 100644 index 00000000..69773cfe Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_lines_top.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_side.png b/src/test/resources/assets/atlastest/images/quartz_block_side.png new file mode 100644 index 00000000..2e57cd55 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_side.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_block_top.png b/src/test/resources/assets/atlastest/images/quartz_block_top.png new file mode 100644 index 00000000..9c2f9c3b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_block_top.png differ diff --git a/src/test/resources/assets/atlastest/images/quartz_ore.png b/src/test/resources/assets/atlastest/images/quartz_ore.png new file mode 100644 index 00000000..9036e380 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/quartz_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_activator.png b/src/test/resources/assets/atlastest/images/rail_activator.png new file mode 100644 index 00000000..9222d578 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_activator.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_activator_powered.png b/src/test/resources/assets/atlastest/images/rail_activator_powered.png new file mode 100644 index 00000000..94983209 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_activator_powered.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_detector.png b/src/test/resources/assets/atlastest/images/rail_detector.png new file mode 100644 index 00000000..b2e88ab3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_detector.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_detector_powered.png b/src/test/resources/assets/atlastest/images/rail_detector_powered.png new file mode 100644 index 00000000..8017c07b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_detector_powered.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_golden.png b/src/test/resources/assets/atlastest/images/rail_golden.png new file mode 100644 index 00000000..d116ef0d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_golden.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_golden_powered.png b/src/test/resources/assets/atlastest/images/rail_golden_powered.png new file mode 100644 index 00000000..5b97f397 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_golden_powered.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_normal.png b/src/test/resources/assets/atlastest/images/rail_normal.png new file mode 100644 index 00000000..140330a0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_normal.png differ diff --git a/src/test/resources/assets/atlastest/images/rail_normal_turned.png b/src/test/resources/assets/atlastest/images/rail_normal_turned.png new file mode 100644 index 00000000..4b172fb3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/rail_normal_turned.png differ diff --git a/src/test/resources/assets/atlastest/images/reactor_core_stage_0.png b/src/test/resources/assets/atlastest/images/reactor_core_stage_0.png new file mode 100644 index 00000000..50ad37e9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/reactor_core_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/reactor_core_stage_1.png b/src/test/resources/assets/atlastest/images/reactor_core_stage_1.png new file mode 100644 index 00000000..09432590 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/reactor_core_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/reactor_core_stage_2.png b/src/test/resources/assets/atlastest/images/reactor_core_stage_2.png new file mode 100644 index 00000000..cb381b1b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/reactor_core_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/red_nether_brick.png b/src/test/resources/assets/atlastest/images/red_nether_brick.png new file mode 100644 index 00000000..03b45c10 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_nether_brick.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sand.png b/src/test/resources/assets/atlastest/images/red_sand.png new file mode 100644 index 00000000..6ad0c557 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sand.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sandstone_bottom.png b/src/test/resources/assets/atlastest/images/red_sandstone_bottom.png new file mode 100644 index 00000000..6441b4c6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sandstone_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sandstone_carved.png b/src/test/resources/assets/atlastest/images/red_sandstone_carved.png new file mode 100644 index 00000000..0a9c9620 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sandstone_carved.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sandstone_normal.png b/src/test/resources/assets/atlastest/images/red_sandstone_normal.png new file mode 100644 index 00000000..642970d9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sandstone_normal.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sandstone_smooth.png b/src/test/resources/assets/atlastest/images/red_sandstone_smooth.png new file mode 100644 index 00000000..be950f16 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sandstone_smooth.png differ diff --git a/src/test/resources/assets/atlastest/images/red_sandstone_top.png b/src/test/resources/assets/atlastest/images/red_sandstone_top.png new file mode 100644 index 00000000..adcd9532 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/red_sandstone_top.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_block.png b/src/test/resources/assets/atlastest/images/redstone_block.png new file mode 100644 index 00000000..0cc3ddf5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_block.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_dust_cross.png b/src/test/resources/assets/atlastest/images/redstone_dust_cross.png new file mode 100644 index 00000000..6004b700 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_dust_cross.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_dust_line.png b/src/test/resources/assets/atlastest/images/redstone_dust_line.png new file mode 100644 index 00000000..37e1e69d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_dust_line.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_lamp_off.png b/src/test/resources/assets/atlastest/images/redstone_lamp_off.png new file mode 100644 index 00000000..8b9f091b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_lamp_off.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_lamp_on.png b/src/test/resources/assets/atlastest/images/redstone_lamp_on.png new file mode 100644 index 00000000..24f04743 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_lamp_on.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_ore.png b/src/test/resources/assets/atlastest/images/redstone_ore.png new file mode 100644 index 00000000..c68c77c2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_ore.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_torch_off.png b/src/test/resources/assets/atlastest/images/redstone_torch_off.png new file mode 100644 index 00000000..245c152c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_torch_off.png differ diff --git a/src/test/resources/assets/atlastest/images/redstone_torch_on.png b/src/test/resources/assets/atlastest/images/redstone_torch_on.png new file mode 100644 index 00000000..0664b2ee Binary files /dev/null and b/src/test/resources/assets/atlastest/images/redstone_torch_on.png differ diff --git a/src/test/resources/assets/atlastest/images/repeater_off.png b/src/test/resources/assets/atlastest/images/repeater_off.png new file mode 100644 index 00000000..3128638e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeater_off.png differ diff --git a/src/test/resources/assets/atlastest/images/repeater_on.png b/src/test/resources/assets/atlastest/images/repeater_on.png new file mode 100644 index 00000000..e98c5eeb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeater_on.png differ diff --git a/src/test/resources/assets/atlastest/images/repeating_command_block_back_mipmap.png b/src/test/resources/assets/atlastest/images/repeating_command_block_back_mipmap.png new file mode 100644 index 00000000..4d49a8b2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeating_command_block_back_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/repeating_command_block_conditional_mipmap.png b/src/test/resources/assets/atlastest/images/repeating_command_block_conditional_mipmap.png new file mode 100644 index 00000000..dc1f2604 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeating_command_block_conditional_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/repeating_command_block_front_mipmap.png b/src/test/resources/assets/atlastest/images/repeating_command_block_front_mipmap.png new file mode 100644 index 00000000..584375a4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeating_command_block_front_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/repeating_command_block_side_mipmap.png b/src/test/resources/assets/atlastest/images/repeating_command_block_side_mipmap.png new file mode 100644 index 00000000..9d1280e7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/repeating_command_block_side_mipmap.png differ diff --git a/src/test/resources/assets/atlastest/images/sand.png b/src/test/resources/assets/atlastest/images/sand.png new file mode 100644 index 00000000..c93e4a31 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sand.png differ diff --git a/src/test/resources/assets/atlastest/images/sandstone_bottom.png b/src/test/resources/assets/atlastest/images/sandstone_bottom.png new file mode 100644 index 00000000..f7896f34 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sandstone_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/sandstone_carved.png b/src/test/resources/assets/atlastest/images/sandstone_carved.png new file mode 100644 index 00000000..c5c469e4 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sandstone_carved.png differ diff --git a/src/test/resources/assets/atlastest/images/sandstone_normal.png b/src/test/resources/assets/atlastest/images/sandstone_normal.png new file mode 100644 index 00000000..aa6a9225 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sandstone_normal.png differ diff --git a/src/test/resources/assets/atlastest/images/sandstone_smooth.png b/src/test/resources/assets/atlastest/images/sandstone_smooth.png new file mode 100644 index 00000000..6ae44430 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sandstone_smooth.png differ diff --git a/src/test/resources/assets/atlastest/images/sandstone_top.png b/src/test/resources/assets/atlastest/images/sandstone_top.png new file mode 100644 index 00000000..18a4dead Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sandstone_top.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_acacia.png b/src/test/resources/assets/atlastest/images/sapling_acacia.png new file mode 100644 index 00000000..c9766d5d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_acacia.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_birch.png b/src/test/resources/assets/atlastest/images/sapling_birch.png new file mode 100644 index 00000000..5c390250 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_birch.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_jungle.png b/src/test/resources/assets/atlastest/images/sapling_jungle.png new file mode 100644 index 00000000..8e54e1e9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_jungle.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_oak.png b/src/test/resources/assets/atlastest/images/sapling_oak.png new file mode 100644 index 00000000..adba4e20 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_roofed_oak.png b/src/test/resources/assets/atlastest/images/sapling_roofed_oak.png new file mode 100644 index 00000000..8e5d14c5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_roofed_oak.png differ diff --git a/src/test/resources/assets/atlastest/images/sapling_spruce.png b/src/test/resources/assets/atlastest/images/sapling_spruce.png new file mode 100644 index 00000000..5b8e46fa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sapling_spruce.png differ diff --git a/src/test/resources/assets/atlastest/images/sea_pickle.png b/src/test/resources/assets/atlastest/images/sea_pickle.png new file mode 100644 index 00000000..3c3b551c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sea_pickle.png differ diff --git a/src/test/resources/assets/atlastest/images/seagrass_carried.png b/src/test/resources/assets/atlastest/images/seagrass_carried.png new file mode 100644 index 00000000..e0b4f7bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/seagrass_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_black.png b/src/test/resources/assets/atlastest/images/shulker_top_black.png new file mode 100644 index 00000000..9c9d8a94 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_black.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_blue.png b/src/test/resources/assets/atlastest/images/shulker_top_blue.png new file mode 100644 index 00000000..ea9e23f3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_brown.png b/src/test/resources/assets/atlastest/images/shulker_top_brown.png new file mode 100644 index 00000000..522b4fb2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_cyan.png b/src/test/resources/assets/atlastest/images/shulker_top_cyan.png new file mode 100644 index 00000000..8c676d59 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_gray.png b/src/test/resources/assets/atlastest/images/shulker_top_gray.png new file mode 100644 index 00000000..09b9d710 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_green.png b/src/test/resources/assets/atlastest/images/shulker_top_green.png new file mode 100644 index 00000000..74cc2b2f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_green.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_light_blue.png b/src/test/resources/assets/atlastest/images/shulker_top_light_blue.png new file mode 100644 index 00000000..250b6a57 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_lime.png b/src/test/resources/assets/atlastest/images/shulker_top_lime.png new file mode 100644 index 00000000..410e36f2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_magenta.png b/src/test/resources/assets/atlastest/images/shulker_top_magenta.png new file mode 100644 index 00000000..8ae0fd5a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_orange.png b/src/test/resources/assets/atlastest/images/shulker_top_orange.png new file mode 100644 index 00000000..24baf9d1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_pink.png b/src/test/resources/assets/atlastest/images/shulker_top_pink.png new file mode 100644 index 00000000..395b765e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_purple.png b/src/test/resources/assets/atlastest/images/shulker_top_purple.png new file mode 100644 index 00000000..5901e7bb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_red.png b/src/test/resources/assets/atlastest/images/shulker_top_red.png new file mode 100644 index 00000000..533c74a6 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_red.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_silver.png b/src/test/resources/assets/atlastest/images/shulker_top_silver.png new file mode 100644 index 00000000..3b455ca3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_undyed.png b/src/test/resources/assets/atlastest/images/shulker_top_undyed.png new file mode 100644 index 00000000..771dbf2d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_undyed.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_white.png b/src/test/resources/assets/atlastest/images/shulker_top_white.png new file mode 100644 index 00000000..45748ad0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_white.png differ diff --git a/src/test/resources/assets/atlastest/images/shulker_top_yellow.png b/src/test/resources/assets/atlastest/images/shulker_top_yellow.png new file mode 100644 index 00000000..addd9a80 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/shulker_top_yellow.png differ diff --git a/src/test/resources/assets/atlastest/images/slime.png b/src/test/resources/assets/atlastest/images/slime.png new file mode 100644 index 00000000..93c40104 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/slime.png differ diff --git a/src/test/resources/assets/atlastest/images/smithing_table_bottom.png b/src/test/resources/assets/atlastest/images/smithing_table_bottom.png new file mode 100644 index 00000000..59833b5e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smithing_table_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/smithing_table_front.png b/src/test/resources/assets/atlastest/images/smithing_table_front.png new file mode 100644 index 00000000..55875ef5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smithing_table_front.png differ diff --git a/src/test/resources/assets/atlastest/images/smithing_table_side.png b/src/test/resources/assets/atlastest/images/smithing_table_side.png new file mode 100644 index 00000000..419312b1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smithing_table_side.png differ diff --git a/src/test/resources/assets/atlastest/images/smithing_table_top.png b/src/test/resources/assets/atlastest/images/smithing_table_top.png new file mode 100644 index 00000000..7099674f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smithing_table_top.png differ diff --git a/src/test/resources/assets/atlastest/images/smoker_front_off.png b/src/test/resources/assets/atlastest/images/smoker_front_off.png new file mode 100644 index 00000000..21a56330 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smoker_front_off.png differ diff --git a/src/test/resources/assets/atlastest/images/smoker_side.png b/src/test/resources/assets/atlastest/images/smoker_side.png new file mode 100644 index 00000000..656ecab7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smoker_side.png differ diff --git a/src/test/resources/assets/atlastest/images/smoker_top.png b/src/test/resources/assets/atlastest/images/smoker_top.png new file mode 100644 index 00000000..439b8c51 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/smoker_top.png differ diff --git a/src/test/resources/assets/atlastest/images/snow.png b/src/test/resources/assets/atlastest/images/snow.png new file mode 100644 index 00000000..2b83227f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/snow.png differ diff --git a/src/test/resources/assets/atlastest/images/soul_sand.png b/src/test/resources/assets/atlastest/images/soul_sand.png new file mode 100644 index 00000000..c6ea8cdf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/soul_sand.png differ diff --git a/src/test/resources/assets/atlastest/images/sponge.png b/src/test/resources/assets/atlastest/images/sponge.png new file mode 100644 index 00000000..27509829 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sponge.png differ diff --git a/src/test/resources/assets/atlastest/images/sponge_wet.png b/src/test/resources/assets/atlastest/images/sponge_wet.png new file mode 100644 index 00000000..918d0374 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sponge_wet.png differ diff --git a/src/test/resources/assets/atlastest/images/spruce_trapdoor.png b/src/test/resources/assets/atlastest/images/spruce_trapdoor.png new file mode 100644 index 00000000..34e2f707 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/spruce_trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/stone.png b/src/test/resources/assets/atlastest/images/stone.png new file mode 100644 index 00000000..618435eb Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_andesite.png b/src/test/resources/assets/atlastest/images/stone_andesite.png new file mode 100644 index 00000000..b418fe29 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_andesite.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_andesite_smooth.png b/src/test/resources/assets/atlastest/images/stone_andesite_smooth.png new file mode 100644 index 00000000..2e2a252b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_andesite_smooth.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_diorite.png b/src/test/resources/assets/atlastest/images/stone_diorite.png new file mode 100644 index 00000000..5eb65d0b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_diorite.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_diorite_smooth.png b/src/test/resources/assets/atlastest/images/stone_diorite_smooth.png new file mode 100644 index 00000000..334f80fc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_diorite_smooth.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_granite.png b/src/test/resources/assets/atlastest/images/stone_granite.png new file mode 100644 index 00000000..36c32e0e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_granite.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_granite_smooth.png b/src/test/resources/assets/atlastest/images/stone_granite_smooth.png new file mode 100644 index 00000000..336ff03a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_granite_smooth.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_slab_side.png b/src/test/resources/assets/atlastest/images/stone_slab_side.png new file mode 100644 index 00000000..2853dcaa Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_slab_side.png differ diff --git a/src/test/resources/assets/atlastest/images/stone_slab_top.png b/src/test/resources/assets/atlastest/images/stone_slab_top.png new file mode 100644 index 00000000..d927562d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stone_slab_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stonebrick.png b/src/test/resources/assets/atlastest/images/stonebrick.png new file mode 100644 index 00000000..3f2c93f8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonebrick.png differ diff --git a/src/test/resources/assets/atlastest/images/stonebrick_carved.png b/src/test/resources/assets/atlastest/images/stonebrick_carved.png new file mode 100644 index 00000000..a46fafcc Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonebrick_carved.png differ diff --git a/src/test/resources/assets/atlastest/images/stonebrick_cracked.png b/src/test/resources/assets/atlastest/images/stonebrick_cracked.png new file mode 100644 index 00000000..0568c327 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonebrick_cracked.png differ diff --git a/src/test/resources/assets/atlastest/images/stonebrick_mossy.png b/src/test/resources/assets/atlastest/images/stonebrick_mossy.png new file mode 100644 index 00000000..79b59eb5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonebrick_mossy.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter2_bottom.png b/src/test/resources/assets/atlastest/images/stonecutter2_bottom.png new file mode 100644 index 00000000..5affe264 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter2_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter2_side.png b/src/test/resources/assets/atlastest/images/stonecutter2_side.png new file mode 100644 index 00000000..2cdaafdf Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter2_side.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter2_top.png b/src/test/resources/assets/atlastest/images/stonecutter2_top.png new file mode 100644 index 00000000..62b7bcb8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter2_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter_bottom.png b/src/test/resources/assets/atlastest/images/stonecutter_bottom.png new file mode 100644 index 00000000..8a6c852c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter_other_side.png b/src/test/resources/assets/atlastest/images/stonecutter_other_side.png new file mode 100644 index 00000000..7b526175 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter_other_side.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter_side.png b/src/test/resources/assets/atlastest/images/stonecutter_side.png new file mode 100644 index 00000000..71cdc074 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter_side.png differ diff --git a/src/test/resources/assets/atlastest/images/stonecutter_top.png b/src/test/resources/assets/atlastest/images/stonecutter_top.png new file mode 100644 index 00000000..b9087ee8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stonecutter_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_acacia_log.png b/src/test/resources/assets/atlastest/images/stripped_acacia_log.png new file mode 100644 index 00000000..7d5eca21 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_acacia_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_acacia_log_top.png b/src/test/resources/assets/atlastest/images/stripped_acacia_log_top.png new file mode 100644 index 00000000..31c1b2ec Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_acacia_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_birch_log.png b/src/test/resources/assets/atlastest/images/stripped_birch_log.png new file mode 100644 index 00000000..3ea65023 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_birch_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_birch_log_top.png b/src/test/resources/assets/atlastest/images/stripped_birch_log_top.png new file mode 100644 index 00000000..2035f3d2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_birch_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_dark_oak_log.png b/src/test/resources/assets/atlastest/images/stripped_dark_oak_log.png new file mode 100644 index 00000000..6e37df48 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_dark_oak_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_dark_oak_log_top.png b/src/test/resources/assets/atlastest/images/stripped_dark_oak_log_top.png new file mode 100644 index 00000000..4393c96a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_dark_oak_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_jungle_log.png b/src/test/resources/assets/atlastest/images/stripped_jungle_log.png new file mode 100644 index 00000000..77ef6d75 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_jungle_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_jungle_log_top.png b/src/test/resources/assets/atlastest/images/stripped_jungle_log_top.png new file mode 100644 index 00000000..0248cf6f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_jungle_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_oak_log.png b/src/test/resources/assets/atlastest/images/stripped_oak_log.png new file mode 100644 index 00000000..26144e66 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_oak_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_oak_log_top.png b/src/test/resources/assets/atlastest/images/stripped_oak_log_top.png new file mode 100644 index 00000000..94178ee5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_oak_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_spruce_log.png b/src/test/resources/assets/atlastest/images/stripped_spruce_log.png new file mode 100644 index 00000000..e62abfda Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_spruce_log.png differ diff --git a/src/test/resources/assets/atlastest/images/stripped_spruce_log_top.png b/src/test/resources/assets/atlastest/images/stripped_spruce_log_top.png new file mode 100644 index 00000000..c2abfd81 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/stripped_spruce_log_top.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_air.png b/src/test/resources/assets/atlastest/images/structure_air.png new file mode 100644 index 00000000..ca8ab0b7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_air.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_block.png b/src/test/resources/assets/atlastest/images/structure_block.png new file mode 100644 index 00000000..d68507ec Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_block.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_block_corner.png b/src/test/resources/assets/atlastest/images/structure_block_corner.png new file mode 100644 index 00000000..02a28953 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_block_corner.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_block_data.png b/src/test/resources/assets/atlastest/images/structure_block_data.png new file mode 100644 index 00000000..3ba52545 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_block_data.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_block_load.png b/src/test/resources/assets/atlastest/images/structure_block_load.png new file mode 100644 index 00000000..19c16b6f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_block_load.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_block_save.png b/src/test/resources/assets/atlastest/images/structure_block_save.png new file mode 100644 index 00000000..8e4c291e Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_block_save.png differ diff --git a/src/test/resources/assets/atlastest/images/structure_void.png b/src/test/resources/assets/atlastest/images/structure_void.png new file mode 100644 index 00000000..a0cbb605 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/structure_void.png differ diff --git a/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage0.png b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage0.png new file mode 100644 index 00000000..6b5854da Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage0.png differ diff --git a/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage1.png b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage1.png new file mode 100644 index 00000000..216269ca Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage1.png differ diff --git a/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage2.png b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage2.png new file mode 100644 index 00000000..4254cc09 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage2.png differ diff --git a/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage3.png b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage3.png new file mode 100644 index 00000000..755ba234 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/sweet_berry_bush_stage3.png differ diff --git a/src/test/resources/assets/atlastest/images/tallgrass.png b/src/test/resources/assets/atlastest/images/tallgrass.png new file mode 100644 index 00000000..57b5a536 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/tallgrass.png differ diff --git a/src/test/resources/assets/atlastest/images/tnt_bottom.png b/src/test/resources/assets/atlastest/images/tnt_bottom.png new file mode 100644 index 00000000..e05663e2 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/tnt_bottom.png differ diff --git a/src/test/resources/assets/atlastest/images/tnt_side.png b/src/test/resources/assets/atlastest/images/tnt_side.png new file mode 100644 index 00000000..b3878700 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/tnt_side.png differ diff --git a/src/test/resources/assets/atlastest/images/tnt_top.png b/src/test/resources/assets/atlastest/images/tnt_top.png new file mode 100644 index 00000000..205821ab Binary files /dev/null and b/src/test/resources/assets/atlastest/images/tnt_top.png differ diff --git a/src/test/resources/assets/atlastest/images/torch_on.png b/src/test/resources/assets/atlastest/images/torch_on.png new file mode 100644 index 00000000..fb09b2ba Binary files /dev/null and b/src/test/resources/assets/atlastest/images/torch_on.png differ diff --git a/src/test/resources/assets/atlastest/images/trapdoor.png b/src/test/resources/assets/atlastest/images/trapdoor.png new file mode 100644 index 00000000..002c2bb8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/trapdoor.png differ diff --git a/src/test/resources/assets/atlastest/images/trapped_chest_front.png b/src/test/resources/assets/atlastest/images/trapped_chest_front.png new file mode 100644 index 00000000..ccf48b7f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/trapped_chest_front.png differ diff --git a/src/test/resources/assets/atlastest/images/trip_wire.png b/src/test/resources/assets/atlastest/images/trip_wire.png new file mode 100644 index 00000000..b6f2b8d0 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/trip_wire.png differ diff --git a/src/test/resources/assets/atlastest/images/trip_wire_source.png b/src/test/resources/assets/atlastest/images/trip_wire_source.png new file mode 100644 index 00000000..ba47b004 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/trip_wire_source.png differ diff --git a/src/test/resources/assets/atlastest/images/turtle_egg_not_cracked.png b/src/test/resources/assets/atlastest/images/turtle_egg_not_cracked.png new file mode 100644 index 00000000..ba920e9a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/turtle_egg_not_cracked.png differ diff --git a/src/test/resources/assets/atlastest/images/turtle_egg_slightly_cracked.png b/src/test/resources/assets/atlastest/images/turtle_egg_slightly_cracked.png new file mode 100644 index 00000000..55fb04e3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/turtle_egg_slightly_cracked.png differ diff --git a/src/test/resources/assets/atlastest/images/turtle_egg_very_cracked.png b/src/test/resources/assets/atlastest/images/turtle_egg_very_cracked.png new file mode 100644 index 00000000..b6a87688 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/turtle_egg_very_cracked.png differ diff --git a/src/test/resources/assets/atlastest/images/vine.png b/src/test/resources/assets/atlastest/images/vine.png new file mode 100644 index 00000000..854c10d8 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/vine.png differ diff --git a/src/test/resources/assets/atlastest/images/vine_carried.png b/src/test/resources/assets/atlastest/images/vine_carried.png new file mode 100644 index 00000000..2ce82e58 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/vine_carried.png differ diff --git a/src/test/resources/assets/atlastest/images/water_placeholder.png b/src/test/resources/assets/atlastest/images/water_placeholder.png new file mode 100644 index 00000000..03640288 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/water_placeholder.png differ diff --git a/src/test/resources/assets/atlastest/images/waterlily.png b/src/test/resources/assets/atlastest/images/waterlily.png new file mode 100644 index 00000000..e7eccb84 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/waterlily.png differ diff --git a/src/test/resources/assets/atlastest/images/web.png b/src/test/resources/assets/atlastest/images/web.png new file mode 100644 index 00000000..6bd63d4b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/web.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_0.png b/src/test/resources/assets/atlastest/images/wheat_stage_0.png new file mode 100644 index 00000000..915bd9ce Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_0.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_1.png b/src/test/resources/assets/atlastest/images/wheat_stage_1.png new file mode 100644 index 00000000..b659ae0c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_1.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_2.png b/src/test/resources/assets/atlastest/images/wheat_stage_2.png new file mode 100644 index 00000000..32ad512a Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_2.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_3.png b/src/test/resources/assets/atlastest/images/wheat_stage_3.png new file mode 100644 index 00000000..14b76ff7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_3.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_4.png b/src/test/resources/assets/atlastest/images/wheat_stage_4.png new file mode 100644 index 00000000..41e36fae Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_4.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_5.png b/src/test/resources/assets/atlastest/images/wheat_stage_5.png new file mode 100644 index 00000000..56070f44 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_5.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_6.png b/src/test/resources/assets/atlastest/images/wheat_stage_6.png new file mode 100644 index 00000000..b0a255c9 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_6.png differ diff --git a/src/test/resources/assets/atlastest/images/wheat_stage_7.png b/src/test/resources/assets/atlastest/images/wheat_stage_7.png new file mode 100644 index 00000000..d0b6c6be Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wheat_stage_7.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_black.png b/src/test/resources/assets/atlastest/images/wool_colored_black.png new file mode 100644 index 00000000..4ee0ded7 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_black.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_blue.png b/src/test/resources/assets/atlastest/images/wool_colored_blue.png new file mode 100644 index 00000000..4f8e3447 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_brown.png b/src/test/resources/assets/atlastest/images/wool_colored_brown.png new file mode 100644 index 00000000..83b19f20 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_brown.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_cyan.png b/src/test/resources/assets/atlastest/images/wool_colored_cyan.png new file mode 100644 index 00000000..3eb475d3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_cyan.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_gray.png b/src/test/resources/assets/atlastest/images/wool_colored_gray.png new file mode 100644 index 00000000..89a4398f Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_gray.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_green.png b/src/test/resources/assets/atlastest/images/wool_colored_green.png new file mode 100644 index 00000000..5475c8d5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_green.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_light_blue.png b/src/test/resources/assets/atlastest/images/wool_colored_light_blue.png new file mode 100644 index 00000000..2ee487a5 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_light_blue.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_lime.png b/src/test/resources/assets/atlastest/images/wool_colored_lime.png new file mode 100644 index 00000000..dc3da54b Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_lime.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_magenta.png b/src/test/resources/assets/atlastest/images/wool_colored_magenta.png new file mode 100644 index 00000000..abbf348d Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_magenta.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_orange.png b/src/test/resources/assets/atlastest/images/wool_colored_orange.png new file mode 100644 index 00000000..bd125395 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_orange.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_pink.png b/src/test/resources/assets/atlastest/images/wool_colored_pink.png new file mode 100644 index 00000000..a2204f6c Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_pink.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_purple.png b/src/test/resources/assets/atlastest/images/wool_colored_purple.png new file mode 100644 index 00000000..448076db Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_purple.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_red.png b/src/test/resources/assets/atlastest/images/wool_colored_red.png new file mode 100644 index 00000000..8e9d27d3 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_red.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_silver.png b/src/test/resources/assets/atlastest/images/wool_colored_silver.png new file mode 100644 index 00000000..9d6fea83 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_silver.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_white.png b/src/test/resources/assets/atlastest/images/wool_colored_white.png new file mode 100644 index 00000000..23ba5a35 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_white.png differ diff --git a/src/test/resources/assets/atlastest/images/wool_colored_yellow.png b/src/test/resources/assets/atlastest/images/wool_colored_yellow.png new file mode 100644 index 00000000..c3735de1 Binary files /dev/null and b/src/test/resources/assets/atlastest/images/wool_colored_yellow.png differ diff --git a/src/test/resources/assets/awacoder/about.png b/src/test/resources/assets/awacoder/about.png new file mode 100644 index 00000000..ebf5219b Binary files /dev/null and b/src/test/resources/assets/awacoder/about.png differ diff --git a/src/test/resources/assets/clock/fonts/CascadiaCode.ttf b/src/test/resources/assets/clock/fonts/CascadiaCode.ttf new file mode 100644 index 00000000..bda8bb9f Binary files /dev/null and b/src/test/resources/assets/clock/fonts/CascadiaCode.ttf differ diff --git a/src/test/resources/assets/clock/images/background.png b/src/test/resources/assets/clock/images/background.png new file mode 100644 index 00000000..8903f320 Binary files /dev/null and b/src/test/resources/assets/clock/images/background.png differ diff --git a/src/test/resources/assets/clock/images/battery.png b/src/test/resources/assets/clock/images/battery.png new file mode 100755 index 00000000..eeb4f537 Binary files /dev/null and b/src/test/resources/assets/clock/images/battery.png differ diff --git a/src/test/resources/assets/clock/images/user_icon.jpg b/src/test/resources/assets/clock/images/user_icon.jpg new file mode 100644 index 00000000..ad5a3214 Binary files /dev/null and b/src/test/resources/assets/clock/images/user_icon.jpg differ diff --git a/src/test/resources/assets/clock/images2/background.pdn b/src/test/resources/assets/clock/images2/background.pdn new file mode 100644 index 00000000..ccad3eb8 Binary files /dev/null and b/src/test/resources/assets/clock/images2/background.pdn differ diff --git a/src/test/resources/assets/clock/images2/background.png b/src/test/resources/assets/clock/images2/background.png new file mode 100644 index 00000000..0ea5c4dc Binary files /dev/null and b/src/test/resources/assets/clock/images2/background.png differ diff --git a/src/test/resources/assets/clock/images2/icon.jpg b/src/test/resources/assets/clock/images2/icon.jpg new file mode 100644 index 00000000..5ecdc580 Binary files /dev/null and b/src/test/resources/assets/clock/images2/icon.jpg differ diff --git a/src/test/resources/assets/rendertest/shaders/shader.frag b/src/test/resources/assets/rendertest/shaders/shader.frag new file mode 100644 index 00000000..00d4d4df --- /dev/null +++ b/src/test/resources/assets/rendertest/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 330 + +in vec3 ocolor; + +out vec4 out_color; + +void main() { + out_color = vec4(ocolor, 1.0); +} diff --git a/src/test/resources/assets/rendertest/shaders/shader.vert b/src/test/resources/assets/rendertest/shaders/shader.vert new file mode 100644 index 00000000..1c63c7d4 --- /dev/null +++ b/src/test/resources/assets/rendertest/shaders/shader.vert @@ -0,0 +1,11 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec3 color; + +out vec3 ocolor; + +void main() { + gl_Position = vec4(pos, 1.0); + ocolor = color; +} diff --git a/src/test/resources/assets/synthtest/lucky_star.mid b/src/test/resources/assets/synthtest/lucky_star.mid new file mode 100644 index 00000000..dc02a7d1 Binary files /dev/null and b/src/test/resources/assets/synthtest/lucky_star.mid differ diff --git a/src/test/resources/assets/texturetest/images/test.jpg b/src/test/resources/assets/texturetest/images/test.jpg new file mode 100644 index 00000000..b4d0fceb Binary files /dev/null and b/src/test/resources/assets/texturetest/images/test.jpg differ diff --git a/src/test/resources/assets/texturetest/shaders/shader.frag b/src/test/resources/assets/texturetest/shaders/shader.frag new file mode 100644 index 00000000..25f14adf --- /dev/null +++ b/src/test/resources/assets/texturetest/shaders/shader.frag @@ -0,0 +1,12 @@ +#version 330 + +in vec3 ocolor; +in vec2 ouv; + +uniform sampler2D textureSampler; + +out vec4 out_color; + +void main() { + out_color = vec4(ocolor, 1.0) * texture(textureSampler, ouv); +} diff --git a/src/test/resources/assets/texturetest/shaders/shader.vert b/src/test/resources/assets/texturetest/shaders/shader.vert new file mode 100644 index 00000000..b7618f19 --- /dev/null +++ b/src/test/resources/assets/texturetest/shaders/shader.vert @@ -0,0 +1,14 @@ +#version 330 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec3 color; +layout (location = 2) in vec2 uv; + +out vec3 ocolor; +out vec2 ouv; + +void main() { + gl_Position = vec4(pos, 1.0); + ocolor = color; + ouv = uv; +}