Initial Commit (from Lod Mod 1.16.5 11-10-2021)

This commit is contained in:
James Seibel
2021-11-10 22:02:38 -06:00
commit a5f98b02d4
96 changed files with 15275 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# Disable autocrlf on generated files, they always generate with LF
# Add any extra files or paths here to make git stop saying they
# are changed when only line endings change.
src/generated/**/.cache/cache text eol=lf
src/generated/**/*.json text eol=lf
+26
View File
@@ -0,0 +1,26 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
# idea
out
*.ipr
*.iws
*.iml
.idea
# gradle
build
.gradle
# other
eclipse
run
# Files from Forge MDK
logs
forge*changelog.txt
Binary file not shown.
+621
View File
@@ -0,0 +1,621 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
+68
View File
@@ -0,0 +1,68 @@
# Distant Horizons
This mod adds a Level Of Detail (LOD) system to Minecraft.\
This implementation renders simplified chunks outside the normal render distance\
allowing for an increased view distance without harming performance.
Or in other words: this mod lets you see farther without turning your game into a slide show.\
If you want to see a quick demo, check out a video covering the mod here:
<a href="https://www.youtube.com/watch?v=H2tnvEVbO1c" target="_blank">![Minecraft Level Of Detail (LOD) mod - Alpha 1.4](https://i.ytimg.com/vi_webp/H2tnvEVbO1c/mqdefault.webp)</a>
Forge version: 1.16.5-36.1.0
Notes:\
This version has been confirmed to work in Eclipse and Retail Minecraft.\
(Retail running forge version 1.16.5-36.1.0)
## source code installation
See the Forge Documentation online for more detailed instructions:\
http://mcforge.readthedocs.io/en/latest/gettingstarted/
1. Create a system variable called "JAVA_MC_HOME" with the location of the JDK 1.8.0_251 (This is needed for gradle to work correctly)
2. replace JAVA_HOME with JAVA_MC_HOME in gradle.bat
3. open a command line in the project folder
**If using Ecplise:**
1. run the command: `./gradlew geneclipseruns`
2. run the command: `./gradlew eclipse`
3. Make sure eclipse has the JDK 1.8.0_251 installed. (This is needed so that eclipse can run minecraft)
4. Import the project into eclipse
**If using IntelliJ:**
1. open IDEA and import the build.gradle
2. run the command: `./gradlew genIntellijRuns`
3. refresh the Gradle project in IDEA if required
## Compiling
1. open a command line in the project folder
2. run the command: `./gradlew build`
3. the compiled jar file will be in the folder `build\libs`
## Other commands
`./gradlew --refresh-dependencies` to refresh local dependencies.
`./gradlew clean` to reset everything (this does not affect your code) and then start the process again.
## Note to self
The Minecraft source code is NOT added to your workspace in an editable way. Minecraft is treated like a normal Library. Sources are there for documentation and research purposes only.
Source code uses Mojang mappings.
The source code can be 'created' with the `./eclipse` command and can be found in the following path:\
`minecraft-lod-mod\build\fg_cache\mcp\ VERSION \joined\ RANDOM_STRING \patch\output.jar`
## Open Source Acknowledgements
XZ for Java (data compression)\
https://tukaani.org/xz/java.html
+390
View File
@@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="21">
<profile kind="CodeFormatterProfile" name="Eclipse (James' Edit)" version="21">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="99"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="1200"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
@@ -0,0 +1,50 @@
<code_scheme name="Eclipse (James' Edit)" version="173">
<option name="RIGHT_MARGIN" value="1200" />
<JavaCodeStyleSettings>
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
<option name="JD_ADD_BLANK_AFTER_DESCRIPTION" value="false" />
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<option name="JD_KEEP_INVALID_TAGS" value="false" />
<option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
<option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
<option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
<option name="JD_KEEP_EMPTY_RETURN" value="false" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="10" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="10" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="10" />
<option name="BLANK_LINES_BEFORE_PACKAGE" value="1" />
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="INDENT_CASE_FROM_SWITCH" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="5" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

+234
View File
@@ -0,0 +1,234 @@
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net' }
mavenCentral()
// potential replacement in case of problems:
// https://dist.creeper.host/Sponge/maven
maven { url = 'https://repo.spongepowered.org/maven/' }
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT'
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
}
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'org.spongepowered.mixin'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
apply plugin: 'com.github.johnrengelman.shadow'
version = 'a1.5.2'
group = 'com.seibel.lod'
archivesBaseName = 'Distant-Horizons_1.16.5'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
minecraft {
// The mappings can be changed at any time, and must be in the following format.
// snapshot_YYYYMMDD Snapshot are built nightly.
// stable_# Stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: 'official', version: '1.16.5'
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
workingDirectory project.file('run')
arg "-mixin.config=lod.mixins.json"
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
examplemod {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
arg "-mixin.config=lod.mixins.json"
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
examplemod {
source sourceSets.main
}
}
}
data {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', 'lod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
mods {
examplemod {
source sourceSets.main
}
}
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
// this is required so that we can use
// jitpack in the dependencies section below
repositories {
mavenCentral()
// used to download and compile dependencies from git repos
maven { url 'https://jitpack.io' }
}
configurations {
shadowMe
compileOnly.extendsFrom(embed)
}
dependencies {
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.16.5-36.1.0'
compile 'org.tukaani:xz:1.9'
shadowMe 'org.tukaani:xz:1.9'
compile 'org.apache.commons:commons-compress:1.21'
shadowMe 'org.apache.commons:commons-compress:1.21'
// these were added to hopefully allow for cloning
// configuredFeatures to allow for safe
// multi threaded feature generation. Sadly I couldn't find
// a way to duplicate lambda functions (which features use)
// so for now I'm not sure what to do.
//implementation 'io.github.kostaskougios:cloning:1.10.3'
//
//implementation ('com.esotericsoftware:kryo:5.1.1') {
// exclude group: "org.objenesis"
//}
//implementation 'org.objenesis:objenesis:3.2'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
// Real examples
// compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
// compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
// The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
// provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// These dependencies get remapped to your current MCP mappings
// deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// For more info...
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
shadowJar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
configurations = [project.configurations.getByName("shadowMe")]
relocate 'org.tukaani', 'shaded.tukaani'
relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress'
classifier = ''
}
reobf {
shadowJar {
dependsOn tasks.createMcpToSrg
mappings = tasks.createMcpToSrg.outputs.files.singleFile
}
}
artifacts {
archives tasks.shadowJar
}
// Example for how to get properties into the manifest for reading by the runtime..
jar {
manifest {
attributes([
"Specification-Title": "LOD",
"Specification-Version": "1", // We are version 1 of ourselves
"Implementation-Title": project.name,
"Implementation-Version": "1",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs": "lod.mixins.json",
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
//publish.dependsOn('reobfJar')
publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file:///${project.projectDir}/mcmodsrepo"
}
}
}
mixin {
add sourceSets.main, "lod.refmap.json"
}
+4
View File
@@ -0,0 +1,4 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
Vendored
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+84
View File
@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_MC_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_MC_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_MC_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_MC_HOME=%JAVA_MC_HOME:"=%
set JAVA_EXE=%JAVA_MC_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_MC_HOME is set to an invalid directory: %JAVA_MC_HOME%
echo.
echo Please set the JAVA_MC_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
Binary file not shown.
+82
View File
@@ -0,0 +1,82 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
/**
* Initialize and setup the Mod.
* <br>
* If you are looking for the real start of the mod
* check out the ClientProxy.
*
* @author James Seibel
* @version 7-3-2021
*/
@Mod(ModInfo.ID)
public class LodMain
{
public static LodMain instance;
public static ClientProxy client_proxy;
private void init(final FMLCommonSetupEvent event)
{
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.CLIENT_SPEC);
}
public LodMain()
{
// Register the methods
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientStart);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
}
private void onClientStart(final FMLClientSetupEvent event)
{
client_proxy = new ClientProxy();
MinecraftForge.EVENT_BUS.register(client_proxy);
}
@SubscribeEvent
public void onServerStarting(FMLServerStartingEvent event)
{
// this is called when the server starts
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod;
/**
* This file is similar to mcmod.info
* @author James Seibel
* @version 10-23-2021
*/
public final class ModInfo
{
public static final String ID = "lod";
public static final String NAME = "DistantHorizons";
/** Human readable version of MOD_NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String API = "LodAPI";
public static final String VERSION = "a1.5.2";
}
@@ -0,0 +1,975 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.enums.GpuUploadMethod;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.PosToRenderContainer;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.LightType;
/**
* This object is used to create NearFarBuffer objects.
* @author James Seibel
* @version 10-23-2021
*/
public class LodBufferBuilder
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** The thread used to generate new LODs off the main thread. */
public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/** The threads used to generate buffers. */
public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfBufferBuilderThreads.get(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build());
/**
* When uploading to a buffer that is too small,
* recreate it this many times bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5;
/**
* When buffers are first created they are allocated to this size (in Bytes).
* This size will be too small, more than likely. The buffers will be expanded
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = 1024;
public static int skyLightPlayer = 15;
/**
* How many buffers there are for the given region. <Br>
* This is done because some regions may require more memory than
* can be directly allocated, so we split the regions into smaller sections. <Br>
* This keeps track of those sections.
*/
public volatile int[][] numberOfBuffersPerRegion;
/** Stores the vertices when building the VBOs */
public volatile BufferBuilder[][][] buildableBuffers;
/** The OpenGL IDs of the storage buffers used by the buildableVbos */
public int[][][] buildableStorageBufferIds;
/** The OpenGL IDs of the storage buffers used by the drawableVbos */
public int[][][] drawableStorageBufferIds;
/** Used when building new VBOs */
public volatile VertexBuffer[][][] buildableVbos;
/** VBOs that are sent over to the LodNodeRenderer */
public volatile VertexBuffer[][][] drawableVbos;
/**
* if this is true the LOD buffers are currently being
* regenerated.
*/
public boolean generatingBuffers = false;
/**
* if this is true new LOD buffers have been generated
* and are waiting to be swapped with the drawable buffers
*/
private boolean switchVbos = false;
/** Size of the buffer builders in bytes last time we created them */
public int previousBufferSize = 0;
/** Width of the dimension in regions last time we created the buffers */
public int previousRegionWidth = 0;
/** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */
private final ReentrantLock bufferLock = new ReentrantLock();
private volatile Box[][] boxCache;
private volatile PosToRenderContainer[][] setsToRender;
private volatile RegionPos center;
/**
* This is the ChunkPos the player was at the last time the buffers were built.
* IE the center of the buffers last time they were built
*/
private volatile ChunkPos drawableCenterChunkPos = new ChunkPos(0, 0);
private volatile ChunkPos buildableCenterChunkPos = new ChunkPos(0, 0);
public LodBufferBuilder()
{
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawable near and far buffers.
* <br>
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
BlockPos playerBlockPos, boolean fullRegen)
{
// only allow one generation process to happen at a time
if (generatingBuffers)
return;
if (buildableBuffers == null)
// setupBuffers hasn't been called yet
return;
generatingBuffers = true;
Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerBlockPos, fullRegen));
mainGenThread.execute(thread);
}
// this was pulled out as a separate method so that it could be
// more easily edited by hot swapping. Because, As far as James is aware
// you can't hot swap lambda expressions.
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim,
BlockPos playerBlockPos, boolean fullRegen)
{
bufferLock.lock();
try
{
// round the player's block position down to the nearest chunk BlockPos
ChunkPos playerChunkPos = new ChunkPos(playerBlockPos);
BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition();
//long startTime = System.currentTimeMillis();
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
startBuffers(fullRegen, lodDim);
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (setsToRender.length != lodDim.getWidth())
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache == null)
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache.length != lodDim.getWidth())
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
buildableCenterChunkPos = playerChunkPos;
//================================//
// create the nodeToRenderThreads //
//================================//
ClientWorld world = mc.getClientWorld();
skyLightPlayer = world.getBrightness(LightType.SKY, playerBlockPos);
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterRegionPosX() - lodDim.getWidth() / 2,
zRegion + lodDim.getCenterRegionPosZ() - lodDim.getWidth() / 2);
// local position in the vbo and bufferBuilder arrays
BufferBuilder[] currentBuffers = buildableBuffers[xRegion][zRegion];
LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z);
if (region == null)
continue;
// make sure the buffers weren't
// changed while we were running this method
if (currentBuffers == null || !currentBuffers[0].building())
return;
byte minDetail = region.getMinDetailLevel();
final int xR = xRegion;
final int zR = zRegion;
//we create the Callable to use for the buffer builder creation
Callable<Boolean> dataToRenderThread = () ->
{
//Variable initialization
byte detailLevel;
int posX;
int posZ;
int xAdj;
int zAdj;
int bufferIndex;
boolean posNotInPlayerChunk;
boolean adjPosInPlayerChunk;
Box box = ThreadMapUtil.getBox();
boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray();
// determine how many LODs we can stack vertically
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0);
//we get or create the map that will contain the adj data
Map<Direction, long[]> adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData);
//previous setToRender cache
if (setsToRender[xR][zR] == null)
setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z);
//We ask the lod dimension which block we have to render given the player position
PosToRenderContainer posToRender = setsToRender[xR][zR];
posToRender.clear(minDetail, regionPos.x, regionPos.z);
lodDim.getPosToRender(
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
// keep a local version, so we don't have to worry about indexOutOfBounds Exceptions
// if it changes in the LodRenderer while we are working here
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
for (int index = 0; index < posToRender.getNumberOfPos(); index++)
{
bufferIndex = index % currentBuffers.length;
detailLevel = posToRender.getNthDetailLevel(index);
posX = posToRender.getNthPosX(index);
posZ = posToRender.getNthPosZ(index);
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
//We don't want to render this fake block if
//The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
//
//The block is in the player chunk or in a chunk adjacent to the player
if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance))
{
continue;
}
//we check if the block to render is not in player chunk
posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0);
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass
// to avoid having a "darker border" underground
Arrays.fill(adjShadeDisabled, false);
//We check every adj block in each direction
for (Direction direction : Box.ADJ_DIRECTIONS)
{
xAdj = posX + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
zAdj = posZ + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
long data;
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.x;
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.z;
adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0);
//If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is not
// to always have a wall underwater
if(posToRender.contains(detailLevel, xAdj, zAdj)
&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance)
&& !(posNotInPlayerChunk && adjPosInPlayerChunk))
{
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
{
data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = false;
adjData.get(direction)[verticalIndex] = data;
}
}
else
{
//Otherwise, we check if this position is
data = lodDim.getSingleData(detailLevel, xAdj, zAdj);
adjData.get(direction)[0] = DataPointUtil.EMPTY_DATA;
if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk))
&& !DataPointUtil.isVoid(data))
{
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = DataPointUtil.getAlpha(data) < 255;
}
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non existing block
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
//we get the above block as adj UP
if (verticalIndex > 0)
adjData.get(Direction.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1);
else
adjData.get(Direction.UP)[0] = DataPointUtil.EMPTY_DATA;
//we get the below block as adj DOWN
if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1)
adjData.get(Direction.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1);
else
adjData.get(Direction.DOWN)[0] = DataPointUtil.EMPTY_DATA;
//We extract the data to render
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
//If the data is not renderable (Void or non existing) we stop since there is no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
//We send the call to create the vertices
LodConfig.CLIENT.graphics.advancedGraphicsOption.lodTemplate.get().template.addLodToBuffer(currentBuffers[bufferIndex], playerBlockPosRounded, data, adjData,
detailLevel, posX, posZ, box, renderer.previousDebugMode, adjShadeDisabled);
}
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
} // region z
} // region z
//long executeStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// the future will be false if its thread failed
if (!future.get())
{
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
break;
}
}
//long executeEnd = System.currentTimeMillis();
//long endTime = System.currentTimeMillis();
//long buildTime = endTime - startTime;
//long executeTime = executeEnd - executeStart;
// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' +
// "thread execute time: " + executeTime + " ms");
// mark that the buildable buffers as ready to swap
switchVbos = true;
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
e.printStackTrace();
}
finally
{
// clean up any potentially open resources
if (buildableBuffers != null)
closeBuffers(fullRegen, lodDim);
try
{
// upload the new buffers
uploadBuffers(fullRegen, lodDim);
}
catch (Exception e)
{
ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage());
e.printStackTrace();
}
// regardless of whether we were able to successfully create
// the buffers, we are done generating.
generatingBuffers = false;
bufferLock.unlock();
}
}
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, ChunkPos playerChunkPos, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
// skip any chunks that Minecraft is going to render
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.x;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.z;
// check if the chunk is on the border
boolean isItBorderPos;
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get() == VanillaOverdraw.BORDER)
isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1);
else
isItBorderPos = false;
//boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW;
// get the positions that will be rendered
return (gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
&& (!isItBorderPos);
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void setupBuffers(LodDimension lodDimension)
{
GlProxy glProxy = GlProxy.getInstance();
bufferLock.lock();
int numbRegionsWide = lodDimension.getWidth();
long regionMemoryRequired;
int numberOfBuffers;
previousRegionWidth = numbRegionsWide;
numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide];
buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide][];
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide][];
drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide][];
if (glProxy.bufferStorageSupported)
{
buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
}
for (int x = 0; x < numbRegionsWide; x++)
{
for (int z = 0; z < numbRegionsWide; z++)
{
regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION;
// if the memory required is greater than the max buffer
// capacity, divide the memory across multiple buffers
if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY)
{
numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1;
// TODO shouldn't this be determined with regionMemoryRequired?
// always allocating the max memory is a bit expensive isn't it?
regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY;
numberOfBuffersPerRegion[x][z] = numberOfBuffers;
buildableBuffers[x][z] = new BufferBuilder[numberOfBuffers];
buildableVbos[x][z] = new VertexBuffer[numberOfBuffers];
drawableVbos[x][z] = new VertexBuffer[numberOfBuffers];
if (glProxy.bufferStorageSupported)
{
buildableStorageBufferIds[x][z] = new int[numberOfBuffers];
drawableStorageBufferIds[x][z] = new int[numberOfBuffers];
}
}
else
{
// we only need one buffer for this region
numberOfBuffersPerRegion[x][z] = 1;
buildableBuffers[x][z] = new BufferBuilder[1];
buildableVbos[x][z] = new VertexBuffer[1];
drawableVbos[x][z] = new VertexBuffer[1];
if (glProxy.bufferStorageSupported)
{
buildableStorageBufferIds[x][z] = new int[1];
drawableStorageBufferIds[x][z] = new int[1];
}
}
for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++)
{
buildableBuffers[x][z][i] = new BufferBuilder((int) regionMemoryRequired);
buildableVbos[x][z][i] = new VertexBuffer(LodUtil.LOD_VERTEX_FORMAT);
drawableVbos[x][z][i] = new VertexBuffer(LodUtil.LOD_VERTEX_FORMAT);
// create the initial mapped buffers (system memory)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_DYNAMIC_DRAW);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (glProxy.bufferStorageSupported)
{
// create the buffer storage (GPU memory)
buildableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
drawableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
}
}
bufferLock.unlock();
}
/**
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
* and destroys any bound OpenGL objects. <br><br>
* <p>
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers()
{
bufferLock.lock();
// destroy the buffer storages if they aren't already
if (buildableStorageBufferIds != null)
{
for (int x = 0; x < buildableStorageBufferIds.length; x++)
{
for (int z = 0; z < buildableStorageBufferIds.length; z++)
{
for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++)
{
int buildableId = buildableStorageBufferIds[x][z][i];
int drawableId = drawableStorageBufferIds[x][z][i];
// Send this over to the render thread, if this is being
// called we aren't worried about stuttering anyway.
// This way we don't have to worry about what context this
// was called from (if any).
RenderSystem.recordRenderCall(() ->
{
GL15.glDeleteBuffers(buildableId);
GL15.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableStorageBufferIds = null;
drawableStorageBufferIds = null;
// destroy the VBOs if they aren't already
if (buildableVbos != null)
{
for (int i = 0; i < buildableVbos.length; i++)
{
for (int j = 0; j < buildableVbos.length; j++)
{
for (int k = 0; k < buildableVbos[i][j].length; k++)
{
int buildableId;
int drawableId;
// variables passed into a lambda expression
// need to be effectively final, so we have
// to use an else statement here
if (buildableVbos[i][j][k] != null)
buildableId = buildableVbos[i][j][k].id;
else
buildableId = 0;
if (drawableVbos[i][j][k] != null)
drawableId = drawableVbos[i][j][k].id;
else
drawableId = 0;
RenderSystem.recordRenderCall(() ->
{
if (buildableId != 0)
GL15.glDeleteBuffers(buildableId);
if (drawableId != 0)
GL15.glDeleteBuffers(drawableId);
});
}
}
}
}
buildableVbos = null;
drawableVbos = null;
// these don't contain any OpenGL objects, so
// they don't require any special clean-up
buildableBuffers = null;
bufferLock.unlock();
}
/** Calls begin on each of the buildable BufferBuilders. */
private void startBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
{
for (int z = 0; z < buildableBuffers.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
// for some reason BufferBuilder.vertexCounts
// isn't reset unless this is called, which can cause
// a false indexOutOfBoundsException
buildableBuffers[x][z][i].discard();
buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
}
}
}
}
}
/** Calls end on each of the buildable BufferBuilders. */
private void closeBuffers(boolean fullRegen, LodDimension lodDim)
{
for (int x = 0; x < buildableBuffers.length; x++)
for (int z = 0; z < buildableBuffers.length; z++)
for (int i = 0; i < buildableBuffers[x][z].length; i++)
if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
buildableBuffers[x][z][i].end();
}
/** Upload all buildableBuffers to the GPU. */
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
{
GlProxy glProxy = GlProxy.getInstance();
try
{
// make sure we are uploading to the builder context,
// this helps prevent interference (IE stuttering) with the Minecraft context.
glProxy.setGlContext(GlProxyContext.LOD_BUILDER);
// determine the upload method
GpuUploadMethod uploadMethod = LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.get();
if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to SUB_DATA
LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.set(GpuUploadMethod.SUB_DATA);
uploadMethod = GpuUploadMethod.SUB_DATA;
}
// actually upload the buffers
for (int x = 0; x < buildableVbos.length; x++)
{
for (int z = 0; z < buildableVbos.length; z++)
{
if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
{
for (int i = 0; i < buildableBuffers[x][z].length; i++)
{
ByteBuffer uploadBuffer = buildableBuffers[x][z][i].popNextBuffer().getSecond();
vboUpload(buildableVbos[x][z][i], buildableStorageBufferIds[x][z][i], uploadBuffer, true, uploadMethod);
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
}
}
}
}
}
catch (Exception e)
{
// this doesn't appear to be necessary anymore, but just in case.
ClientProxy.LOGGER.error(LodBufferBuilder.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
e.printStackTrace();
}
finally
{
GL11.glFinish();
// close the context so it can be re-used later.
// I'm guessing we can't just leave it because the executor service
// does something that invalidates the OpenGL context.
glProxy.setGlContext(GlProxyContext.NONE);
}
}
/** Uploads the uploadBuffer so the GPU can use it. */
private void vboUpload(VertexBuffer vbo, int storageBufferId, ByteBuffer uploadBuffer,
boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
{
// this shouldn't happen, but just to be safe
if (vbo.id != -1 && GlProxy.getInstance().getGlContext() == GlProxyContext.LOD_BUILDER)
{
// this is how many points will be rendered
vbo.vertexCount = (uploadBuffer.capacity() / vbo.format.getVertexSize());
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
try
{
// if possible use the faster buffer storage route
if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
{
// get a pointer to the buffer in system memory
ByteBuffer vboBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
if (vboBuffer == null)
{
int previousCapacity = uploadBuffer.capacity();
// only expand the buffers if the uploadBuffer actually
// has something in it and expansion is allowed
if (previousCapacity != 0 && allowBufferExpansion)
{
// the buffer(s) aren't big enough, expand them.
// This does cause lag/stuttering, so it should be avoided!
// expand the buffer in system memory
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
// un-bind the system memory buffer
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// expand the buffer storage
GL15.glDeleteBuffers(storageBufferId);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId);
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// recursively try to upload into the newly created buffer storage
// but don't recurse again if that fails
// (we don't want an infinitely expanding buffer!)
vboUpload(vbo, storageBufferId, uploadBuffer, false, uploadMethod);
}
}
else
{
// upload the buffer into system memory...
vboBuffer.put(uploadBuffer);
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
// ...then upload into GPU memory
// (uploading into GPU memory directly can only be done
// through the glCopyBufferSubData/glCopyNamed... methods)
GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity());
}
}
else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
{
// no stuttering but high GPU usage
// stores everything in system memory instead of GPU memory
// making rendering much slower.
// Unless the user is running integrated graphics,
// in that case this will actually work better than SUB_DATA.
ByteBuffer vboBuffer;
// map buffer range is better since it can be explicitly unsynchronized
if (GlProxy.getInstance().mapBufferRangeSupported)
vboBuffer = GL30.glMapBufferRange(GL30.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
else
vboBuffer = GL15.glMapBuffer(GL30.GL_ARRAY_BUFFER, uploadBuffer.capacity());
if (vboBuffer == null)
{
GL15.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
}
else
{
vboBuffer.put(uploadBuffer);
}
}
else
{
// hybrid subData/bufferData //
// less stutter, low GPU usage
//long size = GL31.glGetBufferParameteri64(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE); // hopefully just a int should be long enough
long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER)
{
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
}
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
}
}
catch (Exception e)
{
ClientProxy.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
e.printStackTrace();
}
finally
{
if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}//if vbo exists and in correct GL context
}//vboUpload
/** Get the newly created VBOs */
public VertexBuffersAndOffset getVertexBuffers()
{
// don't wait for the lock to open,
// since this is called on the main render thread
if (bufferLock.tryLock())
{
VertexBuffer[][][] tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmpVbo;
int[][][] tmpStorage = drawableStorageBufferIds;
drawableStorageBufferIds = buildableStorageBufferIds;
buildableStorageBufferIds = tmpStorage;
drawableCenterChunkPos = buildableCenterChunkPos;
// the vbos have been swapped
switchVbos = false;
bufferLock.unlock();
}
return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPos);
}
/** A simple container to pass multiple objects back in the getVertexBuffers method. */
public static class VertexBuffersAndOffset
{
public final VertexBuffer[][][] vbos;
public final int[][][] storageBufferIds;
public final ChunkPos drawableCenterChunkPos;
public VertexBuffersAndOffset(VertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, ChunkPos newDrawableCenterChunkPos)
{
vbos = newVbos;
storageBufferIds = newStorageBufferIds;
drawableCenterChunkPos = newDrawableCenterChunkPos;
}
}
/**
* If this is true the buildable near and far
* buffers have been generated and are ready to be
* sent to the LodRenderer.
*/
public boolean newBuffersAvailable()
{
return switchVbos;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* This is the abstract class used to create different
* BufferBuilders.
* @author James Seibel
* @version 10-10-2021
*/
public abstract class AbstractLodTemplate
{
/** Uploads the given LOD to the buffer. */
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled);
/** add the given position and color to the buffer */
protected void addPosAndColor(BufferBuilder buffer,
double x, double y, double z,
int color)
{
// TODO re-add transparency by replacing the 255 with "ColorUtil.getAlpha(color)"
buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255).endVertex();
}
}
@@ -0,0 +1,595 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3i;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
* @author Leonardo Amato
* @version 10-2-2021
*/
public class Box
{
public static final int ADJACENT_HEIGHT_INDEX = 0;
public static final int ADJACENT_DEPTH_INDEX = 1;
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int MIN = 0;
public static final int MAX = 1;
public static final int VOID_FACE = 0;
/** The six cardinal directions */
public static final Direction[] DIRECTIONS = new Direction[] {
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH };
/** North, South, East, West */
public static final Direction[] ADJ_DIRECTIONS = new Direction[] {
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH };
/** All the faces and vertices of a cube. This is used to extract the vertex from the column */
public static final Map<Direction, int[][]> DIRECTION_VERTEX_MAP = new HashMap<Direction, int[][]>()
{{
put(Direction.UP, new int[][] {
{ 0, 1, 0 },
{ 0, 1, 1 },
{ 1, 1, 1 },
{ 1, 1, 0 } });
put(Direction.DOWN, new int[][] {
{ 1, 0, 0 },
{ 1, 0, 1 },
{ 0, 0, 1 },
{ 0, 0, 0 } });
put(Direction.EAST, new int[][] {
{ 1, 1, 0 },
{ 1, 1, 1 },
{ 1, 0, 1 },
{ 1, 0, 0 } });
put(Direction.WEST, new int[][] {
{ 0, 0, 0 },
{ 0, 0, 1 },
{ 0, 1, 1 },
{ 0, 1, 0 } });
put(Direction.SOUTH, new int[][] {
{ 1, 0, 1 },
{ 1, 1, 1 },
{ 0, 1, 1 },
{ 0, 0, 1 } });
put(Direction.NORTH, new int[][] {
{ 0, 0, 0 },
{ 0, 1, 0 },
{ 1, 1, 0 },
{ 1, 0, 0 } });
}};
/**
* This indicates which position is invariable in the DIRECTION_VERTEX_MAP.
* Is used to extract the vertex
*/
public static final Map<Direction, int[]> FACE_DIRECTION = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[] { Y, MAX });
put(Direction.DOWN, new int[] { Y, MIN });
put(Direction.EAST, new int[] { X, MAX });
put(Direction.WEST, new int[] { X, MIN });
put(Direction.SOUTH, new int[] { Z, MAX });
put(Direction.NORTH, new int[] { Z, MIN });
}};
/**
* This is a map from Direction to the relative normal vector
* we are using this since I'm not sure if the getNormal create new object at every call
*/
public static final Map<Direction, Vector3i> DIRECTION_NORMAL_MAP = new HashMap<Direction, Vector3i>()
{{
put(Direction.UP, Direction.UP.getNormal());
put(Direction.DOWN, Direction.DOWN.getNormal());
put(Direction.EAST, Direction.EAST.getNormal());
put(Direction.WEST, Direction.WEST.getNormal());
put(Direction.SOUTH, Direction.SOUTH.getNormal());
put(Direction.NORTH, Direction.NORTH.getNormal());
}};
/** We use this index for all array that are going to */
public static final Map<Direction, Integer> DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.UP, 0);
put(Direction.DOWN, 1);
put(Direction.EAST, 2);
put(Direction.WEST, 3);
put(Direction.SOUTH, 4);
put(Direction.NORTH, 5);
}};
public static final Map<Direction, Integer> ADJ_DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.EAST, 0);
put(Direction.WEST, 1);
put(Direction.SOUTH, 2);
put(Direction.NORTH, 3);
}};
/** holds the box's x, y, z offset */
public final int[] boxOffset;
/** holds the box's x, y, z width */
public final int[] boxWidth;
/** Holds each direction's color */
public final int[] colorMap;
/** The original color (before shading) of this box */
public int color;
/**
*
*/
public final Map<Direction, int[]> adjHeight;
public final Map<Direction, int[]> adjDepth;
public final Map<Direction, byte[]> skyLights;
public byte blockLight;
/** Holds if the given direction should be culled or not */
public final boolean[] culling;
/** creates an empty box */
public Box()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new int[6];
skyLights = new HashMap<Direction, byte[]>()
{{
put(Direction.UP, new byte[1]);
put(Direction.DOWN, new byte[1]);
put(Direction.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjHeight = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjDepth = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
culling = new boolean[6];
}
/** Set the light of the columns */
public void setLights(int skyLight, int blockLight)
{
this.blockLight = (byte) blockLight;
skyLights.get(Direction.UP)[0] = (byte) skyLight;
}
/**
* Set the color of the columns
* @param color color to add
* @param adjShadeDisabled this array indicates which face does not need shading
*/
public void setColor(int color, boolean[] adjShadeDisabled)
{
this.color = color;
for (Direction direction : DIRECTIONS)
{
if (!adjShadeDisabled[DIRECTION_INDEX.get(direction)])
colorMap[DIRECTION_INDEX.get(direction)] = ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
else
colorMap[DIRECTION_INDEX.get(direction)] = color;
}
}
/**
* @param direction of the face of which we want to get the color
* @return color of the face
*/
public int getColor(Direction direction)
{
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() != DebugMode.SHOW_DETAIL)
return colorMap[DIRECTION_INDEX.get(direction)];
else
return ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientWorld().getShade(direction, true));
}
/**
*/
public byte getSkyLight(Direction direction, int verticalIndex)
{
if(direction == Direction.UP || direction == Direction.DOWN)
return skyLights.get(direction)[0];
else
return skyLights.get(direction)[verticalIndex];
}
/**
*/
public int getBlockLight()
{
return blockLight;
}
/** clears this box, resetting everything to default values */
public void reset()
{
Arrays.fill(boxWidth, 0);
Arrays.fill(boxOffset, 0);
Arrays.fill(colorMap, 0);
blockLight = 0;
for (Direction direction : ADJ_DIRECTIONS)
{
for (int i = 0; i < adjHeight.get(direction).length; i++)
{
adjHeight.get(direction)[i] = VOID_FACE;
adjDepth.get(direction)[i] = VOID_FACE;
skyLights.get(direction)[i] = 0;
}
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, BlockPos playerPos)
{
for (Direction direction : DIRECTIONS)
{
if (direction == Direction.DOWN || direction == Direction.WEST || direction == Direction.NORTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if (direction == Direction.UP || direction == Direction.EAST || direction == Direction.SOUTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
culling[DIRECTION_INDEX.get(direction)] = false;
}
}
/**
* @param direction direction that we want to check if it's culled
* @return true if and only if the face of the direction is culled
*/
public boolean isCulled(Direction direction)
{
return culling[DIRECTION_INDEX.get(direction)];
}
/**
* This method create all the shared face culling based on the adjacent data
* @param adjData data adjacent to the column we are going to render
*/
public void setAdjData(Map<Direction, long[]> adjData)
{
int height;
int depth;
int minY = getMinY();
int maxY = getMaxY();
long singleAdjDataPoint;
/* TODO implement attached vertical face culling
//Up direction case
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
{
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
}*/
//Down direction case
singleAdjDataPoint = adjData.get(Direction.DOWN)[0];
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(Direction.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(Direction.DOWN)[0] = skyLights.get(Direction.UP)[0];
//other sided
//TODO clean some similar cases
for (Direction direction : ADJ_DIRECTIONS)
{
if (isCulled(direction))
continue;
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(direction)[0] = maxY;
adjDepth.get(direction)[0] = minY;
adjHeight.get(direction)[1] = VOID_FACE;
adjDepth.get(direction)[1] = VOID_FACE;
skyLights.get(direction)[0] = 15; //in void set full sky light
continue;
}
int i;
int faceToDraw = 0;
boolean firstFace = true;
boolean toFinish = false;
int toFinishIndex = 0;
boolean allAbove = true;
for (i = 0; i < dataPoint.length; i++)
{
singleAdjDataPoint = dataPoint[i];
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
break;
height = DataPointUtil.getHeight(singleAdjDataPoint);
depth = DataPointUtil.getDepth(singleAdjDataPoint);
if (depth <= maxY)
{
allAbove = false;
if (height < minY)
{
// the adj data is lower than the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = getMinY();
skyLights.get(direction)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY)
{
if (height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(direction)[0] = VOID_FACE;
adjDepth.get(direction)[0] = VOID_FACE;
}
else // height < maxY
{
// the adj data intersects the lower part of the current data
// if this is the only face, use the maxY and break,
// if there was another face we finish the last one and break
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = height;
skyLights.get(direction)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
toFinish = false;
faceToDraw++;
}
break;
}
else if (height >= maxY)//depth > minY &&
{
// the adj data intersects the higher part of the current data
// we start the creation of a new face
adjHeight.get(direction)[faceToDraw] = depth;
//skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
else
{
// if (depth > minY && height < maxY)
// the adj data is contained in the current data
if (firstFace)
{
adjHeight.get(direction)[0] = getMaxY();
}
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
faceToDraw++;
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
}
}
if (allAbove)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(direction)[faceToDraw] = minY;
if(toFinishIndex < dataPoint.length)
{
singleAdjDataPoint = dataPoint[toFinishIndex];
if (DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(direction)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(direction)[faceToDraw] = skyLights.get(Direction.UP)[0];
}
faceToDraw++;
}
adjHeight.get(direction)[faceToDraw] = VOID_FACE;
adjDepth.get(direction)[faceToDraw] = VOID_FACE;
}
}
/** We use this method to set the various width of the column */
public void setWidth(int xWidth, int yWidth, int zWidth)
{
boxWidth[X] = xWidth;
boxWidth[Y] = yWidth;
boxWidth[Z] = zWidth;
}
/** We use this method to set the various offset of the column */
public void setOffset(int xOffset, int yOffset, int zOffset)
{
boxOffset[X] = xOffset;
boxOffset[Y] = yOffset;
boxOffset[Z] = zOffset;
}
/**
* This method return the position of a face in the axis of the face
* This is useful for the face culling
* @param direction that we want to check
* @return position in the axis of the face
*/
public int getFacePos(Direction direction)
{
return boxOffset[FACE_DIRECTION.get(direction)[0]] + boxWidth[FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
return adjIndex == 0;
return !(adjHeight.get(direction)[adjIndex] == VOID_FACE && adjDepth.get(direction)[adjIndex] == VOID_FACE);
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position x of the relative vertex
*/
public int getX(Direction direction, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position y of the relative vertex
*/
public int getY(Direction direction, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @param adjIndex, index of the n-th culled face of this direction
* @return position x of the relative vertex
*/
public int getY(Direction direction, int vertexIndex, int adjIndex)
{
if (direction == Direction.DOWN || direction == Direction.UP)
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
return adjHeight.get(direction)[adjIndex];
else
return adjDepth.get(direction)[adjIndex];
}
}
/**
* @param direction direction of the face we want to render
* @param vertexIndex index of the vertex of the face (0-1-2-3)
* @return position z of the relative vertex
*/
public int getZ(Direction direction, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Z];
}
public int getMinX()
{
return boxOffset[X];
}
public int getMaxX()
{
return boxOffset[X] + boxWidth[X];
}
public int getMinY()
{
return boxOffset[Y];
}
public int getMaxY()
{
return boxOffset[Y] + boxWidth[Y];
}
public int getMinZ()
{
return boxOffset[Z];
}
public int getMaxZ()
{
return boxOffset[Z] + boxWidth[Z];
}
}
@@ -0,0 +1,141 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 10-10-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
if (box == null)
return;
// equivalent to 2^detailLevel
int blockWidth = 1 << detailLevel;
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else
color = DataPointUtil.getColor(data);
generateBoundingBox(
box,
DataPointUtil.getHeight(data),
DataPointUtil.getDepth(data),
blockWidth,
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
bufferCenterBlockPos,
adjData,
color,
DataPointUtil.getLightSkyAlt(data),
DataPointUtil.getLightBlock(data),
adjShadeDisabled);
addBoundingBoxToBuffer(buffer, box);
}
private void generateBoundingBox(Box box,
int height, int depth, int width,
double xOffset, double yOffset, double zOffset,
BlockPos bufferCenterBlockPos,
Map<Direction, long[]> adjData,
int color,
int skyLight,
int blockLight,
boolean[] adjShadeDisabled)
{
// don't add an LOD if it is empty
if (height == -1 && depth == -1)
return;
if (depth == height)
// if the top and bottom points are at the same height
// render this LOD as 1 block thick
height++;
// offset the AABB by its x/z position in the world since
// it uses doubles to specify its location, unlike the model view matrix
// which only uses floats
double x = -bufferCenterBlockPos.getX();
double z = -bufferCenterBlockPos.getZ();
box.reset();
box.setColor(color, adjShadeDisabled);
box.setLights(skyLight, blockLight);
box.setWidth(width, height - depth, width);
box.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
box.setUpCulling(32, bufferCenterBlockPos);
box.setAdjData(adjData);
}
private void addBoundingBoxToBuffer(BufferBuilder buffer, Box box)
{
int color;
int skyLight;
int blockLight;
for (Direction direction : Box.DIRECTIONS)
{
if(box.isCulled(direction))
continue;
int verticalFaceIndex = 0;
while (box.shouldRenderFace(direction, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++)
{
color = box.getColor(direction);
skyLight = box.getSkyLight(direction, verticalFaceIndex);
blockLight = box.getBlockLight();
color = ColorUtil.applyLightValue(color, skyLight, blockLight);
addPosAndColor(buffer,
box.getX(direction, vertexIndex),
box.getY(direction, vertexIndex, verticalFaceIndex),
box.getZ(direction, vertexIndex),
color);
}
verticalFaceIndex++;
}
}
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* TODO DynamicLodTemplate
* Chunks smoothly transition between
* each other, unless a neighboring chunk
* is at a significantly different height.
* @author James Seibel
* @version 06-16-2021
*/
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
* @author James Seibel
* @version 06-16-2021
*/
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPos bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -0,0 +1,543 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodBuilding;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockColorWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Block.BlockShapeWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import com.seibel.lod.wrappers.World.BiomeColorWrapper;
import com.seibel.lod.wrappers.World.BiomeWrapper;
import com.seibel.lod.wrappers.World.WorldWrapper;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
/**
* This object is in charge of creating Lod related objects.
*
* @author Cola
* @author Leonardo Amato
* @author James Seibel
* @version 10-22-2021
*/
public class LodBuilder
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
/** If no blocks are found in the area in determineBottomPointForArea return this */
public static final short DEFAULT_DEPTH = 0;
/** If no blocks are found in the area in determineHeightPointForArea return this */
public static final short DEFAULT_HEIGHT = 0;
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
/**
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 0;
//public static final boolean useExperimentalLighting = true;
public LodBuilder()
{
}
public void generateLodNodeAsync(ChunkWrapper chunk, LodWorld lodWorld, IWorld world)
{
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
}
public void generateLodNodeAsync(ChunkWrapper chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
return;
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
return;
Thread thread = new Thread(() ->
{
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (mc.getClientWorld() == null)
return;
// don't try to generate LODs if the user isn't in the world anymore
// (this happens a lot when the user leaves a world/server)
if (mc.getSinglePlayerServer() == null && mc.getCurrentServer() == null)
return;
DimensionType dim = world.dimensionType();
// make sure the dimension exists
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
}
catch (IllegalArgumentException | NullPointerException e)
{
e.printStackTrace();
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
});
lodGenThreadPool.execute(thread);
}
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, ChunkWrapper chunk) throws IllegalArgumentException
{
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
}
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, ChunkWrapper chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
if (region == null)
return;
// this happens if a LOD is generated after the user leaves the world.
if (MinecraftWrapper.INSTANCE.getWrappedClientWorld() == null)
return;
// determine how many LODs to generate horizontally
byte minDetailLevel = region.getMinDetailLevel();
HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
// determine how many LODs to generate vertically
//VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
byte detailLevel = detail.detailLevel;
// generate the LODs
int posX;
int posZ;
for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
startX = detail.startX[i];
startZ = detail.startZ[i];
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.worldHeight / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getPos().getX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().getZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
}
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().getX(), chunk.getPos().getZ());
}
/** creates a vertical DataPoint */
private long[] createVerticalDataToMerge(HorizontalResolution detail, ChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ)
{
// equivalent to 2^detailLevel
int size = 1 << detail.detailLevel;
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
int verticalData = DataPointUtil.worldHeight / 2 + 1;
ChunkPosWrapper chunkPos = chunk.getPos();
int height;
int depth;
int color;
int light;
int lightSky;
int lightBlock;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
boolean hasCeiling = mc.getClientWorld().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientWorld().dimensionType().hasSkyLight();
boolean isDefault;
BlockPosWrapper blockPos = new BlockPosWrapper();
int index;
for (index = 0; index < size * size; index++)
{
xRel = startX + index % size;
zRel = startZ + index / size;
xAbs = chunkPos.getMinBlockX() + xRel;
zAbs = chunkPos.getMinBlockZ() + zRel;
//Calculate the height of the lod
yAbs = DataPointUtil.worldHeight + 1;
int count = 0;
boolean topBlock = true;
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
// If the lod is at the default height, it must be void data
if (height == DEFAULT_HEIGHT)
{
if (topBlock)
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
break;
}
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
if (hasCeiling && topBlock)
{
yAbs = depth;
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, true, hasSkyLight, true);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs, blockPos);
blockPos.set(xAbs, yAbs - 1, zAbs);
}
else
{
blockPos.set(xAbs, yAbs, zAbs);
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
blockPos.set(xAbs, yAbs + 1, zAbs);
}
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
}
}
return dataToMerge;
}
/**
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private short determineBottomPointFrom(ChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, BlockPosWrapper blockPos)
{
short depth = DEFAULT_DEPTH;
for (int y = yAbs; y >= 0; y--)
{
blockPos.set(xAbs, y, zAbs);
if (!isLayerValidLodPoint(chunk, blockPos))
{
depth = (short) (y + 1);
break;
}
}
return depth;
}
/** Find the highest valid point from the Top */
private short determineHeightPointFrom(ChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, BlockPosWrapper blockPos)
{
short height = DEFAULT_HEIGHT;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
{
for (int y = yAbs; y >= 0; y--)
{
blockPos.set(xAbs, y, zAbs);
if (isLayerValidLodPoint(chunk, blockPos))
{
height = (short) (y + 1);
break;
}
}
}
return height;
}
// =====================//
// constructor helpers //
// =====================//
/**
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
*/
private int generateLodColor(ChunkWrapper chunk, LodBuilderConfig config, int xRel, int yAbs, int zRel, BlockPosWrapper blockPos)
{
int colorInt;
if (config.useBiomeColors)
{
// I have no idea why I need to bit shift to the right, but
// if I don't the biomes don't show up correctly.
colorInt = chunk.getBiome(xRel, yAbs, zRel).getColorForBiome(xRel, zRel);
}
else
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs, chunk.getPos().getMinBlockZ() + zRel);
colorInt = getColorForBlock(chunk, blockPos);
// if we are skipping non-full and non-solid blocks that means we ignore
// snow, flowers, etc. Get the above block so we can still get the color
// of the snow, flower, etc. that may be above this block
int aboveColorInt = 0;
if (LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull || LodConfig.CLIENT.worldGenerator.blockToAvoid.get().noCollision)
{
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs + 1, chunk.getPos().getMinBlockZ() + zRel);
aboveColorInt = getColorForBlock(chunk, blockPos);
}
//if (colorInt == 0 && yAbs > 0)
// if this block is invisible, check the block below it
// colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
// override this block's color if there was a block above this
// and we were avoiding non-full/non-solid blocks
if (aboveColorInt != 0)
colorInt = aboveColorInt;
}
return colorInt;
}
/** Gets the light value for the given block position */
private int getLightValue(ChunkWrapper chunk, BlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight = 0;
int blockLight;
// 1 means the lighting is a guess
int isDefault = 0;
WorldWrapper world = MinecraftWrapper.INSTANCE.getWrappedServerWorld();
int blockBrightness = chunk.getEmittedBrightness(blockPos);
// get the air block above or below this block
if (hasCeiling && topBlock)
blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
else
blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
if (world != null && !world.isEmpty())
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(blockPos);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(blockPos);
//else
// skyLight = 0;
}
if (!topBlock && skyLight == 15)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= mc.getClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
else
{
world = MinecraftWrapper.INSTANCE.getWrappedClientWorld();
if (world.isEmpty())
return 0;
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(blockPos);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(blockPos);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
// we don't know what the light here is,
// lets just take a guess
if (blockPos.getY() >= mc.getClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
if (hasSkyLight)
skyLight = 0;
}
}
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4) + (isDefault << 8);
}
/** Returns a color int for the given block. */
private int getColorForBlock(ChunkWrapper chunk, BlockPosWrapper blockPos)
{
int colorOfBlock;
int colorInt;
int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
//int x = blockPos.getX();
int y = blockPos.getY();
//int z = blockPos.getZ();
BlockColorWrapper blockColorWrapper;
BlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos);
if (chunk.isWaterLogged(blockPos))
{
BiomeWrapper biome = chunk.getBiome(xRel, y, zRel);
return biome.getWaterTint();
}
else
blockColorWrapper = chunk.getBlockColorWrapper(blockPos);
if (blockShapeWrapper.isToAvoid())
return 0;
colorOfBlock = blockColorWrapper.getColor();
if (blockColorWrapper.hasTint())
{
WorldWrapper world = MinecraftWrapper.INSTANCE.getWrappedServerWorld();
if (world == null || world.isEmpty())
world = MinecraftWrapper.INSTANCE.getWrappedClientWorld();
int tintValue;
if (blockColorWrapper.hasGrassTint())
// grass and green plants
tintValue = BiomeColorWrapper.getGrassColor(world, blockPos);
else if (blockColorWrapper.hasFolliageTint())
tintValue = BiomeColorWrapper.getFoliageColor(world, blockPos);
else
//we can reintroduce this with the wrappers
tintValue = BiomeColorWrapper.getWaterColor(world, blockPos);
colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, colorOfBlock);
}
else
colorInt = colorOfBlock;
return colorInt;
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(ChunkWrapper chunk, BlockPosWrapper blockPos)
{
if (chunk.isWaterLogged(blockPos))
return true;
boolean nonFullAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull;
boolean noCollisionAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().noCollision;
BlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos);
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
}
}
@@ -0,0 +1,95 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodBuilding;
import com.seibel.lod.enums.DistanceGenerationMode;
/**
* This is used to easily configure how LodChunks are generated.
* Generally this will only be used if we want to generate a
* LodChunk using an incomplete Chunk, otherwise the defaults
* work best for a fully generated chunk (IE has correct surface blocks).
* @author James Seibel
* @version 8-14-2021
*/
public class LodBuilderConfig
{
/** default: false */
public boolean useHeightmap;
/** default: false */
public boolean useBiomeColors;
/** default: true */
public boolean useSolidBlocksInColorGen;
/** default: server */
public DistanceGenerationMode distanceGenerationMode;
/**
* default settings for a normal chunk <br>
* useHeightmap = false <br>
* useBiomeColors = false <br>
* useSolidBlocksInColorGen = true <br>
* generationMode = Server <br>
*/
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
distanceGenerationMode = DistanceGenerationMode.SERVER;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newDistanceGenerationMode;
}
/**
* @param newUseHeightmap default = false
* @param newUseBiomeColors default = false
* @param newUseSolidBlocksInBiomeColor default = true
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
this();
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
}
/**
* @param newDistanceGenerationMode default = Server
*/
public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
{
this();
distanceGenerationMode = newDistanceGenerationMode;
}
}
@@ -0,0 +1,705 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilderConfig;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.palette.UpgradeData;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.ChunkGenerator;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
import net.minecraft.world.gen.feature.BlockClusterFeatureConfig;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.DecoratedFeatureConfig;
import net.minecraft.world.gen.feature.FeatureSpread;
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
import net.minecraft.world.gen.feature.IceAndSnowFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.feature.template.TemplateManager;
import net.minecraft.world.gen.placement.ConfiguredPlacement;
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
import net.minecraft.world.gen.placement.IPlacementConfig;
import net.minecraft.world.gen.placement.NoiseDependant;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.server.ServerWorldLightManager;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 10-22-2021
*/
public class LodGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private boolean threadStarted = false;
private final LodChunkGenThread thread;
/**
* If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line.
*/
private static final ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodGenWorker(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
{
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
else
{
// Every other method can
// be done asynchronously
Thread newThread = new Thread(thread);
newThread.setPriority(5);
genThreads.execute(newThread);
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private static class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
public final LodBuilder lodBuilder;
private final ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
generationMode = newGenerationMode;
lodBuilder = newLodBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
try
{
// only generate LodChunks if they can
// be added to the current LodDimension
/* TODO I must disable this 'if', if I will find a way to replace it */
if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
//
//{
// lodBuilder.generateLodNodeFromChunk(lodDim, loadedChunk, new LodBuilderConfig(DistanceGenerationMode.SERVER));
//}
//else
//{
/*
IChunk loadedChunk = null;
if (lodDim.isChunkPreGenerated(pos.x, pos.z) && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
{
// generate a Lod like normal
loadedChunk = ChunkLoader.getChunkFromFile(pos);
if(loadedChunk != null)
lodBuilder.generateLodNodeFromChunk(lodDim, loadedChunk, new LodBuilderConfig(DistanceGenerationMode.SERVER));
else
{
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
lodBuilder.generateLodNodeFromChunk(lodDim, serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), new LodBuilderConfig(DistanceGenerationMode.SERVER));
//generateWithServer();
break;
}
}
}
else
{*/
switch (generationMode)
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
}
//}
//lodRenderer.regenerateLODsNextFrame();
/*
boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
if (dataExistence)
ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
else
ClientProxy.LOGGER.info(pos.x + " " + pos.z);
*/
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}
catch (Exception e)
{
ClientProxy.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
e.printStackTrace();
}
finally
{
// decrement how many threads are running
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
// this position is no longer being generated
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
}
}// run
/**
* takes about 2-5 ms
*/
private void generateUsingBiomesOnly()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverWorld.getSeaLevel();
boolean simulateHeight = generationMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
boolean inTheEnd = false;
// add fake heightmap data so our LODs aren't at height 0
Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP);
for (int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// these heights are of course aren't super accurate,
// they are just to simulate height data where there isn't any
switch (chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
{
case NETHER:
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
break;
case EXTREME_HILLS:
heightmap.setHeight(x, z, seaLevel + 30);
break;
case MESA:
case JUNGLE:
heightmap.setHeight(x, z, seaLevel + 20);
break;
case BEACH:
heightmap.setHeight(x, z, seaLevel + 5);
break;
case NONE:
heightmap.setHeight(x, z, 0);
break;
case OCEAN:
case RIVER:
heightmap.setHeight(x, z, seaLevel);
break;
case THEEND:
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.setHeight(x, z, seaLevel + 10);
break;
}// heightmap switch
}
else
{
// we aren't simulating height
// always use sea level
heightmap.setHeight(x, z, seaLevel);
}
}// z
}// x
chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
if (!inTheEnd)
{
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(true, true, false));
}
else
{
// if we are in the end, don't generate any chunks.
// Since we don't know where the islands are, everything
// generates the same, and it looks awful.
//TODO it appears that 'if' can be collapsed, but comment says that it should not be a case
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(true, true, false));
}
// long startTime = System.currentTimeMillis();
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}
/**
* takes about 10 - 20 ms
*/
private void generateUsingSurface()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ServerWorldLightManager lightEngine = (ServerWorldLightManager) serverWorld.getLightEngine();
TemplateManager templateManager = serverWorld.getStructureManager();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// this feature has been proven to be thread safe,
// so we will add it
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.SURFACE));
/*TODO if we want to use Biome utils and terrain utils for overworld
* lodBuilder.generateLodNodeFromChunk(lodDim, pos ,detailLevel, serverWorld.getSeed());*/
}
/**
* takes about 15 - 20 ms
* <p>
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
private void generateUsingFeatures()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ServerWorldLightManager lightEngine = (ServerWorldLightManager) serverWorld.getLightEngine();
TemplateManager templateManager = serverWorld.getStructureManager();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
// get all the biomes in the chunk
HashSet<Biome> biomes = new HashSet<>();
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
// Issue #35
// For some reason Jungle biomes cause incredible lag
// the features here must be interacting with each other
// in unpredictable ways (specifically tree feature generation).
// When generating Features my CPU usage generally hovers around 30 - 40%
// when generating Jungles it spikes to 100%.
if (biome.getBiomeCategory() != Biome.Category.JUNGLE)
{
// should probably use the heightmap here instead of seaLevel,
// but this seems to get the job done well enough
biomes.add(biome);
}
}
}
boolean allowUnstableFeatures = LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.get();
// generate all the features related to this chunk.
// this may or may not be thread safe
for (Biome biome : biomes)
{
List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
for (List<Supplier<ConfiguredFeature<?, ?>>> suppliers : featuresForState)
{
for (Supplier<ConfiguredFeature<?, ?>> featureSupplier : suppliers)
{
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
if (!allowUnstableFeatures &&
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
continue;
try
{
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
}
catch (ConcurrentModificationException | UnsupportedOperationException e)
{
// This will happen. I'm not sure what to do about it
// except pray that it doesn't affect the normal world generation
// in any harmful way.
// Update: this can cause crashes and high CPU usage.
// Issue #35
// I tried cloning the config for each feature, but that
// path was blocked since I can't clone lambda methods.
// I tried using a deep cloning library and discovered
// the problem there.
// ( https://github.com/kostaskougios/cloning
// and
// https://github.com/EsotericSoftware/kryo )
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
// This will happen when the LodServerWorld
// isn't able to return something that a feature
// generator needs
catch (Exception e)
{
// I'm not sure what happened, print to the log
e.printStackTrace();
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
}
}
}
// generate a Lod like normal
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.FEATURES));
}
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 ms
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
* <p>
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES)), new LodBuilderConfig(DistanceGenerationMode.SERVER));
}
//================//
// Unused methods //
//================//
// Sadly I wasn't able to get these to work,
// they are here for documentation purposes
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
{
IPlacementConfig placementConfig;
Class oldConfigClass = config.decorator.config().getClass();
if (oldConfigClass == FeatureSpreadConfig.class)
{
FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config();
FeatureSpread oldSpread = oldPlacementConfig.count();
placementConfig = new FeatureSpreadConfig(oldSpread);
}
else if (oldConfigClass == DecoratedPlacementConfig.class)
{
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config();
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer());
}
else if (oldConfigClass == NoiseDependant.class)
{
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config();
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
}
else
{
// ClientProxy.LOGGER.debug("unknown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
return config;
}
ConfiguredPlacement<?> newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig);
return new DecoratedFeatureConfig(config.feature, newPlacement);
}
@SuppressWarnings("unused")
private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config)
{
WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
provider.weightedList.entries.addAll(((WeightedBlockStateProvider) config.stateProvider).weightedList.entries);
HashSet<Block> whitelist = new HashSet<>(config.whitelist);
HashSet<BlockState> blacklist = new HashSet<>(config.blacklist);
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
builder.whitelist(whitelist);
builder.blacklist(blacklist);
builder.xspread(config.xspread);
builder.yspread(config.yspread);
builder.zspread(config.zspread);
if (config.canReplace)
{
builder.canReplace();
}
if (config.needWater)
{
builder.needWater();
}
if (config.project)
{
builder.noProjection();
}
builder.tries(config.tries);
return builder.build();
}
}
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* <p>
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public static void restartExecutorService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
/*
* performance/generation tests related to
* serverWorld.getChunk(x, z, ChunkStatus. *** )
true/false is whether they generated blocks or not
the time is how long it took to generate
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
ChunkStatus.BIOMES 1 - 10 ms false (no height)
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
ChunkStatus.FEATURES 7 - 25 ms true
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
ChunkStatus.LIGHT 20 - 40 ms true
ChunkStatus.FULL 30 - 50 ms true
ChunkStatus.SPAWN 50 - 80 ms true
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise, if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
}
@@ -0,0 +1,331 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.particles.IParticleData;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.util.registry.DynamicRegistries;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.DimensionType;
import net.minecraft.world.EmptyTickList;
import net.minecraft.world.ISeedReader;
import net.minecraft.world.ITickList;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeManager;
import net.minecraft.world.border.WorldBorder;
import net.minecraft.world.chunk.AbstractChunkProvider;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.Heightmap.Type;
import net.minecraft.world.gen.feature.structure.Structure;
import net.minecraft.world.gen.feature.structure.StructureStart;
import net.minecraft.world.lighting.WorldLightManager;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.storage.IWorldInfo;
/**
* This is a fake ServerWorld used when generating features.
* This allows us to keep each LodChunk generation independent
* of the actual ServerWorld, allowing us
* to multithread generation.
* @author James Seibel
* @version 7-26-2021
*/
public class LodServerWorld implements ISeedReader
{
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
public final IChunk chunk;
public final ServerWorld serverWorld;
public LodServerWorld(ServerWorld newServerWorld, IChunk newChunk)
{
chunk = newChunk;
serverWorld = newServerWorld;
}
@Override
public int getHeight(Type heightmapType, int x, int z)
{
// make sure the block position is set relative to the chunk
x = x % LodUtil.CHUNK_WIDTH;
x = (x < 0) ? x + 16 : x;
z = z % LodUtil.CHUNK_WIDTH;
z = (z < 0) ? z + 16 : z;
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
}
@Override
public Biome getBiome(BlockPos pos)
{
return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
}
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft)
{
return chunk.setBlockState(pos, state, false) == state;
}
@Override
public BlockState getBlockState(BlockPos pos)
{
return chunk.getBlockState(pos);
}
@Override
public FluidState getFluidState(BlockPos pos)
{
return chunk.getFluidState(pos);
}
@Override
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> state)
{
return state.test(chunk.getBlockState(pos));
}
@Override
public ITickList<Block> getBlockTicks()
{
return EmptyTickList.empty();
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
{
return chunk;
}
@Override
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos p_241827_1_, Structure<?> p_241827_2_)
{
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
}
@Override
public ITickList<Fluid> getLiquidTicks()
{
return EmptyTickList.empty();
}
@Override
public WorldLightManager getLightEngine()
{
return new WorldLightManager(null, false, false);
}
@Override
public long getSeed()
{
return serverWorld.getSeed();
}
@Override
public DynamicRegistries registryAccess()
{
return serverWorld.registryAccess();
}
/**
* All methods below shouldn't be needed
* and thus have been left unimplemented.
*/
@Override
public Random getRandom()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void playSound(PlayerEntity player, BlockPos pos, SoundEvent soundIn, SoundCategory category, float volume,
float pitch)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void addParticle(IParticleData particleData, double x, double y, double z, double xSpeed, double ySpeed,
double zSpeed)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public BiomeManager getBiomeManager()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSeaLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public float getShade(Direction p_230487_1_, boolean p_230487_2_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public WorldBorder getWorldBorder()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean removeBlock(BlockPos pos, boolean isMoving)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public ServerWorld getLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public AbstractChunkProvider getChunkSource()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public IWorldInfo getLevelData()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> arg0, AxisAlignedBB arg1,
Predicate<? super T> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<? extends PlayerEntity> players()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSkyDarken()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public boolean isClientSide()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DimensionType dimensionType()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public TileEntity getBlockEntity(BlockPos p_175625_1_)
{
throw new UnsupportedOperationException("Not Implemented");
}
}
@@ -0,0 +1,204 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.PosToGenerateContainer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
/**
* A singleton that handles all long distance LOD world generation.
* @author James Seibel
* @version 9-25-2021
*/
public class LodWorldGenerator
{
public final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This holds the thread used to generate new LODs off the main thread. */
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
/** we only want to queue up one generator thread at a time */
private boolean generatorThreadRunning = false;
/**
* How many chunks to generate outside the player's view distance at one
* time. (or more specifically how many requests to make at one time). I
* multiply by 8 to make sure there is always a buffer of chunk requests, to
* make sure the CPU is always busy, and we can generate LODs as quickly as
* possible.
*/
public int maxChunkGenRequests;
/**
* This keeps track of how many chunk generation requests are on going. This is
* to limit how many chunks are queued at once. To prevent chunks from being
* generated for a long time in an area the player is no longer in.
*/
public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
public final Set<ChunkPos> positionsWaitingToBeGenerated = new HashSet<>();
/**
* Singleton copy of this object
*/
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
private LodWorldGenerator()
{
}
/**
* Queues up LodNodeGenWorkers for the given lodDimension.
* @param renderer needed so the LodNodeGenWorkers can flag that the
* buffers need to be rebuilt.
*/
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() != DistanceGenerationMode.NONE
&& !generatorThreadRunning
&& mc.hasSinglePlayerServer())
{
// the thread is now running, don't queue up another thread
generatorThreadRunning = true;
// just in case the config changed
maxChunkGenRequests = LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get() * 8;
Thread generatorThread = new Thread(() ->
{
try
{
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = mc.getPlayer().blockPosition().getX();
int playerPosZ = mc.getPlayer().blockPosition().getZ();
//=======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
byte detailLevel;
int posX;
int posZ;
int nearIndex = 0;
int farIndex = 0;
for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
{
// I wish there was a way to compress this code, but I'm not aware of
// an easy way to do so.
// add the near positions
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
posX = posToGenerate.getNthPosX(nearIndex, true);
posZ = posToGenerate.getNthPosZ(nearIndex, true);
nearIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
break;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
// add the far positions
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
posZ = posToGenerate.getNthPosZ(farIndex, false);
farIndex++;
ChunkPos chunkPos = new ChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
// don't add more to the generation queue then allowed
if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
continue;
//break;
// prevent generating the same chunk multiple times
if (positionsWaitingToBeGenerated.contains(chunkPos))
continue;
positionsWaitingToBeGenerated.add(chunkPos);
numberOfChunksWaitingToGenerate.addAndGet(1);
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
}
}
catch (Exception e)
{
// this shouldn't ever happen, but just in case
e.printStackTrace();
}
finally
{
generatorThreadRunning = false;
}
});
mainGenThread.execute(generatorThread);
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
} // queueGenerationRequests
}
@@ -0,0 +1,591 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.config;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.BlockToAvoid;
import com.seibel.lod.enums.BufferRebuildTimes;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.GpuUploadMethod;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.LodUtil;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
/**
* This handles any configuration the user has access to.
* @author Leonardo Amato
* @author James Seibel
* @version 10-25-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
// CONFIG STRUCTURE
// -> Client
// |
// |-> Graphics
// | |-> QualityOption
// | |-> FogQualityOption
// | |-> AdvancedGraphicsOption
// |
// |-> World Generation
// |
// |-> Advanced Mod Option
// |-> Threads
// |-> Buffers
// |-> Debugging
public static class Client
{
public final Graphics graphics;
public final WorldGenerator worldGenerator;
public final AdvancedModOptions advancedModOptions;
//================//
// Client Configs //
//================//
public Client(ForgeConfigSpec.Builder builder)
{
builder.push(this.getClass().getSimpleName());
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
advancedModOptions = new AdvancedModOptions(builder);
}
builder.pop();
}
//==================//
// Graphics Configs //
//==================//
public static class Graphics
{
public final QualityOption qualityOption;
public final FogQualityOption fogQualityOption;
public final AdvancedGraphicsOption advancedGraphicsOption;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how the mod will look in game").push("Graphics");
{
qualityOption = new QualityOption(builder);
advancedGraphicsOption = new AdvancedGraphicsOption(builder);
fogQualityOption = new FogQualityOption(builder);
}
builder.pop();
}
public static class QualityOption
{
public final ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
public final ForgeConfigSpec.IntValue lodChunkRenderDistance;
public final ForgeConfigSpec.EnumValue<VerticalQuality> verticalQuality;
public final ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public final ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
QualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how detailed the fake chunks will be.").push(this.getClass().getSimpleName());
verticalQuality = builder
.comment("\n\n"
+ " This indicates how detailed fake chunks will represent \n"
+ " overhangs, caves, floating islands, ect. \n"
+ " Higher options will use more memory and increase GPU usage. \n"
+ " " + VerticalQuality.LOW + ": uses at max 2 columns per position. \n"
+ " " + VerticalQuality.MEDIUM + ": uses at max 4 columns per position. \n"
+ " " + VerticalQuality.HIGH + ": uses at max 8 columns per position. \n")
.defineEnum("Vertical Quality", VerticalQuality.MEDIUM);
horizontalScale = builder
.comment("\n\n"
+ " This indicates how quickly fake chunks drop off in quality. \n"
+ " " + HorizontalScale.LOW + ": quality drops every " + HorizontalScale.LOW.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.MEDIUM + ": quality drops every " + HorizontalScale.MEDIUM.distanceUnit / 16 + " chunks. \n"
+ " " + HorizontalScale.HIGH + ": quality drops every " + HorizontalScale.HIGH.distanceUnit / 16 + " chunks. \n")
.defineEnum("Horizontal Scale", HorizontalScale.MEDIUM);
horizontalQuality = builder
.comment("\n\n"
+ " This indicates the exponential base of the quadratic drop-off \n"
+ " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n")
.defineEnum("Horizontal Quality", HorizontalQuality.MEDIUM);
drawResolution = builder
.comment("\n\n"
+ " What is the maximum detail fake chunks should be drawn at? \n"
+ " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk. \n")
.defineEnum("Block size", HorizontalResolution.BLOCK);
lodChunkRenderDistance = builder
.comment("\n\n"
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("Lod Render Distance", 64, 32, 1024);
builder.pop();
}
}
public static class FogQualityOption
{
public final ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public final ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public final ForgeConfigSpec.BooleanValue disableVanillaFog;
FogQualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control the fog quality.").push(this.getClass().getSimpleName());
fogDistance = builder
.comment("\n\n"
+ " At what distance should Fog be drawn on the fake chunks? \n"
+ " If the fog cuts off abruptly or you are using Optifine's \"fast\" fog option \n"
+ " set this to " + FogDistance.NEAR + " or " + FogDistance.FAR + ". \n")
.defineEnum("Fog Distance", FogDistance.FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " When should fog be drawn? \n"
+ " " + FogDrawOverride.OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.FANCY + ". \n"
+ " " + FogDrawOverride.NO_FOG + ": Never draw fog on the LODs \n"
+ " " + FogDrawOverride.FAST + ": Always draw fast fog on the LODs \n"
+ " " + FogDrawOverride.FANCY + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n")
.defineEnum("Fog Draw Override", FogDrawOverride.FANCY);
disableVanillaFog = builder
.comment("\n\n"
+ " If true disable Minecraft's fog. \n\n"
+ ""
+ " Experimental! May cause issues with Sodium. \n\n"
+ ""
+ " Unlike Optifine or Sodium's fog disabling option this won't change \n"
+ " performance (we don't actually disable the fog, we just tell it to render a infinite distance away). \n"
+ " May or may not play nice with other mods that edit fog. \n")
.define("Experimental Disable Vanilla Fog", false);
builder.pop();
}
}
public static class AdvancedGraphicsOption
{
public final ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public final ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public final ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public final ForgeConfigSpec.EnumValue<VanillaOverdraw> vanillaOverdraw;
public final ForgeConfigSpec.EnumValue<GpuUploadMethod> gpuUploadMethod;
public final ForgeConfigSpec.BooleanValue useExtendedNearClipPlane;
AdvancedGraphicsOption(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced graphics option for the mod").push(this.getClass().getSimpleName());
lodTemplate = builder
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
+ " \n"
+ " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
+ " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between each other, \n"
+ " " + " unless a neighboring chunk is at a significantly different height. \n")
.defineEnum("LOD Template", LodTemplate.CUBIC);
disableDirectionalCulling = builder
.comment("\n\n"
+ " If false fake chunks behind the player's camera \n"
+ " aren't drawn, increasing performance. \n\n"
+ ""
+ " If true all LODs are drawn, even those behind \n"
+ " the player's camera, decreasing performance. \n\n"
+ ""
+ " Disable this if you see LODs disappearing. \n"
+ " (Which may happen if you are using a camera mod) \n")
.define("Disable Directional Culling", false);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ " Disable quality falloff, \n"
+ " all fake chunks will be drawn at the highest \n"
+ " available detail level. \n\n"
+ " "
+ " WARNING: \n"
+ " This could cause a Out Of Memory crash on render \n"
+ " distances higher than 128 \n")
.define("Always Use Max Quality", false);
vanillaOverdraw = builder
.comment("\n\n"
+ " How often should LODs be drawn on top of regular chunks? \n"
+ " HALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves. \n\n"
+ " " + VanillaOverdraw.NEVER + ": LODs won't render on top of vanilla chunks. \n"
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n"
+ " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
+ " " + " More effective on higher render distances. \n"
+ " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " may be used depending on the dimension. \n"
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n")
.defineEnum("Vanilla Overdraw", VanillaOverdraw.DYNAMIC);
gpuUploadMethod = builder
.comment("\n\n"
+ " What method should be used to upload geometry to the GPU? \n\\n"
+ ""
+ " " + GpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. \n"
+ " " + GpuUploadMethod.SUB_DATA + ": Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. \n"
+ " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly better than " + GpuUploadMethod.SUB_DATA + " if using a integrated GPU. \n")
.defineEnum("GPU Upload Method", GpuUploadMethod.BUFFER_STORAGE);
// This is a temporary fix (like vanilla overdraw)
// hopefully we can remove both once we get individual chunk rendering figured out
useExtendedNearClipPlane = builder
.comment("\n\n"
+ " Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly \n"
+ " especially when in/near an ocean. \n")
.define("Use Extended Near Clip Plane", false);
builder.pop();
}
}
}
//========================//
// WorldGenerator Configs //
//========================//
public static class WorldGenerator
{
public final ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public final ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public final ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public final ForgeConfigSpec.EnumValue<BlockToAvoid> blockToAvoid;
//public final ForgeConfigSpec.BooleanValue useExperimentalPreGenLoading;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how fake chunks outside your normal view range are generated.").push("Generation");
generationPriority = builder
.comment("\n\n"
+ " " + GenerationPriority.FAR_FIRST + " \n"
+ " LODs are generated from low to high detail \n"
+ " with a small priority for far away regions. \n"
+ " This fills in the world fastest. \n\n"
+ ""
+ " " + GenerationPriority.NEAR_FIRST + " \n"
+ " LODs are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n")
.defineEnum("Generation Priority", GenerationPriority.FAR_FIRST);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
+ " one of the developer's PC to generate 1 chunk, \n"
+ " and are included so you can compare the \n"
+ " different generation options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.NONE + " \n"
+ " Don't run the distance generator. \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY + " \n"
+ " Only generate the biomes and use the biome's \n"
+ " grass color, water color, or snow color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability! \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This will show player made structures, which can \n"
+ " be useful if you are adding the mod to a pre-existing world. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("Distance Generation Mode", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " When using the " + DistanceGenerationMode.FEATURES + " generation mode \n"
+ " some features may not be thread safe, which could \n"
+ " cause instability and crashes. \n"
+ " By default (false) those features are skipped, \n"
+ " improving stability, but decreasing how many features are \n"
+ " actually generated. \n"
+ " (for example: some tree generation is unstable, \n"
+ " so some trees may not be generated.) \n"
+ " By setting this to true, all features will be generated, \n"
+ " but your game will be more unstable and crashes may occur. \n"
+ " \n"
+ " I would love to remove this option and always generate everything, \n"
+ " but I'm not sure how to do that. \n"
+ " If you are a Java wizard, check out the git issue here: \n"
+ " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n")
.define("Allow Unstable Feature Generation", false);
blockToAvoid = builder
.comment("\n\n"
+ " " + BlockToAvoid.NONE + ": Use all blocks when generating fake chunks \n\n"
+ ""
+ " " + BlockToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, grass, etc.) \n\n"
+ ""
+ " " + BlockToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores grass, torches, etc.) \n"
+ ""
+ " " + BlockToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n"
+ "\n")
.defineEnum("Block to avoid", BlockToAvoid.BOTH);
/*useExperimentalPreGenLoading = builder
.comment("\n\n"
+ " if a chunk has been pre-generated, then the mod would use the real chunk for the \n"
+ "fake chunk creation. May require a deletion of the lod file to see the result. \n")
.define("Use pre-generated chunks", false);*/
builder.pop();
}
}
//============================//
// AdvancedModOptions Configs //
//============================//
public static class AdvancedModOptions
{
public final Threading threading;
public final Debugging debugging;
public final Buffers buffers;
public AdvancedModOptions(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced mod settings").push(this.getClass().getSimpleName());
{
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
}
builder.pop();
}
public static class Threading
{
public final ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public final ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " This is how many threads are used when generating LODs outside \n"
+ " the normal render distance. \n"
+ " If you experience stuttering when generating distant LODs, decrease \n"
+ " this number. If you want to increase LOD generation speed, \n"
+ " increase this number. \n\n"
+ ""
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfWorldGenerationThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ " This is how many threads are used when building vertex buffers \n"
+ " (The things sent to your GPU to draw the fake chunks). \n"
+ " If you experience high CPU usage when NOT generating distant \n"
+ " fake chunks, lower this number. \n"
+ " \n"
+ " The maximum value is the number of logical processors on your CPU. \n"
+ " Requires a restart to take effect. \n")
.defineInRange("numberOfBufferBuilderThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
//===============//
// Debug Options //
//===============//
public static class Debugging
{
public final ForgeConfigSpec.BooleanValue drawLods;
public final ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public final ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used to look for bugs, or see how certain aspects of the mod work.").push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ " If true, the mod is enabled and fake chunks will be drawn. \n"
+ " If false, the mod will still generate fake chunks, \n"
+ " but they won't be rendered. \n")
.define("Enable Rendering", true);
debugMode = builder
.comment("\n\n"
+ " " + DebugMode.OFF + ": Fake chunks will be drawn with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL + ": Fake chunks color will be based on their detail level. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": Fake chunks color will be based on their detail level, drawn as a wireframe. \n")
.defineEnum("Debug Mode", DebugMode.OFF);
enableDebugKeybindings = builder
.comment("\n\n"
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
+ " and the F6 key can be used to enable and disable LOD rendering.")
.define("Enable Debug Keybinding", false);
builder.pop();
}
}
public static class Buffers
{
public final ForgeConfigSpec.EnumValue<BufferRebuildTimes> rebuildTimes;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings affect how often geometry is are built.").push(this.getClass().getSimpleName());
rebuildTimes = builder
.comment("\n\n"
+ " How frequently should geometry be rebuilt and sent to the GPU? \n"
+ " Higher settings may cause stuttering, but will prevent holes in the world \n")
.defineEnum("rebuildFrequency", BufferRebuildTimes.NORMAL);
builder.pop();
}
}
}
}
/** {@link Path} to the configuration file of this mod */
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.NAME + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
static
{
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT_SPEC = specPair.getRight();
CLIENT = specPair.getLeft();
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
clientConfig.load();
clientConfig.save();
CLIENT_SPEC.setConfig(clientConfig);
}
@SubscribeEvent
public static void onLoad(final ModConfig.Loading configEvent)
{
LogManager.getLogger().debug(ModInfo.NAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
}
@SubscribeEvent
public static void onFileChange(final ModConfig.Reloading configEvent)
{
LogManager.getLogger().debug(ModInfo.NAME, "Forge config just got changed on the file system!");
}
}
@@ -0,0 +1,47 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @author Leonardo Amato
* @version 19-10-2021
*/
public enum BlockToAvoid
{
NONE(false, false),
NON_FULL(true, false),
NO_COLLISION(false, true),
BOTH(true, true);
public final boolean nonFull;
public final boolean noCollision;
BlockToAvoid(boolean nonFull, boolean noCollision)
{
this.nonFull = nonFull;
this.noCollision = noCollision;
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Near_First <br>
* Far_First <br>
* <br>
* Determines how fast the buffers need to be regenerated
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum BufferRebuildTimes
{
FREQUENT(1000, 500, 2500),
NORMAL(2000, 1000, 5000),
RARE(5000, 2000, 10000);
public final int playerMoveTimeout;
public final int renderedChunkTimeout;
public final int chunkChangeTimeout;
BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout)
{
this.playerMoveTimeout = playerMoveTimeout;
this.renderedChunkTimeout = renderedChunkTimeout;
this.chunkChangeTimeout = chunkChangeTimeout;
}
}
@@ -0,0 +1,54 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* off, detail, detail wireframe
*
* @author James Seibel
* @version 8-28-2021
*/
public enum DebugMode
{
/** LODs are rendered normally */
OFF,
/** LOD colors are based on their detail */
SHOW_DETAIL,
/** LOD colors are based on their detail, and draws in wireframe. */
SHOW_DETAIL_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
static
{
OFF.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
public DebugMode getNext()
{
return this.next;
}
}
@@ -0,0 +1,95 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* NONE <br>
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
* <p>
* In order of fastest to slowest.
*
* @author James Seibel
* @author Leonardo Amato
* @version 8-7-2021
*/
public enum DistanceGenerationMode
{
/**
* Don't generate anything
*/
NONE((byte) 0),
/**
* Only generate the biomes and use biome
* grass/foliage color, water color, or ice color
* to generate the color.
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms)
*/
BIOME_ONLY((byte) 1),
/**
* Same as BIOME_ONLY, except instead
* of always using sea level as the LOD height
* different biome types (mountain, ocean, forest, etc.)
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
/**
* Generate the world surface,
* this does NOT include caves, trees,
* or structures.
* Multithreaded - Faster (10-20 ms)
*/
SURFACE((byte) 3),
/**
* Generate everything except structures.
* NOTE: This may cause world generation bugs or instability,
* since some features cause concurrentModification exceptions.
* Multithreaded - Fast (15-20 ms)
*/
FEATURES((byte) 4),
/**
* Ask the server to generate/load each chunk.
* This is the most compatible, but causes server/simulation lag.
* This will also show player made structures if you
* are adding the mod to a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
SERVER((byte) 5);
/**
* The higher the number the more complete the generation is.
*/
public final byte complexity;
DistanceGenerationMode(byte complexity)
{
this.complexity = complexity;
}
}
@@ -0,0 +1,38 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* NEAR, FAR, or NEAR_AND_FAR.
*
* @author James Seibel
* @version 02-14-2021
*/
public enum FogDistance
{
/** good for fast or fancy fog qualities. */
NEAR,
/** good for fast or fancy fog qualities. */
FAR,
/** only looks good if the fog quality is set to Fancy. */
NEAR_AND_FAR
}
@@ -0,0 +1,47 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* USE_OPTIFINE_FOG_SETTING, <br>
* NEVER_DRAW_FOG, <br>
* ALWAYS_DRAW_FOG_FAST, <br>
* ALWAYS_DRAW_FOG_FANCY <br>
*
* @author James Seibel
* @version 7-3-2021
*/
public enum FogDrawOverride
{
/**
* Use whatever Fog setting optifine is using.
* If optifine isn't installed this defaults to ALWAYS_DRAW_FOG.
*/
OPTIFINE_SETTING,
/** Never draw fog on the LODs */
NO_FOG,
/** Always draw fast fog on the LODs */
FAST,
/** Always draw fancy fog on the LODs */
FANCY
}
@@ -0,0 +1,33 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* fast, fancy, or off
*
* @author James Seibel
* @version 02-14-2021
*/
public enum FogQuality
{
FAST,
FANCY,
OFF
}
@@ -0,0 +1,37 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Near_First <br>
* Far_First <br>
* <br>
* Determines which LODs should have priority when generating
* outside the normal view distance.
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum GenerationPriority
{
NEAR_FIRST,
FAR_FIRST
}
@@ -0,0 +1,38 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Minecraft, Lod_Builder, None
*
* @author James Seibel
* @version 10-1-2021
*/
public enum GlProxyContext
{
/** Minecraft's render thread */
MINECRAFT,
/** The context we send buffers to the GPU on */
LOD_BUILDER,
/** used to un-bind threads */
NONE,
}
@@ -0,0 +1,38 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Buffer_Storage, Sub_Data, Buffer_Mapping
*
* @author James Seibel
* @version 10-23-2021
*/
public enum GpuUploadMethod
{
/** Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. */
BUFFER_STORAGE,
/** Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. */
SUB_DATA,
/** May end up storing buffers in System memory. Slower rendering but won't stutter when uploading. */
BUFFER_MAPPING,
}
@@ -0,0 +1,53 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Lowest <br>
* Low <br>
* Medium <br>
* High <br>
* <br>
* this indicates the base of the quadratic function we use for the quality drop off
*
* @author Leonardo Amato
* @version 9-29-2021
*/
public enum HorizontalQuality
{
/** 1.0 AKA Linear */
LOWEST(1.0f),
/** exponent 1.5 */
LOW(1.5f),
/** exponent 2.0 */
MEDIUM(2.0f),
/** exponent 2.2 */
HIGH(2.2f);
public final double quadraticBase;
HorizontalQuality(double distanceUnit)
{
this.quadraticBase = distanceUnit;
}
}
@@ -0,0 +1,175 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import java.util.ArrayList;
import java.util.Collections;
import com.seibel.lod.util.LodUtil;
/**
* chunk <Br>
* half_chunk <Br>
* four_blocks <br>
* two_blocks <Br>
* block <br>
*
* @author James Seibel
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum HorizontalResolution
{
/** render 256 LODs for each chunk */
BLOCK(16, 0),
/** render 64 LODs for each chunk */
TWO_BLOCKS(8, 1),
/** render 16 LODs for each chunk */
FOUR_BLOCKS(4, 2),
/** render 4 LODs for each chunk */
HALF_CHUNK(2, 3),
/** render 1 LOD for each chunk */
CHUNK(1, 4);
/**
* How many DataPoints should
* be drawn per side, per LodChunk
*/
public final int dataPointLengthCount;
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/**
* This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9
*/
public final byte detailLevel;
/* Start/End X/Z give the block positions
* for each individual dataPoint in a LodChunk */
public final int[] startX;
public final int[] startZ;
public final int[] endX;
public final int[] endZ;
/**
* 1st dimension: LodDetail.detailLevel <br>
* 2nd dimension: An array of all LodDetails that are less than or <br>
* equal to that detailLevel
*/
private static HorizontalResolution[][] lowerDetailArrays;
HorizontalResolution(int newLengthCount, int newDetailLevel)
{
detailLevel = (byte) newDetailLevel;
dataPointLengthCount = newLengthCount;
dataPointWidth = 16 / dataPointLengthCount;
startX = new int[dataPointLengthCount * dataPointLengthCount];
endX = new int[dataPointLengthCount * dataPointLengthCount];
startZ = new int[dataPointLengthCount * dataPointLengthCount];
endZ = new int[dataPointLengthCount * dataPointLengthCount];
int index = 0;
for (int x = 0; x < newLengthCount; x++)
{
for (int z = 0; z < newLengthCount; z++)
{
startX[index] = x * dataPointWidth;
startZ[index] = z * dataPointWidth;
endX[index] = (x * dataPointWidth) + dataPointWidth;
endZ[index] = (z * dataPointWidth) + dataPointWidth;
index++;
}
}
}// constructor
/**
* Returns an array of all LodDetails that have a detail level
* that is less than or equal to the given LodDetail
*/
public static HorizontalResolution[] getSelfAndLowerDetails(HorizontalResolution detail)
{
if (lowerDetailArrays == null)
{
// run first time setup
lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][];
// go through each LodDetail
for (HorizontalResolution currentDetail : HorizontalResolution.values())
{
ArrayList<HorizontalResolution> lowerDetails = new ArrayList<>();
// find the details lower than currentDetail
for (HorizontalResolution compareDetail : HorizontalResolution.values())
{
if (currentDetail.detailLevel <= compareDetail.detailLevel)
{
lowerDetails.add(compareDetail);
}
}
// have the highest detail item first in the list
Collections.sort(lowerDetails);
Collections.reverse(lowerDetails);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new HorizontalResolution[lowerDetails.size()]);
}
}
return lowerDetailArrays[detail.detailLevel];
}
/** Returns what detail level should be used at a given distance and maxDistance. */
public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance)
{
HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
int distanceBetweenDetails = maxDistance / lowerDetails.length;
int index = LodUtil.clamp(0, distance / distanceBetweenDetails, lowerDetails.length - 1);
return lowerDetails[index];
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Low <br>
* Medium <br>
* High <br>
* <br>
* this is a quality scale for the detail drop-off
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public enum HorizontalScale
{
/** Lods are 2D with heightMap */
LOW(64),
/** Lods expand in three dimension */
MEDIUM(128),
/** Lods expand in three dimension */
HIGH(256);
public final int distanceUnit;
HorizontalScale(int distanceUnit)
{
this.distanceUnit = distanceUnit;
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
/**
* Cubic, Triangular, Dynamic
*
* @author James Seibel
* @version 10-10-2021
*/
public enum LodTemplate
{
/**
* LODs are rendered as
* rectangular prisms.
*/
CUBIC(new CubicLodTemplate()),
/**
* LODs smoothly transition between
* each other.
*/
TRIANGULAR(new TriangularLodTemplate()),
/**
* LODs smoothly transition between
* each other, unless a neighboring LOD
* is at a significantly different height.
*/
DYNAMIC(new DynamicLodTemplate());
public final AbstractLodTemplate template;
LodTemplate(AbstractLodTemplate newTemplate)
{
template = newTemplate;
}
}
@@ -0,0 +1,22 @@
package com.seibel.lod.enums;
/**
* NONE, GAME_SHADING
*
* @author James Seibel
* @version 7-25-2020
*/
public enum ShadingMode
{
/**
* LODs will have darker sides and bottoms to simulate
* Minecraft's fast lighting.
*/
GAME_SHADING,
/**
* LODs will use ambient occlusion to mimic Minecarft's
* Fancy lighting.
*/
AMBIENT_OCCLUSION
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* None, Dynamic, Always
*
* <p>
* This represents how far the LODs should overlap with
* the vanilla Minecraft terrain.
*
* @author James Seibel
* @version 10-11-2021
*/
public enum VanillaOverdraw
{
/** Never draw LODs where a minecraft chunk could be. */
NEVER,
/** Draw LODs over the farther minecraft chunks. */
DYNAMIC,
/** Draw LODs over all minecraft chunks. */
ALWAYS,
/** Draw LODs over border chunks. */
BORDER,
}
@@ -0,0 +1,80 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @author Leonardo Amato
* @version 10-07-2021
*/
public enum VerticalQuality
{
LOW(
new int[] { 2,
2,
2,
2,
1,
1,
1,
1,
1,
1,
1 }
),
MEDIUM(
new int[] { 4,
4,
2,
2,
2,
1,
1,
1,
1,
1,
1 }
),
HIGH(
new int[] {
8,
8,
4,
4,
2,
2,
2,
1,
1,
1,
1 }
);
public final int[] maxVerticalData;
VerticalQuality(int[] maxVerticalData)
{
this.maxVerticalData = maxVerticalData;
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.File;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.chunk.storage.ChunkSerializer;
import net.minecraft.world.server.ServerWorld;
/**
*
* @author ??
* @version ??
*/
public class ChunkLoader
{
public static IChunk getChunkFromFile(ChunkPos pos){
ClientWorld clientWorld = MinecraftWrapper.INSTANCE.getClientWorld();
if (clientWorld == null)
return null;
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(clientWorld.dimensionType());
try
{
File file = new File(serverWorld.getChunkSource().getDataStorage().dataFolder.getParent() + File.separatorChar + "region", "r." + (pos.x >> 5) + "." + (pos.z >> 5) + ".mca");
if(!file.exists())
return null;
IChunk loadedChunk = ChunkSerializer.read(
serverWorld,
serverWorld.getStructureManager(),
serverWorld.getPoiManager(),
pos,
serverWorld.getChunkSource().chunkMap.read(pos)
);
boolean emptyChunk = true;
for(int i = 0; i < 16; i++){
for(int j = 0; j < 16; j++){
emptyChunk &= loadedChunk.isYSpaceEmpty(i,j);
}
}
if(emptyChunk)
return null;
else
return loadedChunk;
}
catch (Exception e)
{
return null;
}
}
}
@@ -0,0 +1,401 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.VerticalLevelContainer;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
* This object handles creating LodRegions
* from files and saving LodRegion objects
* to file.
*
* @author James Seibel
* @author Cola
* @version 9-25-2021
*/
public class LodDimensionFileHandler
{
/** This is the dimension that owns this file handler */
private LodDimension lodDimension;
private final File dimensionDataSaveFolder;
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private static final String FILE_EXTENSION = ".xz";
/** detail- */
private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
/**
* .tmp <br>
* Added to the end of the file path when saving to prevent
* nulling a currently existing file. <br>
* After the file finishes saving it will end with
* FILE_EXTENSION.
*/
private static final String TMP_FILE_EXTENSION = ".tmp";
/**
* This is the file version currently accepted by this
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read.
*/
public static final int LOD_SAVE_FILE_VERSION = 6;
/**
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
{
if (newSaveFolder == null)
throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
dimensionDataSaveFolder = newSaveFolder;
lodDimension = newLodDimension;
}
//================//
// read from file //
//================//
/**
* Returns the LodRegion at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
try
{
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
File file = new File(fileName);
if (!file.exists())
{
//there is no file for current gen mode
//search others above current from the most to the least detailed
DistanceGenerationMode tempGenMode = DistanceGenerationMode.SERVER;
while (tempGenMode != generationMode)
{
fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality);
if (fileName != null)
{
file = new File(fileName);
if (file.exists())
break;
}
//decrease gen mode
if (tempGenMode == DistanceGenerationMode.SERVER)
tempGenMode = DistanceGenerationMode.FEATURES;
else if (tempGenMode == DistanceGenerationMode.FEATURES)
tempGenMode = DistanceGenerationMode.SURFACE;
else if (tempGenMode == DistanceGenerationMode.SURFACE)
tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT)
tempGenMode = DistanceGenerationMode.BIOME_ONLY;
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY)
tempGenMode = DistanceGenerationMode.NONE;
}
if (!file.exists())
//there wasn't a file, don't return anything
continue;
}
// don't try parsing empty files
long dataSize = file.length();
dataSize -= 1;
if (dataSize > 0)
{
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
{
int fileVersion;
fileVersion = inputStream.read();
// check if this file can be read by this file handler
if (fileVersion < LOD_SAVE_FILE_VERSION)
{
// the file we are reading is an older version,
// close the reader and delete the file.
inputStream.close();
file.delete();
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ ". File was been deleted.");
break;
}
else if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidentally delete anything the user may want.
inputStream.close();
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ " version found: " + fileVersion
+ ", version requested: " + LOD_SAVE_FILE_VERSION
+ " this region will not be written to in order to protect the newer file.");
break;
}
// this file is a readable version,
// read the file
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
inputStream.read(data);
inputStream.close();
// add the data to our region
region.addLevelContainer(new VerticalLevelContainer(data));
}
catch (IOException ioEx)
{
ClientProxy.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
ioEx.printStackTrace();
}
}
}
catch (Exception e)
{
// the buffered reader encountered a
// problem reading the file
ClientProxy.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}// for each detail level
if (region.getMinDetailLevel() >= detailLevel)
region.growTree(detailLevel);
return region;
}
//==============//
// Save to File //
//==============//
/**
* Save all dirty regions in this LodDimension to file.
*/
public void saveDirtyRegionsToFileAsync()
{
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private final Thread saveDirtyRegionsThread = new Thread(() ->
{
try
{
for (int i = 0; i < lodDimension.getWidth(); i++)
{
for (int j = 0; j < lodDimension.getWidth(); j++)
{
if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
lodDimension.SetIsRegionDirty(i, j, false);
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
});
/**
* Save a specific region to disk.<br>
* Note: <br>
* 1. If a file already exists for a newer version
* the file won't be written.<br>
* 2. This will save to the LodDimension that this
* handler is associated with.
*/
private void saveRegionToFile(LodRegion region)
{
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
{
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
// if the fileName was null that means the folder is inaccessible
// for some reason
if (fileName == null)
{
ClientProxy.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
return;
}
File oldFile = new File(fileName);
//ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
try
{
// make sure the file and folder exists
if (!oldFile.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
oldFile.createNewFile();
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
int fileVersion = LOD_SAVE_FILE_VERSION;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
inputStream.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
// check if this file can be written to by the file handler
if (fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidentally
// delete anything the user may want.
return;
}
// if we got this far then we are good
// to overwrite the old file
}
// the old file is good, now create a new temporary save file
File newFile = new File(fileName + TMP_FILE_EXTENSION);
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
{
// add the version of this file
outputStream.write(LOD_SAVE_FILE_VERSION);
// add each LodChunk to the file
outputStream.write(region.getLevel(detailLevel).toDataString());
outputStream.close();
// overwrite the old file with the new one
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
catch (Exception e)
{
ClientProxy.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
//================//
// helper methods //
//================//
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
* Returns null if this object isn't available to read and write. <br><br>
* <p>
* example: "lod.0.0.txt" <br>
* <p>
* Returns null if there is an IO or security Exception.
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data\"
// or
// ".\Super Flat\data\"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
verticalQuality + File.separatorChar +
generationMode.toString() + File.separatorChar +
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch (IOException | SecurityException e)
{
ClientProxy.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
e.printStackTrace();
return null;
}
}
}
@@ -0,0 +1,165 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import com.seibel.lod.enums.FogQuality;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.math.vector.Matrix4f;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* This object is used to get variables from methods
* where they are private. Specifically the fog setting
* in Optifine.
* @author James Seibel
* @version 9 -25-2021
*/
public class ReflectionHandler
{
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public Field ofFogField = null;
public Method vertexBufferUploadMethod = null;
private ReflectionHandler()
{
setupFogField();
}
/**
* finds the Optifine fog type field
*/
private void setupFogField()
{
// get every variable from the entity renderer
Field[] optionFields = mc.getOptions().getClass().getDeclaredFields();
// try and find the ofFogType variable in gameSettings
for (Field field : optionFields)
{
if (field.getName().equals("ofFogType"))
{
ofFogField = field;
return;
}
}
// we didn't find the field,
// either optifine isn't installed, or
// optifine changed the name of the variable
ClientProxy.LOGGER.info(ReflectionHandler.class.getSimpleName() + ": unable to find the Optifine fog field. If Optifine isn't installed this can be ignored.");
}
/**
* Get what type of fog optifine is currently set to render.
* @return the fog quality
*/
public FogQuality getFogQuality()
{
if (ofFogField == null)
{
// either optifine isn't installed,
// the variable name was changed, or
// the setup method wasn't called yet.
return FogQuality.FANCY;
}
int returnNum = 0;
try
{
returnNum = (int) ofFogField.get(mc.getOptions());
}
catch (IllegalArgumentException | IllegalAccessException e)
{
e.printStackTrace();
}
switch (returnNum)
{
default:
case 0:
// optifine's "default" option,
// it should never be called in this case
// normal options
case 1:
return FogQuality.FAST;
case 2:
return FogQuality.FANCY;
case 3:
return FogQuality.OFF;
}
}
/** Detect if Vivecraft is present using reflection. Attempts to find the "VRRenderer" class. */
public boolean detectVivecraft()
{
try
{
Class.forName("org.vivecraft.provider.VRRenderer");
return true;
}
catch (ClassNotFoundException ignored)
{
System.out.println("Vivecraft not detected.");
}
return false;
}
/**
* Modifies a projection matrix's clip planes.
* The projection matrix must be in a column-major format.
* @param projectionMatrix The projection matrix to be modified.
* @param n the new near clip plane value.
* @param f the new far clip plane value.
* @return The modified matrix.
*/
public Matrix4f Matrix4fModifyClipPlanes(Matrix4f projectionMatrix, float n, float f)
{
//find the matrix values.
float nMatrixValue = -((f + n) / (f - n));
float fMatrixValue = -((2 * f * n) / (f - n));
try
{
//get the fields of the projectionMatrix
Field[] fields = projectionMatrix.getClass().getDeclaredFields();
//bypass the security protections on the fields that encode near and far plane values.
fields[10].setAccessible(true);
fields[11].setAccessible(true);
//Change the values of the near and far plane.
fields[10].set(projectionMatrix, nMatrixValue);
fields[11].set(projectionMatrix, fMatrixValue);
}
catch (Exception e)
{
e.printStackTrace();
}
return projectionMatrix;
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.LodMain;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.WorldRenderer;
/**
* This class is used to mix in my rendering code
* before Minecraft starts rendering blocks.
* If this wasn't done, and we used Forge's
* render last event, the LODs would render on top
* of the normal terrain.
*
* @author James Seibel
* @version 9-19-2021
*/
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer
{
private static float previousPartialTicks = 0;
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V")
private void renderSky(MatrixStack matrixStackIn, float partialTicks, CallbackInfo callback)
{
// get the partial ticks since renderBlockLayer doesn't
// have access to them
previousPartialTicks = partialTicks;
}
@Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V")
private void renderChunkLayer(RenderType renderType, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
{
// only render if LODs are enabled and
// only render before solid blocks
if (renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(matrixStackIn, previousPartialTicks);
}
}
@@ -0,0 +1,115 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
/**
* A level container is a quad tree level
*/
public interface LevelContainer
{
/**
* With this you can add data to the level container
* @param data actual data to add in an array of long format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @param index z position in the detail level
* @return true if correctly added, false otherwise
*/
boolean addData(long data, int posX, int posZ, int index);
/**
* With this you can add data to the level container
* @param data actual data to add in an array of long[] format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly added, false otherwise
*/
boolean addVerticalData(long[] data, int posX, int posZ);
/**
* With this you can add data to the level container
* @param data actual data to add in an array of long format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly added, false otherwise
*/
boolean addSingleData(long data, int posX, int posZ);
/**
* With this you can get data from the level container
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
long getData(int posX, int posZ, int index);
/**
* With this you can get data from the level container
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return the data in long array format
*/
long getSingleData(int posX, int posZ);
/**
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true only if the data exist
*/
boolean doesItExist(int posX, int posZ);
/**
* @return return the detailLevel of this level container
*/
byte getDetailLevel();
int getMaxVerticalData();
/** Clears the dataPoint at the given array index */
void clear(int posX, int posZ);
/**
* This return a level container with detail level lower than the current level.
* The new level container may use information of this level.
* @return the new level container
*/
LevelContainer expand();
/**
* @param lowerLevelContainer lower level where we extract the data
* @param posX x position in the detail level to update
* @param posZ z position in the detail level to update
*/
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
/**
* This will give the data to save in the file
* @return data as a String
*/
byte[] toDataString();
/**
* This will give the data to save in the file
* @return data as a String
*/
int getMaxNumberOfLods();
}
@@ -0,0 +1,905 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.DimensionType;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
/**
* This object holds all loaded LOD regions
* for a given dimension. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @author Leonardo Amato
* @author James Seibel
* @version 10-10-2021
*/
public class LodDimension
{
public final DimensionType dimension;
/** measured in regions */
private volatile int width;
/** measured in regions */
private volatile int halfWidth;
// these three variables are private to force use of the getWidth() method
// which is a safer way to get the width then directly asking the arrays
/** stores all the regions in this dimension */
public volatile LodRegion[][] regions;
/** stores if the region at the given x and z index needs to be saved to disk */
private volatile boolean[][] isRegionDirty;
/** stores if the region at the given x and z index needs to be regenerated */
private volatile boolean[][] regenRegionBuffer;
/** stores if the buffer size at the given x and z index needs to be changed */
private volatile boolean[][] recreateRegionBuffer;
/**
* if true that means there are regions in this dimension
* that need to have their buffers rebuilt.
*/
public volatile boolean regenDimensionBuffers = false;
private LodDimensionFileHandler fileHandler;
private final RegionPos center;
/** prevents the cutAndExpandThread from expanding at the same location multiple times */
private volatile ChunkPos lastExpandedChunk;
/** prevents the cutAndExpandThread from cutting at the same location multiple times */
private volatile ChunkPos lastCutChunk;
private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand"));
/**
* Creates the dimension centered at (0,0)
* @param newWidth in regions
*/
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newWidth)
{
lastCutChunk = null;
lastExpandedChunk = null;
dimension = newDimension;
width = newWidth;
halfWidth = width / 2;
MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
if (newDimension != null && lodWorld != null)
{
try
{
File saveDir;
if (mc.hasSinglePlayerServer())
{
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
// provider needs a separate variable to prevent
// the compiler from complaining
ServerChunkProvider provider = serverWorld.getChunkSource();
saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod");
}
else
{
// connected to server
saveDir = new File(mc.getGameDirectory().getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + mc.getCurrentDimensionId());
}
fileHandler = new LodDimensionFileHandler(saveDir, this);
}
catch (IOException e)
{
// the file handler wasn't able to be created
// we won't be able to read or write any files
}
}
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
regenRegionBuffer = new boolean[width][width];
recreateRegionBuffer = new boolean[width][width];
center = new RegionPos(0, 0);
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset. <br><br>
* <p>
* Synchronized to prevent multiple moves happening on top of each other.
*/
public synchronized void move(RegionPos regionOffset)
{
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
// if the x or z offset is equal to or greater than
// the total width, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++)
regions[x][z] = null;
// update the new center
center.x += xOffset;
center.z += zOffset;
return;
}
// X
if (xOffset > 0)
{
// move everything over to the left (as the center moves to the right)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset < width)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
else
{
// move everything over to the right (as the center moves to the left)
for (int x = width - 1; x >= 0; x--)
{
for (int z = 0; z < width; z++)
{
if (x + xOffset >= 0)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
// Z
if (zOffset > 0)
{
// move everything up (as the center moves down)
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
if (z + zOffset < width)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
else
{
// move everything down (as the center moves up)
for (int x = 0; x < width; x++)
{
for (int z = width - 1; z >= 0; z--)
{
if (z + zOffset >= 0)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
// update the new center
center.x += xOffset;
center.z += zOffset;
}
/**
* Gets the region at the given LevelPos
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(byte detailLevel, int levelPosX, int levelPosZ)
{
int xRegion = LevelPosUtil.getRegion(detailLevel, levelPosX);
int zRegion = LevelPosUtil.getRegion(detailLevel, levelPosZ);
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
if (!regionIsInRange(xRegion, zRegion))
return null;
// throw new ArrayIndexOutOfBoundsException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " out of range");
else if (regions[xIndex][zIndex] == null)
return null;
else if (regions[xIndex][zIndex].getMinDetailLevel() > detailLevel)
return null;
//throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
return regions[xIndex][zIndex];
}
/**
* Gets the region at the given X and Z
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(int regionPosX, int regionPosZ)
{
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(regionPosX, regionPosZ))
return null;
//throw new ArrayIndexOutOfBoundsException("Region " + regionPosX + " " + regionPosZ + " out of range");
return regions[xIndex][zIndex];
}
/** Useful when iterating over every region. */
public LodRegion getRegionByArrayIndex(int xIndex, int zIndex)
{
return regions[xIndex][zIndex];
}
/**
* Overwrite the LodRegion at the location of newRegion with newRegion.
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.regionPosX - center.x) + halfWidth;
int zIndex = (newRegion.regionPosZ - center.z) + halfWidth;
if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
// out of range
throw new ArrayIndexOutOfBoundsException("Region " + newRegion.regionPosX + ", " + newRegion.regionPosZ + " out of range");
regions[xIndex][zIndex] = newRegion;
}
/**
* Deletes nodes that are a higher detail then necessary, freeing
* up memory.
*/
public void cutRegionNodesAsync(int playerPosX, int playerPosZ)
{
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
if (lastCutChunk == null)
lastCutChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
// don't run the tree cutter multiple times
// for the same location
if (newPlayerChunk.x != lastCutChunk.x || newPlayerChunk.z != lastCutChunk.z)
{
lastCutChunk = newPlayerChunk;
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
int minDistance;
byte detail;
byte minAllowedDetailLevel;
// go over every region in the dimension
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
if (regions[x][z] != null)
{
// check what detail level this region should be
// and cut it if it is higher then that
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel)
{
regions[x][z].cutTree(minAllowedDetailLevel);
recreateRegionBuffer[x][z] = true;
}
}
}// region z
}// region z
});
cutAndExpandThread.execute(thread);
}
}
/** Either expands or loads all regions in the rendered LOD area */
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
{
DistanceGenerationMode generationMode = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
if (lastExpandedChunk == null)
lastExpandedChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
// don't run the expander multiple times
// for the same location
if (newPlayerChunk.x != lastExpandedChunk.x || newPlayerChunk.z != lastExpandedChunk.z)
{
lastExpandedChunk = newPlayerChunk;
Thread thread = new Thread(() ->
{
int regionX;
int regionZ;
LodRegion region;
int minDistance;
byte detail;
byte levelToGen;
for (int x = 0; x < regions.length; x++)
{
for (int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
final RegionPos regionPos = new RegionPos(regionX, regionZ);
region = regions[x][z];
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
// check that the region isn't null and at least this detail level
if (region == null || region.getGenerationMode() != generationMode)
{
// First case, region has to be created
// try to get the region from file
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
// if there is no region file create an empty region
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
regenRegionBuffer[x][z] = true;
regenDimensionBuffers = true;
recreateRegionBuffer[x][z] = true;
}
else if (region.getMinDetailLevel() > levelToGen)
{
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
recreateRegionBuffer[x][z] = true;
}
}
}
});
cutAndExpandThread.execute(thread);
}
}
/**
* Use addVerticalData when possible.
* Add the given LOD to this dimension at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinate it will be overwritten.
*/
public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave)
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
// don't continue if the region can't be saved
LodRegion region = getRegion(regionPosX, regionPosZ);
if (region == null)
return false;
boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data);
// only save valid LODs to disk
if (!dontSave && fileHandler != null)
{
try
{
// mark the region as dirty, so it will be saved to disk
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
regenRegionBuffer[xIndex][zIndex] = true;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
// If this happens, the method was probably
// called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
return nodeAdded;
}
/**
* Add whole column of LODs to this dimension at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinate it will be overwritten.
*/
public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave)
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
// don't continue if the region can't be saved
LodRegion region = getRegion(regionPosX, regionPosZ);
if (region == null)
return false;
boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, data);
// only save valid LODs to disk
if (!dontSave && fileHandler != null)
{
try
{
// mark the region as dirty, so it will be saved to disk
int xIndex = (regionPosX - center.x) + halfWidth;
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
regenRegionBuffer[xIndex][zIndex] = true;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
// If this happens, the method was probably
// called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
return nodeAdded;
}
/** marks the region at the given region position to have its buffer rebuilt */
public void markRegionBufferToRegen(int xRegion, int zRegion)
{
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
regenRegionBuffer[xIndex][zIndex] = true;
}
/**
* Returns every position that need to be generated based on the position of the player
*/
public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ)
{
PosToGenerateContainer posToGenerate;
LodRegion lodRegion;
// all the following values are used for the spiral matrix visit
// x and z are the matrix coord
// dx and dz is the next move on the coordinate in the range -1 0 +1
int x, z, dx, dz, t;
x = 0;
z = 0;
dx = 0;
dz = -1;
// We can use two type of generation scheduling
switch (LodConfig.CLIENT.worldGenerator.generationPriority.get())
{
default:
case NEAR_FIRST:
//in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player
//the chunk position to generate will be stored in a posToGenerate object
posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX);
int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ);
int complexity;
int xChunkToCheck;
int zChunkToCheck;
byte detailLevel;
int posX;
int posZ;
long data;
int numbChunksWide = (width) * 32;
int circleLimit = Integer.MAX_VALUE;
//posToGenerate is using an insertion sort algorithm which can become really fast if the
//original data order is almost ordered. For this reason we explore the matrix of the position to generate
//with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order)
for (int i = 0; i < numbChunksWide * numbChunksWide; i++)
{
//Firstly we check if the posToGenerate has been filled
if (maxDataToGenerate == 0)
{
maxDataToGenerate--;
//if it has been filled then we set a stop distance
//the stop distance will be current distance (generically x) per square root of 2
//this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the
//radius of a circle that inscribe a square
circleLimit = (int) (Math.abs(x) * 1.41f);
}
//This second if check if we reached the circleLimit decided in the previous if
//if so we stop
else if (maxDataToGenerate < 0)
{
if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z))
break;
}
xChunkToCheck = x + playerChunkX;
zChunkToCheck = z + playerChunkZ;
//we get the lod region in which the chunk is present
lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
if (lodRegion == null)
continue;
//Now we check if the current chunk has been generated with the correct complexity
//if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck))
// complexity = DistanceGenerationMode.SERVER.complexity;
//else
complexity = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get().complexity;
//we create the level position info of the chunk
detailLevel = lodRegion.getMinDetailLevel();
posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel);
posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel);
data = getSingleData(detailLevel, posX, posZ);
//we will generate the position only if the current generation complexity is lower than the target one.
//an un-generated area will always have 0 generation
if (DataPointUtil.getGenerationMode(data) < complexity)
{
posToGenerate.addPosToGenerate(detailLevel, posX, posZ);
if (maxDataToGenerate >= 0)
maxDataToGenerate--;
}
//with this code section we find the next chunk to check
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
dx = -dz;
dz = t;
}
x += dx;
z += dz;
}
break;
case FAR_FIRST:
//in the FAR_FIRST generation we dedicate part of the generation process to the far region with really
//low detail quality.
posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
int xRegion;
int zRegion;
for (int i = 0; i < width * width; i++)
{
xRegion = x + center.x;
zRegion = z + center.z;
//All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
lodRegion = getRegion(xRegion, zRegion);
if (lodRegion != null)
lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ);
//with this code section we find the next chunk to check
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
{
t = dx;
dx = -dz;
dz = t;
}
x += dx;
z += dz;
}
break;
}
return posToGenerate;
}
/**
* Fills the posToRender with the position to render for the regionPos given in input
*/
public void getPosToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
int playerPosZ)
{
LodRegion region = getRegion(regionPos.x, regionPos.z);
if (region != null)
region.getPosToRender(posToRender, playerPosX, playerPosZ, LodConfig.CLIENT.worldGenerator.generationPriority.get() == GenerationPriority.NEAR_FIRST);
}
/**
* Determines how many vertical LODs could be used
* for the given region at the given detail level
*/
public int getMaxVerticalData(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getMaxVerticalData given a level of [" + detailLevel + "] when [" + LodUtil.REGION_DETAIL_LEVEL + "] is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return 0;
return region.getMaxVerticalData(detailLevel);
}
/**
* Get the data point at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return DataPointUtil.EMPTY_DATA;
return region.getData(detailLevel, posX, posZ, verticalIndex);
}
/**
* Get the data point at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public long getSingleData(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return DataPointUtil.EMPTY_DATA;
return region.getSingleData(detailLevel, posX, posZ);
}
/** Clears the given region */
public void clear(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return;
region.clear(detailLevel, posX, posZ);
}
/**
* Returns if the buffer at the given array index needs
* to have its buffer regenerated.
*/
public boolean doesRegionNeedBufferRegen(int xIndex, int zIndex)
{
return regenRegionBuffer[xIndex][zIndex] || recreateRegionBuffer[xIndex][zIndex];
}
/**
* Sets if the buffer at the given array index needs
* to have its buffer regenerated.
*/
public void setRegenRegionBufferByArrayIndex(int xIndex, int zIndex, boolean newRegen)
{
regenRegionBuffer[xIndex][zIndex] = newRegen;
}
/**
* Get the data point at the given LevelPos
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public void updateData(byte detailLevel, int posX, int posZ)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(detailLevel, posX, posZ);
if (region == null)
return;
region.updateArea(detailLevel, posX, posZ);
}
/** Returns true if a region exists at the given LevelPos */
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
LodRegion region = getRegion(detailLevel, posX, posZ);
return region != null && region.doesDataExist(detailLevel, posX, posZ);
}
/**
* Loads the region at the given RegionPos from file,
* if a file exists for that region.
*/
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel,
DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null;
}
/** Save all dirty regions in this LodDimension to file. */
public void saveDirtyRegionsToFileAsync()
{
fileHandler.saveDirtyRegionsToFileAsync();
}
/** Return true if the chunk has been pregenerated in game */
//public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
//{
//
// LodRegion region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos, zChunkPos);
// if (region == null)
// return false;
//
// return region.isChunkPreGenerated(xChunkPos, zChunkPos);
//}
/**
* Returns whether the region at the given RegionPos
* is within the loaded range.
*/
public boolean regionIsInRange(int regionX, int regionZ)
{
int xIndex = (regionX - center.x) + halfWidth;
int zIndex = (regionZ - center.z) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
}
/** Returns the dimension's center region position X value */
public int getCenterRegionPosX()
{
return center.x;
}
/** Returns the dimension's center region position Z value */
public int getCenterRegionPosZ()
{
return center.z;
}
/** returns the width of the dimension in regions */
public int getWidth()
{
// we want to get the length directly from the
// source to make sure it is in sync with region
// and isRegionDirty
return regions != null ? regions.length : width;
}
/** Update the width of this dimension, in regions */
public void setRegionWidth(int newWidth)
{
width = newWidth;
halfWidth = width/ 2;
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
regenRegionBuffer = new boolean[width][width];
recreateRegionBuffer = new boolean[width][width];
// populate isRegionDirty
for (int i = 0; i < width; i++)
for (int j = 0; j < width; j++)
isRegionDirty[i][j] = false;
}
@Override
public String toString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Dimension : \n");
for (LodRegion[] lodRegions : regions)
{
for (LodRegion region : lodRegions)
{
if (region == null)
stringBuilder.append("n");
else
stringBuilder.append(region.getMinDetailLevel());
stringBuilder.append("\t");
}
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
public boolean GetIsRegionDirty(int i, int j)
{
return isRegionDirty[i][j];
}
public void SetIsRegionDirty(int i, int j, boolean val)
{
isRegionDirty[i][j] = val;
}
}
@@ -0,0 +1,609 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* This object holds all loaded LevelContainers acting as a quad tree
* for a given region. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @author Leonardo Amato
* @version 10-10-2021
*/
public class LodRegion
{
/** Number of detail level supported by a region */
private static final byte POSSIBLE_LOD = 10;
/** Holds the lowest (least detailed) detail level in this region */
private byte minDetailLevel;
/**
* This holds all data for this region
*/
private final LevelContainer[] dataContainer;
/** This chunk Pos has been generated */
//private final boolean[] preGeneratedChunkPos;
/** the generation mode for this region */
private final DistanceGenerationMode generationMode;
/** the vertical quality of this region */
private final VerticalQuality verticalQuality;
/** this region's x RegionPos */
public final int regionPosX;
/** this region's z RegionPos */
public final int regionPosZ;
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.verticalQuality = verticalQuality;
this.generationMode = generationMode;
dataContainer = new LevelContainer[POSSIBLE_LOD];
// Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
dataContainer[lod] = new VerticalLevelContainer(lod);
}
boolean fileFound = false;
/*
preGeneratedChunkPos = new boolean[32 * 32];
if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
{
File regionFileDirHead;
File regionFileDirParent;
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension());
// provider needs a separate variable to prevent
// the compiler from complaining
StringBuilder string = new StringBuilder();
try
{
ServerChunkProvider provider = serverWorld.getChunkSource();
//System.out.println(provider.dataStorage.dataFolder);
regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca");
if (regionFileDirHead.exists())
{
regionFileDirParent = regionFileDirHead.getParentFile();
//string.append(regionFileDirParent.toString());
string.append(regionFileDirHead);
RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true);
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z));
}
}
string.append("region " + regionPosX + " " + regionPosZ + "\n");
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
//regionFile.doesChunkExist()
string.append(preGeneratedChunkPos[x * 32 + z] + "\t");
}
string.append("\n");
}
regionFile.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(string);
}*/
}
/** Return true if the chunk has been pregenerated in game */
//public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
//{
// xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos);
// zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos);
// return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos];
//}
/**
* Inserts the data point into the region.
* <p>
* TODO this will always return true unless it has
* @return true if the data was added successfully
*/
public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
{
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
}
this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
return true;
}
/**
* Inserts the vertical data into the region.
* <p>
* TODO this will always return true unless it has
* @return true if the data was added successfully
*/
public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data)
{
//position is already relative
//posX = LevelPosUtil.getRegionModule(detailLevel, posX);
//posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ);
}
/**
* Get the dataPoint at the given relative position.
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
{
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
/**
* Get the dataPoint at the given relative position.
* @return the data at the relative pos and detail level,
* 0 if the data doesn't exist.
*/
public long getSingleData(byte detailLevel, int posX, int posZ)
{
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
/**
* Clears the datapoint at the given relative position
*/
public void clear(byte detailLevel, int posX, int posZ)
{
dataContainer[detailLevel].clear(posX, posZ);
}
/**
* This method will fill the posToGenerate array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
public void getPosToGenerate(PosToGenerateContainer posToGenerate,
int playerBlockPosX, int playerBlockPosZ)
{
getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
}
/**
* A recursive method that fills the posToGenerate array with all levelPos that
* need to be generated.
* <p>
* TODO why don't we return the posToGenerate, it would make this easier to understand
*/
private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
// calculate what LevelPos are in range to generate
int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
// determine this child's levelPos
byte childDetailLevel = (byte) (detailLevel - 1);
int childPosX = childOffsetPosX * 2;
int childPosZ = childOffsetPosZ * 2;
int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
if (targetDetailLevel <= detailLevel)
{
if (targetDetailLevel == detailLevel)
{
if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
}
else
{
// we want at max one request per chunk (since the world generator creates chunks).
// So for lod smaller than a chunk, only recurse down
// the top right child
if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
{
int ungeneratedChildren = 0;
// make sure all children are generated to this detailLevel
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
ungeneratedChildren++;
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
}
}
}
// only if all the children are correctly generated
// should we go deeper
if (ungeneratedChildren == 0)
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
}
else
{
// The detail Level is smaller than a chunk.
// Only recurse down the top right child.
if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
{
if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
else
getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
}
}
}
}
// we have gone beyond the target Detail level
// we can stop generating
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to understand
*/
public void getPosToRender(PosToRenderContainer posToRender,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
/**
* This method will fill the posToRender array with all levelPos that
* are render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to understand
* TODO this needs some more comments, James was only able to figure out part of it
*/
private void getPosToRender(PosToRenderContainer posToRender,
byte detailLevel, int posX, int posZ,
int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
{
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
byte desiredLevel;
int maxDistance;
int minDistance;
int childLevel;
// calculate the LevelPos that are in range
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
if (detailLevel == childLevel - 1)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
else
//if (desiredLevel > detailLevel)
//{
// we have gone beyond the target Detail level
// we can stop generating
//} else
if (desiredLevel == detailLevel)
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
else //case where (detailLevel > desiredLevel)
{
int childPosX = posX * 2;
int childPosZ = posZ * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
int childrenCount = 0;
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
{
if (!requireCorrectDetailLevel)
childrenCount++;
else
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
}
}
if (!requireCorrectDetailLevel)
{
// If all the four children exist go deeper
if (childrenCount == 4)
{
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
}
else
{
posToRender.addPosToRender(detailLevel,
posX + regionPosX * size,
posZ + regionPosZ * size);
}
}
}
}
/**
* Updates all children.
* <p>
* TODO could this be renamed mergeArea?
*/
public void updateArea(byte detailLevel, int posX, int posZ)
{
int width;
int startX;
int startZ;
// TODO what are each of these loops updating?
for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++)
{
startX = LevelPosUtil.convert(detailLevel, posX, down);
startZ = LevelPosUtil.convert(detailLevel, posZ, down);
width = 1 << (detailLevel - down);
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++)
update(down, startX + x, startZ + z);
}
for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
{
update(up,
LevelPosUtil.convert(detailLevel, posX, up),
LevelPosUtil.convert(detailLevel, posZ, up));
}
}
/**
* Update the child at the given relative Pos
* <p>
* TODO could this be renamed mergeChildData?
*/
private void update(byte detailLevel, int posX, int posZ)
{
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
/**
* Returns if data exists at the given relative Pos.
*/
public boolean doesDataExist(byte detailLevel, int posX, int posZ)
{
if (detailLevel < minDetailLevel)
return false;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
if (dataContainer[detailLevel] == null)
return false;
return dataContainer[detailLevel].doesItExist(posX, posZ);
}
/**
* Gets the generation mode for the data point at the given relative pos.
*/
public byte getGenerationMode(byte detailLevel, int posX, int posZ)
{
if (dataContainer[detailLevel].doesItExist(posX, posZ))
// We take the bottom information always
// TODO what does that mean? bottom of what?
return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ));
else
return DistanceGenerationMode.NONE.complexity;
}
/**
* Returns the lowest (least detailed) detail level in this region
* TODO is that right?
*/
public byte getMinDetailLevel()
{
return minDetailLevel;
}
/**
* Returns the LevelContainer for the detailLevel
* @throws IllegalArgumentException if the detailLevel is less than minDetailLevel
*/
public LevelContainer getLevel(byte detailLevel)
{
if (detailLevel < minDetailLevel)
throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]");
return dataContainer[detailLevel];
}
/**
* Add the levelContainer to this Region, updating the minDetailLevel
* if necessary.
* @throws IllegalArgumentException if the LevelContainer's detailLevel
* is 2 or more detail levels lower than the
* minDetailLevel of this region.
*/
public void addLevelContainer(LevelContainer levelContainer)
{
if (levelContainer.getDetailLevel() < minDetailLevel - 1)
{
throw new IllegalArgumentException(
"the LevelContainer's detailLevel was "
+ "[" + levelContainer.getDetailLevel() + "] but this region "
+ "only allows adding LevelContainers with a "
+ "detail level of [" + (minDetailLevel - 1) + "]");
}
if (levelContainer.getDetailLevel() == minDetailLevel - 1)
minDetailLevel = levelContainer.getDetailLevel();
dataContainer[levelContainer.getDetailLevel()] = levelContainer;
}
// TODO James thinks cutTree and growTree (which he renamed to match cutTree)
// should have more descriptive names, to make sure the "Tree" portion isn't
// confused with Minecraft trees (the plant).
/**
* Removes any dataContainers that are higher than
* the given detailLevel
*/
public void cutTree(byte detailLevel)
{
if (detailLevel > minDetailLevel)
{
for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++)
dataContainer[detailLevelIndex] = null;
minDetailLevel = detailLevel;
}
}
/**
* Make this region more detailed to the detailLevel given.
* TODO is that correct?
*/
public void growTree(byte detailLevel)
{
if (detailLevel < minDetailLevel)
{
for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--)
{
if (dataContainer[detailLevelIndex + 1] == null)
dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1));
dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand();
}
minDetailLevel = detailLevel;
}
}
/**
* return RegionPos of this lod region
*/
public RegionPos getRegionPos()
{
return new RegionPos(regionPosX, regionPosZ);
}
/**
* Returns how many LODs are in this region
*/
public int getNumberOfLods()
{
int count = 0;
for (LevelContainer container : dataContainer)
count += container.getMaxNumberOfLods();
return count;
}
public VerticalQuality getVerticalQuality()
{
return verticalQuality;
}
public DistanceGenerationMode getGenerationMode()
{
return generationMode;
}
public int getMaxVerticalData(byte detailLevel)
{
return dataContainer[detailLevel].getMaxVerticalData();
}
@Override
public String toString()
{
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
}
}
@@ -0,0 +1,172 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Hashtable;
import java.util.Map;
import com.seibel.lod.proxy.ClientProxy;
import net.minecraft.world.DimensionType;
/**
* This stores all LODs for a given world.
*
* @author James Seibel
* @author Leonardo Amato
* @version 9-27-2021
*/
public class LodWorld
{
/** name of this world */
private String worldName;
/** dimensions in this world */
private Map<DimensionType, LodDimension> lodDimensions;
/** If true then the LOD world is setup and ready to use */
private boolean isWorldLoaded = false;
/** the name given to the world if it isn't loaded */
public static final String NO_WORLD_LOADED = "No world loaded";
public LodWorld()
{
worldName = NO_WORLD_LOADED;
}
/**
* Set up the LodWorld with the given newWorldName. <br>
* This should be done whenever loading a new world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
* @param newWorldName name of the world
*/
public void selectWorld(String newWorldName)
{
if (newWorldName.isEmpty())
{
deselectWorld();
return;
}
if (worldName.equals(newWorldName))
// don't recreate everything if we
// didn't actually change worlds
return;
worldName = newWorldName;
lodDimensions = new Hashtable<>();
isWorldLoaded = true;
}
/**
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*/
public void deselectWorld()
{
worldName = NO_WORLD_LOADED;
lodDimensions = null;
isWorldLoaded = false;
}
/**
* Adds newDimension to this world, if a LodDimension
* already exists for the given dimension it is replaced.
*/
public void addLodDimension(LodDimension newDimension)
{
if (lodDimensions == null)
return;
lodDimensions.put(newDimension.dimension, newDimension);
}
/**
* Returns null if no LodDimension exists for the given dimension
*/
public LodDimension getLodDimension(DimensionType dimension)
{
if (lodDimensions == null)
return null;
return lodDimensions.get(dimension);
}
/**
* Resizes the max width in regions that each LodDimension
* should use.
*/
public void resizeDimensionRegionWidth(int newRegionWidth)
{
if (lodDimensions == null)
return;
saveAllDimensions();
for (DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newRegionWidth);
}
/**
* Requests all dimensions save any dirty regions they may have.
*/
public void saveAllDimensions()
{
if (lodDimensions == null)
return;
// TODO we should only print this if lods were actually saved to file
// but that requires a LodDimension.hasDirtyRegions() method or something similar
ClientProxy.LOGGER.info("Saving LODs");
for (DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).saveDirtyRegionsToFileAsync();
}
public boolean getIsWorldNotLoaded()
{
return !isWorldLoaded;
}
public String getWorldName()
{
return worldName;
}
@Override
public String toString()
{
return "World name: " + worldName;
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogQuality;
/**
* This object is just a replacement for an array
* to make things easier to understand in the LodRenderer.
*
* @author James Seibel
* @version 7-03-2021
*/
public class NearFarFogSettings
{
public final NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
public final NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
/**
* If true that means Minecraft is
* rendering fog
*/
public boolean vanillaIsRenderingFog = true;
public NearFarFogSettings()
{
}
/**
* This holds all relevant data to rendering fog at either
* near or far distances.
*/
public static class NearOrFarSetting
{
public FogQuality quality = FogQuality.FANCY;
public FogDistance distance;
public NearOrFarSetting(FogDistance newFogDistance)
{
distance = newFogDistance;
}
}
}
@@ -0,0 +1,210 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LevelPosUtil;
/**
* Holds the levelPos that need to be generated.
* TODO is that correct?
*
* @author Leonardo Amato
* @version 9-27-2021
*/
public class PosToGenerateContainer
{
private final int playerPosX;
private final int playerPosZ;
private final byte farMinDetail;
private int nearSize;
private int farSize;
// TODO what is the format of these two arrays? [detailLevel][4-children]?
private final int[][] nearPosToGenerate;
private final int[][] farPosToGenerate;
public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ)
{
this.playerPosX = playerPosX;
this.playerPosZ = playerPosZ;
this.farMinDetail = farMinDetail;
nearSize = 0;
farSize = 0;
nearPosToGenerate = new int[maxDataToGenerate][4];
farPosToGenerate = new int[maxDataToGenerate][4];
}
// TODO what is going on in this method?
public void addPosToGenerate(byte detailLevel, int posX, int posZ)
{
int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
int index;
if (detailLevel >= farMinDetail)
{
// We are introducing a position in the far array
if (farSize < farPosToGenerate.length)
farSize++;
index = farSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
{
farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
farPosToGenerate[index][2] = farPosToGenerate[index - 1][2];
farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
index--;
}
if (index != farSize - 1 || farSize != farPosToGenerate.length)
{
farPosToGenerate[index][0] = detailLevel + 1;
farPosToGenerate[index][1] = posX;
farPosToGenerate[index][2] = posZ;
farPosToGenerate[index][3] = distance;
}
}
else
{
//We are introducing a position in the near array
if (nearSize < nearPosToGenerate.length)
nearSize++;
index = nearSize - 1;
while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
{
nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2];
nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
index--;
}
if (index != nearSize - 1 || nearSize != nearPosToGenerate.length)
{
nearPosToGenerate[index][0] = detailLevel + 1;
nearPosToGenerate[index][1] = posX;
nearPosToGenerate[index][2] = posZ;
nearPosToGenerate[index][3] = distance;
}
}
}
public int getNumberOfPos()
{
return nearSize + farSize;
}
public int getNumberOfNearPos()
{
return nearSize;
}
public int getNumberOfFarPos()
{
return farSize;
}
// TODO what does getNth mean? could the name be more descriptive or is it just a index?
public int getNthDetail(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][0];
else
return farPosToGenerate[n][0];
}
public int getNthPosX(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][1];
else
return farPosToGenerate[n][1];
}
public int getNthPosZ(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][2];
else
return farPosToGenerate[n][2];
}
public int getNthGeneration(int n, boolean near)
{
if (near)
return nearPosToGenerate[n][3];
else
return farPosToGenerate[n][3];
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append('\n');
builder.append('\n');
builder.append('\n');
builder.append("near pos to generate");
builder.append('\n');
for (int[] ints : nearPosToGenerate)
{
if (ints[0] == 0)
break;
builder.append(ints[0] - 1);
builder.append(" ");
builder.append(ints[1]);
builder.append(" ");
builder.append(ints[2]);
builder.append(" ");
builder.append(ints[3]);
builder.append('\n');
}
builder.append('\n');
builder.append("far pos to generate");
builder.append('\n');
for (int[] ints : farPosToGenerate)
{
if (ints[0] == 0)
break;
builder.append(ints[0] - 1);
builder.append(" ");
builder.append(ints[1]);
builder.append(" ");
builder.append(ints[2]);
builder.append(" ");
builder.append(ints[3]);
builder.append('\n');
}
return builder.toString();
}
}
@@ -0,0 +1,147 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* @author Leonardo Amato
* @version 9-18-2021
*/
public class PosToRenderContainer
{
public byte minDetail;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[] posToRender;
/*TODO this population matrix could be converted to boolean to improve memory use
* no since bools are stored as bytes anyway - cola*/
private byte[][] population;
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size * size * 3];
population = new byte[size][size];
}
public void addPosToRender(byte detailLevel, int posX, int posZ)
{
// When rapidly changing dimensions the bufferBuilder can cause this,
// James isn't sure why, but this will prevent an exception at
// the very least (while stilling logging the problem).
if (numberOfPosToRender >= posToRender.length)
{
// This is might be due to dimensions having a different width
// when first loading in
ClientProxy.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
return;
}
//if(numberOfPosToRender >= posToRender.length)
// posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
posToRender[numberOfPosToRender * 3] = detailLevel;
posToRender[numberOfPosToRender * 3 + 1] = posX;
posToRender[numberOfPosToRender * 3 + 2] = posZ;
numberOfPosToRender++;
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] = (byte) (detailLevel + 1);
}
public boolean contains(byte detailLevel, int posX, int posZ)
{
if (LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ)
return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] == (detailLevel + 1));
else
return false;
}
public void clear(byte minDetail, int regionPosX, int regionPosZ)
{
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
if (this.minDetail == minDetail)
{
Arrays.fill(posToRender, 0);
for (byte[] bytes : population)
Arrays.fill(bytes, (byte) 0);
}
else
{
this.minDetail = minDetail;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size * size * 3];
population = new byte[size][size];
}
}
public int getNumberOfPos()
{
return numberOfPosToRender;
}
public byte getNthDetailLevel(int n)
{
return (byte) posToRender[n * 3];
}
public int getNthPosX(int n)
{
return posToRender[n * 3 + 1];
}
public int getNthPosZ(int n)
{
return posToRender[n * 3 + 2];
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("To render ");
builder.append(numberOfPosToRender);
builder.append('\n');
for (int i = 0; i < numberOfPosToRender; i++)
{
builder.append(posToRender[i * 3]);
builder.append(" ");
builder.append(posToRender[i * 3 + 1]);
builder.append(" ");
builder.append(posToRender[i * 3 + 2]);
builder.append('\n');
}
builder.append('\n');
return builder.toString();
}
}
@@ -0,0 +1,91 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
/**
* This object is similar to ChunkPos or BlockPos.
*
* @author James Seibel
* @version 8-21-2021
*/
public class RegionPos
{
public int x;
public int z;
/**
* Default Constructor <br><br>
* <p>
* Sets x and z to 0
*/
public RegionPos()
{
x = 0;
z = 0;
}
/** simple constructor that sets x and z to new x and z. */
public RegionPos(int newX, int newZ)
{
x = newX;
z = newZ;
}
/** Converts from a BlockPos to a RegionPos */
public RegionPos(BlockPos pos)
{
this(new ChunkPos(pos));
}
/** Converts from a ChunkPos to a RegionPos */
public RegionPos(ChunkPos pos)
{
x = Math.floorDiv(pos.x, LodUtil.REGION_WIDTH_IN_CHUNKS);
z = Math.floorDiv(pos.z, LodUtil.REGION_WIDTH_IN_CHUNKS);
}
/** Returns the ChunkPos at the center of this region */
public ChunkPos chunkPos()
{
return new ChunkPos(
(x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2,
(z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2);
}
/** Returns the BlockPos at the center of this region */
public BlockPos blockPos()
{
return chunkPos().getWorldPosition()
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
}
@@ -0,0 +1,233 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class VerticalLevelContainer implements LevelContainer
{
public final byte detailLevel;
public final int size;
public final int maxVerticalData;
public final long[] dataContainer;
public VerticalLevelContainer(byte detailLevel)
{
this.detailLevel = detailLevel;
size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
dataContainer = new long[size * size * DetailDistanceUtil.getMaxVerticalData(detailLevel)];
}
@Override
public byte getDetailLevel()
{
return detailLevel;
}
@Override
public void clear(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++)
{
dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = DataPointUtil.EMPTY_DATA;
}
}
@Override
public boolean addData(long data, int posX, int posZ, int verticalIndex)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data;
return true;
}
@Override
public boolean addVerticalData(long[] data, int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++)
dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data[verticalIndex];
return true;
}
@Override
public boolean addSingleData(long data, int posX, int posZ)
{
return addData(data, posX, posZ, 0);
}
@Override
public long getData(int posX, int posZ, int verticalIndex)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex];
}
@Override
public long getSingleData(int posX, int posZ)
{
return getData(posX, posZ, 0);
}
@Override
public int getMaxVerticalData()
{
return maxVerticalData;
}
public int getSize()
{
return size;
}
@Override
public boolean doesItExist(int posX, int posZ)
{
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return DataPointUtil.doesItExist(getSingleData(posX, posZ));
}
public VerticalLevelContainer(byte[] inputData)
{
int tempIndex;
int index = 0;
long newData;
detailLevel = inputData[index];
index++;
maxVerticalData = inputData[index];
index++;
size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int x = size * size * maxVerticalData;
this.dataContainer = new long[x];
for (int i = 0; i < x; i++)
{
newData = 0;
for (tempIndex = 0; tempIndex < 8; tempIndex++)
newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
index += 8;
dataContainer[i] = newData;
}
}
@Override
public LevelContainer expand()
{
return new VerticalLevelContainer((byte) (getDetailLevel() - 1));
}
@Override
public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
{
//We reset the array
long[] dataToMerge = ThreadMapUtil.getVerticalUpdateArray(detailLevel);
int lowerMaxVertical = dataToMerge.length / 4;
int childPosX;
int childPosZ;
long[] data;
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
for (int x = 0; x <= 1; x++)
{
for (int z = 0; z <= 1; z++)
{
childPosX = 2 * posX + x;
childPosZ = 2 * posZ + z;
for (int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++)
dataToMerge[(z * 2 + x) * lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex);
}
}
data = DataPointUtil.mergeMultiData(dataToMerge, lowerMaxVertical, getMaxVerticalData());
addVerticalData(data, posX, posZ);
}
@Override
public byte[] toDataString()
{
int index = 0;
int x = size * size * maxVerticalData;
int tempIndex;
long current;
byte[] tempData = ThreadMapUtil.getSaveContainer(detailLevel);
tempData[index] = detailLevel;
index++;
tempData[index] = (byte) maxVerticalData;
index++;
for (int i = 0; i < x; i++)
{
current = dataContainer[i];
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex));
index += 8;
}
return tempData;
}
@Override
@SuppressWarnings("unused")
public String toString()
{
/*
StringBuilder stringBuilder = new StringBuilder();
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
stringBuilder.append(detailLevel);
stringBuilder.append(DATA_DELIMITER);
for (int x = 0; x < size; x++)
{
for (int z = 0; z < size; z++)
{
//Converting the dataToHex
stringBuilder.append(Long.toHexString(dataContainer[x][z][0]));
stringBuilder.append(DATA_DELIMITER);
}
}
return stringBuilder.toString();
*/
return " ";
}
@Override
public int getMaxNumberOfLods()
{
return size * size * getMaxVerticalData();
}
}
@@ -0,0 +1,397 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.proxy;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.worldGeneration.LodGenWorker;
import com.seibel.lod.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
* @author James_Seibel
* @version 10-23-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
/**
* there is some setup that should only happen once,
* once this is true that setup has completed
*/
private boolean firstTimeSetupComplete = false;
private static final LodWorld lodWorld = new LodWorld();
private static final LodBuilder lodBuilder = new LodBuilder();
private static final LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
private static final LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
private boolean configOverrideReminderPrinted = false;
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This is used to determine if the LODs should be regenerated */
public static int previousChunkRenderDistance = 0;
/** This is used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
/**
* can be set if we want to recalculate variables related
* to the LOD view distance
*/
private boolean recalculateWidths = false;
public ClientProxy()
{
}
//==============//
// render event //
//==============//
/** Do any setup that is required to draw LODs and then tell the LodRenderer to draw. */
public void renderLods(MatrixStack mcMatrixStack, float partialTicks)
{
// comment out when creating a release
// applyConfigOverrides();
// clear any out of date objects
mc.clearFrameObjectCache();
try
{
// only run the first time setup once
if (!firstTimeSetupComplete)
firstFrameSetup();
if (mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getCurrentDimension());
if (lodDim == null)
return;
DetailDistanceUtil.updateSettings();
viewDistanceChangedEvent();
playerMoveEvent(lodDim);
lodDim.cutRegionNodesAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
lodDim.expandOrLoadRegionsAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measure
IProfiler profiler = mc.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
renderer.drawLODs(lodDim, mcMatrixStack, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
// these can't be set until after the buffers are built (in renderer.drawLODs)
// otherwise the buffers may be set to the wrong size, or not changed at all
previousChunkRenderDistance = mc.getRenderDistance();
previousLodRenderDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get();
}
catch (Exception e)
{
LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
}
}
/** used in a development environment to change settings on the fly */
private void applyConfigOverrides()
{
// remind the developer(s) that the config override is active
if (!configOverrideReminderPrinted)
{
// mc.getPlayer().sendMessage(new StringTextComponent("LOD experimental build 1.5.1"), mc.getPlayer().getUUID());
// mc.getPlayer().sendMessage(new StringTextComponent("Here be dragons!"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new StringTextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
configOverrideReminderPrinted = true;
}
// LodConfig.CLIENT.graphics.drawResolution.set(HorizontalResolution.BLOCK);
// LodConfig.CLIENT.worldGenerator.generationResolution.set(HorizontalResolution.BLOCK);
// requires a world restart?
// LodConfig.CLIENT.worldGenerator.lodQualityMode.set(VerticalQuality.VOXEL);
// LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.graphics.fogQualityOption.fogDrawOverride.set(FogDrawOverride.FANCY);
// LodConfig.CLIENT.graphics.fogQualityOption.disableVanillaFog.set(true);
// LodConfig.CLIENT.graphics.shadingMode.set(ShadingMode.DARKEN_SIDES);
// LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.set(VanillaOverdraw.DYNAMIC);
// LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.set(GpuUploadMethod.BUFFER_STORAGE);
// LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
// LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.set(128);
// LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.set(DistanceCalculatorType.LINEAR);
// LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(false);
// LodConfig.CLIENT.buffers.rebuildTimes.set(BufferRebuildTimes.FREQUENT);
LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.set(true);
// LodConfig.CLIENT.debugging.debugMode.set(DebugMode.SHOW_DETAIL);
}
//==============//
// forge events //
//==============//
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
if (mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getPlayer().level.dimensionType());
if (lodDim == null)
return;
lodWorldGenerator.queueGenerationRequests(lodDim, renderer, lodBuilder);
}
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
lodBuilder.generateLodNodeAsync(new ChunkWrapper(event.getChunk()), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER);
}
@SubscribeEvent
public void worldSaveEvent(WorldEvent.Save event)
{
lodWorld.saveAllDimensions();
}
/** This is also called when a new dimension loads */
@SubscribeEvent
public void worldLoadEvent(WorldEvent.Load event)
{
DataPointUtil.worldHeight = event.getWorld().getHeight();
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld()));
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
renderer.regenerateLODsNextFrame();
}
@SubscribeEvent
public void worldUnloadEvent(WorldEvent.Unload event)
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
if (mc.getConnection().getLevel() == null)
{
// the player just left the server
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
lodWorld.deselectWorld();
// hopefully this should reduce issues related to the buffer builder
// breaking when changing worlds.
renderer.destroyBuffers();
recalculateWidths = true;
renderer = new LodRenderer(lodBufferBuilder);
// make sure the nilled objects are freed.
// (this prevents an out of memory error when
// changing worlds)
System.gc();
}
}
@SubscribeEvent
public void blockChangeEvent(BlockEvent event)
{
if (event.getClass() == BlockEvent.BreakEvent.class ||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
{
// recreate the LOD where the blocks were changed
lodBuilder.generateLodNodeAsync(new ChunkWrapper(event.getWorld().getChunk(event.getPos())), lodWorld, event.getWorld());
}
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
if (LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F4 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.advancedModOptions.debugging.debugMode.set(LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get().getNext());
}
if (LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F6 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.advancedModOptions.debugging.drawLods.set(!LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get());
}
}
//============//
// LOD events //
//============//
/** Re-centers the given LodDimension if it needs to be. */
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(mc.getPlayer().blockPosition());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
/** Re-sizes all LodDimensions if they need to be. */
private void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (mc.getClientWorld().dimensionType().hasCeiling())
chunksWide = Math.min(LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 2;
// do the dimensions need to change in size?
if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
lodWorld.resizeDimensionRegionWidth(newWidth);
lodBuilder.defaultDimensionWidthInRegions = newWidth;
renderer.setupBuffers(lodWorld.getLodDimension(mc.getClientWorld().dimensionType()));
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
}
DetailDistanceUtil.updateSettings();
}
/** This event is called once during the first frame Minecraft renders in the world. */
public void firstFrameSetup()
{
// make sure the GlProxy is created before the LodBufferBuilder needs it
GlProxy.getInstance();
firstTimeSetupComplete = true;
}
/** this method reset some static data every time we change world */
private void resetMod()
{
ThreadMapUtil.clearMaps();
LodGenWorker.restartExecutorService();
}
//================//
// public getters //
//================//
public static LodWorld getLodWorld()
{
return lodWorld;
}
public static LodBuilder getLodBuilder()
{
return lodBuilder;
}
public static LodRenderer getRenderer()
{
return renderer;
}
}
@@ -0,0 +1,244 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.proxy;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
* A singleton that holds references to different openGL contexts
* and GPU capabilities.
*
* <p>
* Helpful OpenGL resources: <br><br>
* <p>
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br><br>
*
* @author James Seibel
* @version 10-23-2021
*/
public class GlProxy
{
private static GlProxy instance = null;
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** Minecraft's GLFW window */
public final long minecraftGlContext;
/** Minecraft's GL context */
public final GLCapabilities minecraftGlCapabilities;
/** the LodBuilder's GLFW window */
public final long lodBuilderGlContext;
/** the LodBuilder's GL context */
public final GLCapabilities lodBuilderGlCapabilities;
/**
* This is just used for debugging, hopefully it can be removed once
* the context switching is more stable.
*/
public Thread lodBuilderOwnerThread = null;
/** Does this computer's GPU support fancy fog? */
public final boolean fancyFogAvailable;
/** Requires OpenGL 4.5, and offers the best buffer uploading */
public final boolean bufferStorageSupported;
/** Requires OpenGL 3.0 */
public final boolean mapBufferRangeSupported;
private GlProxy()
{
ClientProxy.LOGGER.error("Creating " + GlProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
// getting Minecraft's context has to be done on the render thread,
// where the GL context is
if (!RenderSystem.isOnRenderThread())
throw new IllegalStateException(GlProxy.class.getSimpleName() + " was created outside the render thread!");
//============================//
// create the builder context //
//============================//
// get Minecraft's context
minecraftGlContext = GLFW.glfwGetCurrentContext();
minecraftGlCapabilities = GL.getCapabilities();
// create the LodBuilder's context
// Hopefully this shouldn't cause any issues with other mods that need custom contexts
// (although the number that do should be relatively few)
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
// create an invisible window to hold the context
lodBuilderGlContext = GLFW.glfwCreateWindow(640, 480, "LOD window", 0L, minecraftGlContext);
GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
lodBuilderGlCapabilities = GL.createCapabilities();
// Since this is called on the render thread, make sure the Minecraft context is used in the end
GLFW.glfwMakeContextCurrent(minecraftGlContext);
GL.setCapabilities(minecraftGlCapabilities);
//==============================//
// determine the OpenGL version //
//==============================//
bufferStorageSupported = minecraftGlCapabilities.OpenGL45;
mapBufferRangeSupported = minecraftGlCapabilities.OpenGL30;
if (!minecraftGlCapabilities.OpenGL15)
{
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GlProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 1.5 or greater.";
mc.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 1.5 or greater."));
}
if (!bufferStorageSupported)
{
String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
ClientProxy.LOGGER.error("This GPU doesn't support OpenGL 4.5, falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
}
//==================================//
// get any GPU related capabilities //
//==================================//
// see if this GPU can run fancy fog
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
if (!fancyFogAvailable)
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that the fancy fog option will not be available.");
// GlProxy creation success
ClientProxy.LOGGER.error(GlProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
}
/**
* A wrapper function to make switching contexts easier. <br>
* Does nothing if the calling thread is already using newContext.
*/
public void setGlContext(GlProxyContext newContext)
{
GlProxyContext currentContext = getGlContext();
// we don't have to change the context, we're already there.
if (currentContext == newContext)
return;
long contextPointer = 0L;
GLCapabilities newGlCapabilities = null;
// get the pointer(s) for this context
switch (newContext)
{
case LOD_BUILDER:
contextPointer = lodBuilderGlContext;
newGlCapabilities = lodBuilderGlCapabilities;
break;
case MINECRAFT:
contextPointer = minecraftGlContext;
newGlCapabilities = minecraftGlCapabilities;
break;
default: // default should never happen, it is just here to make the compiler happy
case NONE:
// 0L is equivalent to null
break;
}
GLFW.glfwMakeContextCurrent(contextPointer);
GL.setCapabilities(newGlCapabilities);
// used for debugging
if (newContext == GlProxyContext.LOD_BUILDER)
lodBuilderOwnerThread = Thread.currentThread();
else if (newContext == GlProxyContext.NONE && currentContext == GlProxyContext.LOD_BUILDER)
lodBuilderOwnerThread = null;
}
/** Returns this thread's OpenGL context. */
public GlProxyContext getGlContext()
{
long currentContext = GLFW.glfwGetCurrentContext();
if (currentContext == lodBuilderGlContext)
return GlProxyContext.LOD_BUILDER;
else if (currentContext == minecraftGlContext)
return GlProxyContext.MINECRAFT;
else if (currentContext == 0L)
return GlProxyContext.NONE;
else
// hopefully this shouldn't happen, but
// at least now we will be notified if it does happen
throw new IllegalStateException(Thread.currentThread().getName() + " has a unknown OpenGl context: [" + currentContext + "]. Minecraft context [" + minecraftGlContext + "], LodBuilder context [" + lodBuilderGlContext + "], no context [0].");
}
public static GlProxy getInstance()
{
if (instance == null)
instance = new GlProxy();
return instance;
}
}
@@ -0,0 +1,959 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.render;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.VertexBuffersAndOffset;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.*;
import com.seibel.lod.handlers.ReflectionHandler;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.potion.Effects;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.NVFogDistance;
import java.util.HashSet;
/**
* This is where all the magic happens. <br>
* This is where LODs are draw to the world.
* @author James Seibel
* @version 10-25-2021
*/
public class LodRenderer
{
/**
* this is the light used when rendering the LODs,
* it should be something different from what is used by Minecraft
*/
private static final int LOD_GL_LIGHT_NUMBER = GL15.GL_LIGHT2;
/**
* If true the LODs colors will be replaced with
* a checkerboard, this can be used for debugging.
*/
public DebugMode previousDebugMode = DebugMode.OFF;
private final MinecraftWrapper mc;
private final GameRenderer gameRender;
private IProfiler profiler;
private int farPlaneBlockDistance;
/** This is used to generate the buildable buffers */
private final LodBufferBuilder lodBufferBuilder;
/** Each VertexBuffer represents 1 region */
private VertexBuffer[][][] vbos;
/**
* the OpenGL IDs for the vbos of the same indices.
* These have to be separate because we can't override the
* buffers in the VBOs (and we don't want to)
*/
private int[][][] storageBufferIds;
private ChunkPos vbosCenter = new ChunkPos(0, 0);
/** This is used to determine if the LODs should be regenerated */
private int[] previousPos = new int[] { 0, 0, 0 };
// these variables are used to determine if the buffers should be rebuilt
private float prevSkyBrightness = 0;
private double prevBrightness = 0;
private int prevRenderDistance = 0;
private long prevPlayerPosTime = 0;
private long prevVanillaChunkTime = 0;
private long prevChunkTime = 0;
/** This is used to determine if the LODs should be regenerated */
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
/**
* if this is true the LOD buffers should be regenerated,
* provided they aren't already being regenerated.
*/
private volatile boolean partialRegen = false;
private volatile boolean fullRegen = true;
/**
* This HashSet contains every chunk that Vanilla Minecraft
* is going to render
*/
public boolean[][] vanillaRenderedChunks;
public boolean vanillaRenderedChunksChanged;
public boolean vanillaRenderedChunksEmptySkip = false;
public int vanillaBlockRenderedDistance;
final boolean vivecraftDetected = ReflectionHandler.INSTANCE.detectVivecraft();
public LodRenderer(LodBufferBuilder newLodNodeBufferBuilder)
{
mc = MinecraftWrapper.INSTANCE;
gameRender = mc.getGameRenderer();
lodBufferBuilder = newLodNodeBufferBuilder;
}
/**
* Besides drawing the LODs this method also starts
* the async process of generating the Buffers that hold those LODs.
* @param lodDim The dimension to draw, if null doesn't replace the current dimension.
* @param mcMatrixStack This matrix stack should come straight from MC's renderChunkLayer (or future equivalent) method
* @param partialTicks how far into the current tick this method was called.
*/
@SuppressWarnings("deprecation")
public void drawLODs(LodDimension lodDim, MatrixStack mcMatrixStack, float partialTicks, IProfiler newProfiler)
{
//=================================//
// determine if LODs should render //
//=================================//
if (lodDim == null)
{
// if there aren't any loaded LodChunks
// don't try drawing anything
return;
}
if (mc.getPlayer().getActiveEffectsMap().get(Effects.BLINDNESS) != null)
{
// if the player is blind don't render LODs,
// and don't change minecraft's fog
// which blindness relies on.
return;
}
//===============//
// initial setup //
//===============//
profiler = newProfiler;
profiler.push("LOD setup");
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
// starting here...
determineIfLodsShouldRegenerate(lodDim, partialTicks);
//=================//
// create the LODs //
//=================//
// only regenerate the LODs if:
// 1. we want to regenerate LODs
// 2. we aren't already regenerating the LODs
// 3. we aren't waiting for the build and draw buffers to swap
// (this is to prevent thread conflicts)
if (LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get())
{
if (lodBufferBuilder.buildableBuffers == null)
lodBufferBuilder.setupBuffers(lodDim);
if ((partialRegen || fullRegen) && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvailable())
{
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, mc.getPlayer().blockPosition(), fullRegen);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvailable()
// is true
fullRegen = false;
partialRegen = false;
}
}
else
lodBufferBuilder.destroyBuffers();
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
// ...ending here
if (lodBufferBuilder.newBuffersAvailable())
{
swapBuffers();
}
//===========================//
// GL settings for rendering //
//===========================//
// set the required open GL settings
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() == DebugMode.SHOW_DETAIL_WIREFRAME)
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
else
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glDisable(GL15.GL_TEXTURE_2D);
GL15.glEnable(GL15.GL_CULL_FACE);
GL15.glEnable(GL15.GL_COLOR_MATERIAL);
GL15.glEnable(GL15.GL_DEPTH_TEST);
// enable transparent rendering
GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
GL15.glEnable(GL15.GL_BLEND);
// disable the lights Minecraft uses
GL15.glDisable(GL15.GL_LIGHT0);
GL15.glDisable(GL15.GL_LIGHT1);
// get the default projection matrix, so we can
// reset it after drawing the LODs
float[] mcProjMatrixRaw = new float[16];
GL15.glGetFloatv(GL15.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
Matrix4f mcProjectionMatrix = new Matrix4f(mcProjMatrixRaw);
// OpenGl outputs their matrices in col,row form instead of row,col
// (or maybe vice versa I have no idea :P)
mcProjectionMatrix.transpose();
Matrix4f modelViewMatrix = offsetTheModelViewMatrix(mcMatrixStack, partialTicks);
vanillaBlockRenderedDistance = mc.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// required for setupFog and setupProjectionMatrix
if (mc.getClientWorld().dimensionType().hasCeiling())
farPlaneBlockDistance = Math.min(LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
else
farPlaneBlockDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
setupProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, partialTicks);
// commented out until we can add shaders to handle lighting
//setupLighting(lodDim, partialTicks);
NearFarFogSettings fogSettings = determineFogSettings();
// determine the current fog settings, so they can be
// reset after drawing the LODs
float defaultFogStartDist = GL15.glGetFloat(GL15.GL_FOG_START);
float defaultFogEndDist = GL15.glGetFloat(GL15.GL_FOG_END);
int defaultFogMode = GL15.glGetInteger(GL15.GL_FOG_MODE);
int defaultFogDistance = GlProxy.getInstance().fancyFogAvailable ? GL15.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV) : -1;
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
if (vbos != null && LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get())
{
ActiveRenderInfo renderInfo = mc.getGameRenderer().getMainCamera();
Vector3d cameraDir = new Vector3d(renderInfo.getLookVector());
boolean cullingDisabled = LodConfig.CLIENT.graphics.advancedGraphicsOption.disableDirectionalCulling.get();
boolean renderBufferStorage = LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.get() == GpuUploadMethod.BUFFER_STORAGE && GlProxy.getInstance().bufferStorageSupported;
// used to determine what type of fog to render
int halfWidth = vbos.length / 2;
int quarterWidth = vbos.length / 4;
// where the center of the built buffers is (needed when culling regions)
RegionPos vboCenterRegionPos = new RegionPos(vbosCenter);
for (int x = 0; x < vbos.length; x++)
{
for (int z = 0; z < vbos.length; z++)
{
RegionPos vboPos = new RegionPos(
x + vboCenterRegionPos.x - (lodDim.getWidth() / 2),
z + vboCenterRegionPos.z - (lodDim.getWidth() / 2));
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(renderInfo.getBlockPosition(), cameraDir, vboPos.blockPos()))
{
if ((x > halfWidth - quarterWidth && x < halfWidth + quarterWidth)
&& (z > halfWidth - quarterWidth && z < halfWidth + quarterWidth))
setupFog(fogSettings.near.distance, fogSettings.near.quality);
else
setupFog(fogSettings.far.distance, fogSettings.far.quality);
if (storageBufferIds != null && renderBufferStorage)
for (int i = 0; i < storageBufferIds[x][z].length; i++)
drawStorageBuffer(vbos[x][z][i], storageBufferIds[x][z][i], modelViewMatrix);
else
for (int i = 0; i < vbos[x][z].length; i++)
drawVertexBuffer(vbos[x][z][i], modelViewMatrix);
}
}
}
}
//=========//
// cleanup //
//=========//
profiler.popPush("LOD cleanup");
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
GL15.glEnable(GL15.GL_TEXTURE_2D);
GL15.glDisable(LOD_GL_LIGHT_NUMBER);
GL15.glDisable(GL15.GL_BLEND);
// re-enable the lights Minecraft uses
GL15.glEnable(GL15.GL_LIGHT0);
GL15.glEnable(GL15.GL_LIGHT1);
RenderSystem.disableLighting();
// reset the fog settings so the normal chunks
// will be drawn correctly
cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance);
// reset the projection matrix so anything drawn after
// the LODs will use the correct projection matrix
gameRender.resetProjectionMatrix(mcProjectionMatrix);
// clear the depth buffer so anything drawn is drawn
// over the LODs
GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT);
// end of internal LOD profiling
profiler.pop();
}
/** This is where the actual drawing happens. */
private void drawStorageBuffer(VertexBuffer vbo, int bufferStorageId, Matrix4f modelViewMatrix)
{
if (vbo == null)
return;
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferStorageId);
// 0L is the starting pointer
LodUtil.LOD_VERTEX_FORMAT.setupBufferState(0L);
vbo.draw(modelViewMatrix, GL15.GL_QUADS);
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
LodUtil.LOD_VERTEX_FORMAT.clearBufferState();
}
/** This is where the actual drawing happens. */
private void drawVertexBuffer(VertexBuffer vbo, Matrix4f modelViewMatrix)
{
if (vbo == null)
return;
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
// 0L is the starting pointer
LodUtil.LOD_VERTEX_FORMAT.setupBufferState(0L);
vbo.draw(modelViewMatrix, GL15.GL_QUADS);
GL15C.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
LodUtil.LOD_VERTEX_FORMAT.clearBufferState();
}
//=================//
// Setup Functions //
//=================//
@SuppressWarnings("deprecation")
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
{
if (fogQuality == FogQuality.OFF)
{
GL15.glDisable(GL15.GL_FOG);
return;
}
if (fogDistance == FogDistance.NEAR_AND_FAR)
{
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
}
// determine the fog distance mode to use
int glFogDistanceMode;
if (fogQuality == FogQuality.FANCY)
{
// fancy fog (fragment distance based fog)
glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV;
}
else
{
// fast fog (frustum distance based fog)
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
}
// the multipliers are percentages
// of the regular view distance.
if (fogDistance == FogDistance.FAR)
{
// the reason that I wrote fogEnd then fogStart backwards
// is because we are using fog backwards to how
// it is normally used, with it hiding near objects
// instead of far objects.
if (fogQuality == FogQuality.FANCY)
{
// for more realistic fog when using FAR
if (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get() == FogDistance.NEAR_AND_FAR)
RenderSystem.fogStart(farPlaneBlockDistance * 1.6f * 0.9f);
else
RenderSystem.fogStart(Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f));
RenderSystem.fogEnd(farPlaneBlockDistance * 1.6f);
}
else if (fogQuality == FogQuality.FAST)
{
// for the far fog of the normal chunks
// to start right where the LODs' end use:
// end = 0.8f, start = 1.5f
RenderSystem.fogStart(farPlaneBlockDistance * 0.75f);
RenderSystem.fogEnd(farPlaneBlockDistance * 1.0f);
}
}
else if (fogDistance == FogDistance.NEAR)
{
if (fogQuality == FogQuality.FANCY)
{
RenderSystem.fogEnd(vanillaBlockRenderedDistance * 1.41f);
RenderSystem.fogStart(vanillaBlockRenderedDistance * 1.6f);
}
else if (fogQuality == FogQuality.FAST)
{
RenderSystem.fogEnd(vanillaBlockRenderedDistance * 1.0f);
RenderSystem.fogStart(vanillaBlockRenderedDistance * 1.5f);
}
}
GL15.glEnable(GL15.GL_FOG);
RenderSystem.enableFog();
RenderSystem.setupNvFogDistance();
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
if (GlProxy.getInstance().fancyFogAvailable)
GL15.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
}
/**
* Revert any changes that were made to the fog
* and sets up the fog for Minecraft.
*/
@SuppressWarnings("deprecation")
private void cleanupFog(NearFarFogSettings fogSettings,
float defaultFogStartDist, float defaultFogEndDist,
int defaultFogMode, int defaultFogDistance)
{
RenderSystem.fogStart(defaultFogStartDist);
RenderSystem.fogEnd(defaultFogEndDist);
RenderSystem.fogMode(defaultFogMode);
// this setting is only valid if the GPU supports fancy fog
if (GlProxy.getInstance().fancyFogAvailable)
GL15.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
// disable fog if Minecraft wasn't rendering fog
// or we want it disabled
if (!fogSettings.vanillaIsRenderingFog
|| LodConfig.CLIENT.graphics.fogQualityOption.disableVanillaFog.get())
{
// Make fog render a infinite distance away.
// This doesn't technically disable Minecraft's fog
// so performance will probably be the same regardless, unlike
// Optifine's no fog setting.
// we can't disable minecraft's fog outright because by default
// minecraft will re-enable the fog after our code
RenderSystem.fogStart(0.0F);
RenderSystem.fogEnd(Float.MAX_VALUE);
RenderSystem.fogDensity(0.0F);
}
}
/**
* Translate the camera relative to the LodDimension's center,
* this is done since all LOD buffers are created in world space
* instead of object space.
* (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
* accuracy vs the model view matrix, which only uses floats)
*/
private Matrix4f offsetTheModelViewMatrix(MatrixStack mcMatrixStack, float partialTicks)
{
// duplicate the last matrix
mcMatrixStack.pushPose();
// get all relevant camera info
ActiveRenderInfo renderInfo = mc.getGameRenderer().getMainCamera();
Vector3d projectedView = renderInfo.getPosition();
// translate the camera relative to the regions' center
// (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
// accuracy vs the model view matrix, which only uses floats)
BlockPos bufferPos = vbosCenter.getWorldPosition();
double xDiff = projectedView.x - bufferPos.getX();
double zDiff = projectedView.z - bufferPos.getZ();
mcMatrixStack.translate(-xDiff, -projectedView.y, -zDiff);
// get the modified model view matrix
Matrix4f lodModelViewMatrix = mcMatrixStack.last().pose();
// remove the lod ModelViewMatrix
mcMatrixStack.popPose();
return lodModelViewMatrix;
}
/**
* create a new projection matrix and send it over to the GPU
* @param currentProjectionMatrix this is Minecraft's current projection matrix
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
* @param partialTicks how many ticks into the frame we are
*/
private void setupProjectionMatrix(Matrix4f currentProjectionMatrix, float vanillaBlockRenderedDistance, float partialTicks)
{
Matrix4f lodPoj;
float nearClipPlane = LodConfig.CLIENT.graphics.advancedGraphicsOption.useExtendedNearClipPlane.get() ? vanillaBlockRenderedDistance / 5 : 1;
float farClipPlane = farPlaneBlockDistance * LodUtil.CHUNK_WIDTH >> 1;
if (vivecraftDetected)
{
//use modify clip plane method to modify the current projection matrix's clip planes.
lodPoj = ReflectionHandler.INSTANCE.Matrix4fModifyClipPlanes(
currentProjectionMatrix,
nearClipPlane,
farClipPlane);
}
else
{
// create the new projection matrix
lodPoj = Matrix4f.perspective(
getFov(partialTicks, true),
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
nearClipPlane,
farClipPlane);
// get Minecraft's un-edited projection matrix
// (this is before it is zoomed, distorted, etc.)
Matrix4f defaultMcProj = mc.getGameRenderer().getProjectionMatrix(mc.getGameRenderer().getMainCamera(), partialTicks, true);
// true here means use "use fov setting" (probably)
// this logic strips away the defaultMcProj matrix, so we
// can get the distortionMatrix, which represents all
// transformations, zooming, distortions, etc. done
// to Minecraft's Projection matrix
Matrix4f defaultMcProjInv = defaultMcProj.copy();
defaultMcProjInv.invert();
Matrix4f distortionMatrix = defaultMcProjInv.copy();
distortionMatrix.multiply(currentProjectionMatrix);
// edit the lod projection to match Minecraft's
// (so the LODs line up with the real world)
lodPoj.multiply(distortionMatrix);
}
// send the projection over to the GPU
gameRender.resetProjectionMatrix(lodPoj);
}
///** setup the lighting to be used for the LODs */
/*private void setupLighting(LodDimension lodDimension, float partialTicks)
{
// Determine if the player has night vision
boolean playerHasNightVision = false;
if (this.mc.getPlayer() != null)
{
Iterator<EffectInstance> iterator = this.mc.getPlayer().getActiveEffects().iterator();
while (iterator.hasNext())
{
EffectInstance instance = iterator.next();
if (instance.getEffect() == Effects.NIGHT_VISION)
{
playerHasNightVision = true;
break;
}
}
}
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.getSkyDarken(partialTicks) : 0.2f;
sunBrightness = playerHasNightVision ? 1.0f : sunBrightness;
float gamma = (float) mc.getOptions().gamma - 0.0f;
float dayEffect = (sunBrightness - 0.2f) * 1.25f;
float lightStrength = (gamma * 0.34f - 0.01f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.2980392157f + 0.1647058824f
float blueLightStrength = (gamma * 0.44f + 0.12f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.4235294118f + 0.2784313725f
float[] lightAmbient = {lightStrength, lightStrength, blueLightStrength, 1.0f};
// can be used for debugging
// if (partialTicks < 0.005)
// ClientProxy.LOGGER.debug(lightStrength);
ByteBuffer temp = ByteBuffer.allocateDirect(16);
temp.order(ByteOrder.nativeOrder());
GL15.glLightfv(LOD_GL_LIGHT_NUMBER, GL15.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
GL15.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
RenderSystem.enableLighting();
}*/
/** Create all buffers that will be used. */
public void setupBuffers(LodDimension lodDim)
{
lodBufferBuilder.setupBuffers(lodDim);
}
//======================//
// Other Misc Functions //
//======================//
/**
* If this is called then the next time "drawLODs" is called
* the LODs will be regenerated; the same as if the player moved.
*/
public void regenerateLODsNextFrame()
{
fullRegen = true;
}
/**
* Replace the current Vertex Buffers with the newly
* created buffers from the lodBufferBuilder. <br><br>
* <p>
* For some reason this has to be called after the frame has been rendered,
* otherwise visual stuttering/rubber banding may happen. I'm not sure why...
*/
private void swapBuffers()
{
// replace the drawable buffers with
// the newly created buffers from the lodBufferBuilder
VertexBuffersAndOffset result = lodBufferBuilder.getVertexBuffers();
vbos = result.vbos;
storageBufferIds = result.storageBufferIds;
vbosCenter = result.drawableCenterChunkPos;
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilder.destroyBuffers();
}
private double getFov(float partialTicks, boolean useFovSetting)
{
return mc.getGameRenderer().getFov(mc.getGameRenderer().getMainCamera(), partialTicks, useFovSetting);
}
/** Return what fog settings should be used when rendering. */
private NearFarFogSettings determineFogSettings()
{
NearFarFogSettings fogSettings = new NearFarFogSettings();
FogQuality quality = ReflectionHandler.INSTANCE.getFogQuality();
FogDrawOverride override = LodConfig.CLIENT.graphics.fogQualityOption.fogDrawOverride.get();
fogSettings.vanillaIsRenderingFog = quality != FogQuality.OFF;
// use any fog overrides the user may have set
switch (override)
{
case FANCY:
quality = FogQuality.FANCY;
break;
case NO_FOG:
quality = FogQuality.OFF;
break;
case FAST:
quality = FogQuality.FAST;
break;
case OPTIFINE_SETTING:
// don't override anything
break;
}
// only use fancy fog if the user's GPU can deliver
if (!GlProxy.getInstance().fancyFogAvailable && quality == FogQuality.FANCY)
{
quality = FogQuality.FAST;
}
// how different distances are drawn depends on the quality set
switch (quality)
{
case FANCY:
fogSettings.near.quality = FogQuality.FANCY;
fogSettings.far.quality = FogQuality.FANCY;
switch (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
case NEAR_AND_FAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.FAR;
break;
case NEAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case FAR:
fogSettings.near.distance = FogDistance.FAR;
fogSettings.far.distance = FogDistance.FAR;
break;
}
break;
case FAST:
fogSettings.near.quality = FogQuality.FAST;
fogSettings.far.quality = FogQuality.FAST;
// fast fog setting should only have one type of
// fog, since the LODs are separated into a near
// and far portion; and fast fog is rendered from the
// frustrum's perspective instead of the camera
switch (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
case NEAR_AND_FAR:
case NEAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case FAR:
fogSettings.near.distance = FogDistance.FAR;
fogSettings.far.distance = FogDistance.FAR;
break;
}
break;
case OFF:
fogSettings.near.quality = FogQuality.OFF;
fogSettings.far.quality = FogQuality.OFF;
break;
}
return fogSettings;
}
/** Determines if the LODs should have a fullRegen or partialRegen */
private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
{
short chunkRenderDistance = (short) mc.getRenderDistance();
int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2;
//=============//
// full regens //
//=============//
// check if the view distance changed
if (ClientProxy.previousLodRenderDistance != LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get()
|| chunkRenderDistance != prevRenderDistance
|| prevFogDistance != LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
DetailDistanceUtil.updateSettings();
fullRegen = true;
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
prevFogDistance = LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get();
prevRenderDistance = chunkRenderDistance;
}
// did the user change the debug setting?
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() != previousDebugMode)
{
previousDebugMode = LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get();
fullRegen = true;
}
long newTime = System.currentTimeMillis();
// check if the player has moved
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().playerMoveTimeout)
{
if (LevelPosUtil.getDetailLevel(previousPos) == 0
|| mc.getPlayer().xChunk != LevelPosUtil.getPosX(previousPos)
|| mc.getPlayer().zChunk != LevelPosUtil.getPosZ(previousPos))
{
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
fullRegen = true;
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
}
prevPlayerPosTime = newTime;
}
// determine how far the lighting has to
// change in order to rebuild the buffers
// the max brightness is 1 and the minimum is 0.2
float skyBrightness = lodDim.dimension.hasSkyLight() ? mc.getSkyDarken(partialTicks) : 0.2f;
float minLightingDifference;
switch (LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get())
{
case FREQUENT:
minLightingDifference = 0.025f;
break;
case NORMAL:
minLightingDifference = 0.05f;
break;
default:
case RARE:
minLightingDifference = 0.1f;
break;
}
// check if the lighting changed
if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference
// make sure the lighting gets to the max/minimum value
// (just in case the minLightingDifference is too large to notice the change)
|| (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon
|| (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight
|| mc.getOptions().gamma != prevBrightness)
{
fullRegen = true;
prevBrightness = mc.getOptions().gamma;
prevSkyBrightness = skyBrightness;
}
//================//
// partial regens //
//================//
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().renderedChunkTimeout)
{
if (vanillaRenderedChunksChanged)
{
partialRegen = true;
vanillaRenderedChunksChanged = false;
}
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().chunkChangeTimeout)
{
if (lodDim.regenDimensionBuffers)
{
partialRegen = true;
lodDim.regenDimensionBuffers = false;
}
prevChunkTime = newTime;
}
//==============//
// LOD skipping //
//==============//
// determine which LODs should not be rendered close to the player
HashSet<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.getPlayer().blockPosition());
int xIndex;
int zIndex;
for (ChunkPos pos : chunkPosToSkip)
{
vanillaRenderedChunksEmptySkip = false;
xIndex = (pos.x - mc.getPlayer().xChunk) + (chunkRenderDistance + 1);
zIndex = (pos.z - mc.getPlayer().zChunk) + (chunkRenderDistance + 1);
// sometimes we are given chunks that are outside the render distance,
// This prevents index out of bounds exceptions
if (xIndex >= 0 && zIndex >= 0
&& xIndex < vanillaRenderedChunks.length
&& zIndex < vanillaRenderedChunks.length)
{
if (!vanillaRenderedChunks[xIndex][zIndex])
{
vanillaRenderedChunks[xIndex][zIndex] = true;
vanillaRenderedChunksChanged = true;
lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
}
}
}
// if the player is high enough, draw all LODs
if (chunkPosToSkip.isEmpty() && mc.getPlayer().position().y > 256 && !vanillaRenderedChunksEmptySkip)
{
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
vanillaRenderedChunksChanged = true;
vanillaRenderedChunksEmptySkip = true;
}
}
}
@@ -0,0 +1,123 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.render;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.vector.Vector3d;
/**
* This holds miscellaneous helper code
* to be used in the rendering process.
*
* @author James Seibel
* @version 10-19-2021
*/
public class RenderUtil
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* Returns if the given ChunkPos is in the loaded area of the world.
* @param center the center of the loaded world (probably the player's ChunkPos)
*/
public static boolean isChunkPosInLoadedArea(ChunkPos pos, ChunkPos center)
{
return (pos.x >= center.x - mc.getRenderDistance()
&& pos.x <= center.x + mc.getRenderDistance())
&&
(pos.z >= center.z - mc.getRenderDistance()
&& pos.z <= center.z + mc.getRenderDistance());
}
/**
* Returns if the given coordinate is in the loaded area of the world.
* @param centerCoordinate the center of the loaded world
*/
public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
{
return (x >= centerCoordinate - mc.getRenderDistance()
&& x <= centerCoordinate + mc.getRenderDistance())
&&
(z >= centerCoordinate - mc.getRenderDistance()
&& z <= centerCoordinate + mc.getRenderDistance());
}
/**
* Find the coordinates that are in the center half of the given
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
*/
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
(j >= lodRadius - halfRadius
&& j <= lodRadius + halfRadius);
}
/**
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(BlockPos playerBlockPos, Vector3d cameraDir, BlockPos vboCenterPos)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vector3d vboVec = new Vector3d(vboCenterPos.getX(), 0, vboCenterPos.getZ());
Vector3d playerVec = new Vector3d(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
Vector3d vboCenterVec = vboVec.subtract(playerVec);
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vector3d vboSeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vector3d vboSwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
Vector3d vboNwVec = new Vector3d(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
Vector3d vboNeVec = new Vector3d(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
}
/**
* Currently takes the dot product of the two vectors,
* but in the future could do more complicated frustum culling tests.
*/
private static boolean isNormalizedVectorInViewFrustum(Vector3d objectVector, Vector3d cameraDir)
{
// the -0.1 is to offer a slight buffer, so we are
// more likely to render LODs and thus, hopefully prevent
// flickering or odd disappearances
return objectVector.dot(cameraDir) > -0.1;
}
}
@@ -0,0 +1,115 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import java.awt.Color;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
*
* @author Cola
* @author Leonardo Amato
* @version ??
*/
public class ColorUtil
{
public static int rgbToInt(int red, int green, int blue)
{
return (0xFF << 24) | (red << 16) | (green << 8) | blue;
}
public static int rgbToInt(int alpha, int red, int green, int blue)
{
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
public static int getAlpha(int color)
{
return (color >> 24) & 0xFF;
}
public static int getRed(int color)
{
return (color >> 16) & 0xFF;
}
public static int getGreen(int color)
{
return (color >> 8) & 0xFF;
}
public static int getBlue(int color)
{
return color & 0xFF;
}
public static int applyShade(int color, int shade)
{
if (shade < 0)
return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0);
else
return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255);
}
public static int applyShade(int color, float shade)
{
if (shade < 1)
return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0);
else
return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255);
}
/** This method apply the lightmap to the color to use */
public static int applyLightValue(int color, int skyLight, int blockLight)
{
int lightColor = MinecraftWrapper.INSTANCE.getColorIntFromLightMap(blockLight, skyLight);
int red = ColorUtil.getBlue(lightColor);
int green = ColorUtil.getGreen(lightColor);
int blue = ColorUtil.getRed(lightColor);
return ColorUtil.multiplyRGBcolors(color, ColorUtil.rgbToInt(red, green, blue));
}
/** Edit the given color as an HSV (Hue Saturation Value) color */
public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier)
{
float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), null);
return Color.getHSBColor(
hsv[0], // hue
LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f),
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f)).getRGB();
}
/** Multiply 2 RGB colors */
public static int multiplyRGBcolors(int color1, int color2)
{
return ((getAlpha(color1) * getAlpha(color2) / 255) << 24) | ((getRed(color1) * getRed(color2) / 255) << 16) | ((getGreen(color1) * getGreen(color2) / 255) << 8) | (getBlue(color1) * getBlue(color2) / 255);
}
@SuppressWarnings("unused")
public static String toString(int color)
{
return Integer.toHexString(getAlpha(color)) + " " +
Integer.toHexString(getRed(color)) + " " +
Integer.toHexString(getGreen(color)) + " " +
Integer.toHexString(getBlue(color));
}
}
@@ -0,0 +1,520 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import static com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.skyLightPlayer;
import com.seibel.lod.enums.DistanceGenerationMode;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class DataPointUtil
{
/*
|a |a |a |a |r |r |r |r |
|r |r |r |r |g |g |g |g |
|g |g |g |g |b |b |b |b |
|b |b |b |b |h |h |h |h |
|h |h |h |h |h |h |d |d |
|d |d |d |d |d |d |d |d |
|bl |bl |bl |bl |sl |sl |sl |sl |
|l |l |f |g |g |g |v |e |
*/
// Reminder: bytes have range of [-128, 127].
// When converting to or from an int a 128 should be added or removed.
// If there is a bug with color then it's probably caused by this.
//To be used in the future for negative value
//public final static int MIN_DEPTH = -64;
//public final static int MIN_HEIGHT = -64;
public final static int EMPTY_DATA = 0;
public static int worldHeight = 256;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
//public final static int BLUE_COLOR_SHIFT = 0;
//public final static int GREEN_COLOR_SHIFT = 8;
//public final static int RED_COLOR_SHIFT = 16;
//public final static int ALPHA_COLOR_SHIFT = 24;
public final static int BLUE_SHIFT = 36;
public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
public final static int RED_SHIFT = BLUE_SHIFT + 16;
public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
public final static int COLOR_SHIFT = 36;
public final static int HEIGHT_SHIFT = 26;
public final static int DEPTH_SHIFT = 16;
public final static int BLOCK_LIGHT_SHIFT = 12;
public final static int SKY_LIGHT_SHIFT = 8;
//public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT;
//public final static int VERTICAL_INDEX_SHIFT = 6;
public final static int FLAG_SHIFT = 5;
public final static int GEN_TYPE_SHIFT = 2;
public final static int VOID_SHIFT = 1;
public final static int EXISTENCE_SHIFT = 0;
public final static long ALPHA_MASK = 0b1111;
public final static long RED_MASK = 0b1111_1111;
public final static long GREEN_MASK = 0b1111_1111;
public final static long BLUE_MASK = 0b1111_1111;
public final static long COLOR_MASK = 0b11111111_11111111_11111111;
public final static long HEIGHT_MASK = 0b11_1111_1111;
public final static long DEPTH_MASK = 0b11_1111_1111;
//public final static long LIGHTS_MASK = 0b1111_1111;
public final static long BLOCK_LIGHT_MASK = 0b1111;
public final static long SKY_LIGHT_MASK = 0b1111;
//public final static long VERTICAL_INDEX_MASK = 0b11;
public final static long FLAG_MASK = 0b1;
public final static long GEN_TYPE_MASK = 0b111;
public final static long VOID_MASK = 1;
public final static long EXISTENCE_MASK = 1;
public static long createVoidDataPoint(int generationMode)
{
long dataPoint = 0;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
dataPoint += VOID_MASK << VOID_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag)
{
return createDataPoint(
ColorUtil.getAlpha(color),
ColorUtil.getRed(color),
ColorUtil.getGreen(color),
ColorUtil.getBlue(color),
height, depth, lightSky, lightBlock, generationMode, flag);
}
public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode, boolean flag)
{
long dataPoint = 0;
dataPoint += (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT;
dataPoint += (red & RED_MASK) << RED_SHIFT;
dataPoint += (green & GREEN_MASK) << GREEN_SHIFT;
dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT;
dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT;
dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT;
dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
if (flag) dataPoint += FLAG_MASK << FLAG_SHIFT;
dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
return dataPoint;
}
public static short getHeight(long dataPoint)
{
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
}
public static short getDepth(long dataPoint)
{
return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK);
}
public static short getAlpha(long dataPoint)
{
return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111);
}
public static short getRed(long dataPoint)
{
return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
}
public static short getGreen(long dataPoint)
{
return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
}
public static short getBlue(long dataPoint)
{
return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
}
public static byte getLightSky(long dataPoint)
{
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightSkyAlt(long dataPoint)
{
if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
return 0;
else
return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static byte getLightBlock(long dataPoint)
{
return (byte) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
}
public static boolean getFlag(long dataPoint)
{
return ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1;
}
public static byte getGenerationMode(long dataPoint)
{
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
}
public static boolean isVoid(long dataPoint)
{
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
}
public static boolean doesItExist(long dataPoint)
{
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
}
public static int getColor(long dataPoint)
{
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
}
/** This is used to convert a dataPoint to string (useful for the print function) */
@SuppressWarnings("unused")
public static String toString(long dataPoint)
{
return getHeight(dataPoint) + " " +
getDepth(dataPoint) + " " +
getAlpha(dataPoint) + " " +
getRed(dataPoint) + " " +
getBlue(dataPoint) + " " +
getGreen(dataPoint) + " " +
getLightBlock(dataPoint) + " " +
getLightSky(dataPoint) + " " +
getGenerationMode(dataPoint) + " " +
isVoid(dataPoint) + " " +
doesItExist(dataPoint) + '\n';
}
public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = 0; i < arraySize - start; i++)
{
array[start + i] = array[start + length + i];
//remove comment to not leave garbage at the end
//array[start + packetSize + i] = 0;
}
}
public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
{
start *= packetSize;
length *= packetSize;
arraySize *= packetSize;
for (int i = arraySize - start - 1; i >= 0; i--)
{
array[start + length + i] = array[start + i];
array[start + i] = 0;
}
}
/**
* This method merge column of multiple data together
* @param dataToMerge one or more columns of data
* @param inputVerticalData vertical size of an input data
* @param maxVerticalData max vertical size of the merged data
* @return one column of correctly parsed data
*/
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
{
int size = dataToMerge.length / inputVerticalData;
// We initialize the arrays that are going to be used
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((worldHeight / 2 + 1) * 2);
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
int genMode = DistanceGenerationMode.SERVER.complexity;
boolean allEmpty = true;
boolean allVoid = true;
boolean allDefault;
long singleData;
short depth;
short height;
int count = 0;
int i;
int ii;
int dataIndex;
//We collect the indexes of the data, ordered by the depth
for (int index = 0; index < size; index++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData))
{
genMode = Math.min(genMode, getGenerationMode(singleData));
allEmpty = false;
if (!isVoid(singleData))
{
allVoid = false;
depth = getDepth(singleData);
height = getHeight(singleData);
int botPos = -1;
int topPos = -1;
//values fall in between and possibly require extension of array
boolean botExtend = false;
boolean topExtend = false;
for (i = 0; i < count; i++)
{
if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
{
botPos = i;
break;
}
else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
botPos = i;
botExtend = true;
break;
}
}
for (i = 0; i < count; i++)
{
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
{
topPos = i;
break;
}
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
{
topPos = i;
topExtend = true;
break;
}
}
if (topPos == -1)
{
if (botPos == -1)
{
//whole block falls above
extendArray(heightAndDepth, 2, 0, 1, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count++;
}
else if (!botExtend)
{
//only top falls above extending it there, while bottom is inside existing
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
count -= botPos;
}
else
{
//top falls between some blocks, extending those as well
shrinkArray(heightAndDepth, 2, 0, botPos, count);
heightAndDepth[0] = height;
heightAndDepth[1] = depth;
count -= botPos;
}
}
else if (!topExtend)
{
if (!botExtend)
//both top and bottom are within some exiting blocks, possibly merging them
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
else
//top falls between some blocks, extending it there
heightAndDepth[topPos * 2 + 1] = depth;
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
if (!botExtend)
{
//only top is within some exiting block, extending it
topPos++; //to make it easier
heightAndDepth[topPos * 2] = height;
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
}
else
{
//both top and bottom are outside existing blocks
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
count -= botPos - topPos;
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
count++;
heightAndDepth[topPos * 2 + 2] = height;
heightAndDepth[topPos * 2 + 3] = depth;
}
}
}
}
else
break;
}
}
//We check if there is any data that's not empty or void
if (allEmpty)
return dataPoint;
if (allVoid)
{
dataPoint[0] = createVoidDataPoint(genMode);
return dataPoint;
}
//we limit the vertical portion to maxVerticalData
int j = 0;
while (count > maxVerticalData)
{
ii = worldHeight;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
{
ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
j = i;
}
}
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
for (i = j + 1; i < count - 1; i++)
{
heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2];
heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
}
//System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1);
count--;
}
//As standard the vertical lods are ordered from top to bottom
for (j = count - 1; j >= 0; j--)
{
height = heightAndDepth[j * 2];
depth = heightAndDepth[j * 2 + 1];
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
break;
int numberOfChildren = 0;
int tempAlpha = 0;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempLightBlock = 0;
int tempLightSky = 0;
byte tempGenMode = DistanceGenerationMode.SERVER.complexity;
allEmpty = true;
allVoid = true;
allDefault = true;
long data = 0;
for (int index = 0; index < size; index++)
{
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
{
singleData = dataToMerge[index * inputVerticalData + dataIndex];
if (doesItExist(singleData) && !isVoid(singleData))
{
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
{
if (getHeight(singleData) > getHeight(data))
data = singleData;
}
}
else
break;
}
if (!doesItExist(data))
{
singleData = dataToMerge[index * inputVerticalData];
data = createVoidDataPoint(getGenerationMode(singleData));
}
if (doesItExist(data))
{
allEmpty = false;
if (!isVoid(data))
{
numberOfChildren++;
allVoid = false;
tempAlpha += getAlpha(data);
tempRed += getRed(data);
tempGreen += getGreen(data);
tempBlue += getBlue(data);
tempLightBlock += getLightBlock(data);
tempLightSky += getLightSky(data);
if (!getFlag(data)) allDefault = false;
}
tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
}
else
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
}
if (allEmpty)
//no child has been initialized
dataPoint[j] = EMPTY_DATA;
else if (allVoid)
//all the children are void
dataPoint[j] = createVoidDataPoint(tempGenMode);
else
{
//we have at least 1 child
tempAlpha = tempAlpha / numberOfChildren;
tempRed = tempRed / numberOfChildren;
tempGreen = tempGreen / numberOfChildren;
tempBlue = tempBlue / numberOfChildren;
tempLightBlock = tempLightBlock / numberOfChildren;
tempLightSky = tempLightSky / numberOfChildren;
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
}
}
return dataPoint;
}
}
@@ -0,0 +1,171 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class DetailDistanceUtil
{
private static final double genMultiplier = 1.0;
private static final double treeGenMultiplier = 1.0;
private static final double treeCutMultiplier = 1.0;
private static byte minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
private static byte minDrawDetail = (byte) Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static final int minDistance = 0;
private static int minDetailDistance = (int) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
private static int maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 16 * 2;
private static final HorizontalResolution[] lodGenDetails = {
HorizontalResolution.BLOCK,
HorizontalResolution.TWO_BLOCKS,
HorizontalResolution.FOUR_BLOCKS,
HorizontalResolution.HALF_CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK,
HorizontalResolution.CHUNK };
public static void updateSettings()
{
minDetailDistance = (int) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
minDrawDetail = (byte) Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 16 * 8;
}
public static int baseDistanceFunction(int detail)
{
if (detail <= minGenDetail)
return minDistance;
if (detail >= maxDetail)
return maxDistance;
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return detail * 0x10000; //if you want more you are doing wrong
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
return (detail * distanceUnit);
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().quadraticBase;
return (int) (Math.pow(base, detail) * distanceUnit);
}
}
public static int getDrawDistanceFromDetail(int detail)
{
return baseDistanceFunction(detail);
}
public static byte baseInverseFunction(int distance, byte minDetail, boolean useRenderMinDistance)
{
int detail;
if (distance == 0
|| (distance < minDetailDistance && useRenderMinDistance)
|| LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return minDetail;
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
detail = (byte) distance / distanceUnit;
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().quadraticBase;
double logBase = Math.log(base);
//noinspection IntegerDivisionInFloatingPointContext
detail = (byte) (Math.log(distance / distanceUnit) / logBase);
}
return (byte) LodUtil.clamp(minDetail, detail, maxDetail - 1);
}
public static byte getDrawDetailFromDistance(int distance)
{
return baseInverseFunction(distance, minDrawDetail, false);
}
public static byte getGenerationDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * genMultiplier), minGenDetail, true);
}
public static byte getTreeCutDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail, true);
}
public static byte getTreeGenDetailFromDistance(int distance)
{
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
}
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
{
return LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
}
public static byte getLodDrawDetail(int detail)
{
if (detail < minDrawDetail)
return (byte) minDrawDetail;
else
return (byte) detail;
}
public static HorizontalResolution getLodGenDetail(int detail)
{
if (detail < minGenDetail)
return lodGenDetails[minGenDetail];
else
return lodGenDetails[detail];
}
public static byte getCutLodDetail(int detail)
{
if (detail < minGenDetail)
return lodGenDetails[minGenDetail].detailLevel;
else if (detail == maxDetail)
return LodUtil.REGION_DETAIL_LEVEL;
else
return lodGenDetails[detail].detailLevel;
}
public static int getMaxVerticalData(int detail)
{
return LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
}
}
@@ -0,0 +1,269 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class LevelPosUtil
{
public static int[] convert(int[] levelPos, byte newDetailLevel)
{
return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel);
}
public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel)
{
int width;
if (newDetailLevel >= detailLevel)
{
width = 1 << (newDetailLevel - detailLevel);
return createLevelPos(
newDetailLevel,
Math.floorDiv(posX, width),
Math.floorDiv(posZ, width));
}
else
{
width = 1 << (detailLevel - newDetailLevel);
return createLevelPos(
newDetailLevel,
posX * width,
posZ * width);
}
}
public static int[] createLevelPos(byte detailLevel, int posX, int posZ)
{
return new int[] { detailLevel, posX, posZ };
}
public static int convert(byte detailLevel, int pos, byte newDetailLevel)
{
int width;
if (newDetailLevel >= detailLevel)
{
width = 1 << (newDetailLevel - detailLevel);
return Math.floorDiv(pos, width);
}
else
{
width = 1 << (detailLevel - newDetailLevel);
return pos * width;
}
}
public static int getRegion(byte detailLevel, int pos)
{
return Math.floorDiv(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static int getRegionModule(byte detailLevel, int pos)
{
return Math.floorMod(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
}
public static byte getDetailLevel(int[] levelPos)
{
return (byte) levelPos[0];
}
public static int getPosX(int[] levelPos)
{
return levelPos[1];
}
public static int getPosZ(int[] levelPos)
{
return levelPos[2];
}
public static int getDistance(int[] levelPos)
{
return levelPos[3];
}
public static int[] getRegionModule(int[] levelPos)
{
return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos));
}
public static int[] getRegionModule(byte detailLevel, int posX, int posZ)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
return createLevelPos(
detailLevel,
Math.floorMod(posX, width),
Math.floorMod(posZ, width));
}
public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset)
{
return createLevelPos(
getDetailLevel(levelPos),
getPosX(levelPos) + xOffset,
getPosZ(levelPos) + zOffset);
}
public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset)
{
return createLevelPos(
getDetailLevel(levelPos),
getPosX(levelPos) + xOffset * (1 << detailOffset),
getPosZ(levelPos) + zOffset * (1 << detailOffset));
}
public static int getRegionPosX(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosX(levelPos), width);
}
public static int getRegionPosZ(int[] levelPos)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
return Math.floorDiv(getPosZ(levelPos), width);
}
public static int getChunkPos(byte detailLevel, int pos)
{
return convert(detailLevel, pos, LodUtil.CHUNK_DETAIL_LEVEL);
}
public static int myPow2(int x)
{
return x*x;
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = myPow2(playerPosX - startPosX - width);
int endPosZ = myPow2(playerPosZ - startPosZ - width);
startPosX = myPow2(playerPosX - startPosX);
startPosZ = myPow2(playerPosZ - startPosZ);
int maxDistance = (int) Math.sqrt(startPosX + startPosZ);
maxDistance = Math.max(maxDistance, (int) Math.sqrt(startPosX + endPosZ));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + startPosZ));
maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + endPosZ));
return maxDistance;
}
public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int newPosX = xRegion * width + posX;
int newPosZ = zRegion * width + posZ;
return maxDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
{
int width = 1 << detailLevel;
int startPosX = posX * width;
int startPosZ = posZ * width;
int endPosX = startPosX + width;
int endPosZ = startPosZ + width;
boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
if (inXArea && inZArea)
return 0;
else if (inXArea)
{
return Math.min(
Math.abs(playerPosZ - startPosZ),
Math.abs(playerPosZ - endPosZ)
);
}
else if (inZArea)
{
return Math.min(
Math.abs(playerPosX - startPosX),
Math.abs(playerPosX - endPosX)
);
}
else
{
startPosX = myPow2(playerPosX - startPosX);
startPosZ = myPow2(playerPosZ - startPosZ);
endPosX = myPow2(playerPosX - endPosX);
endPosZ = myPow2(playerPosZ - endPosZ);
int minDistance = (int) Math.sqrt(startPosX + startPosZ);
minDistance = Math.min(minDistance, (int) Math.sqrt(startPosX + endPosZ));
minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + startPosZ));
minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + endPosZ));
return minDistance;
}
}
public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
{
int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
int newPosX = xRegion * width + posX;
int newPosZ = zRegion * width + posZ;
return minDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
}
public static int compareDistance(int firstDistance, int secondDistance)
{
return Integer.compare(
firstDistance,
secondDistance);
}
public static int compareLevelAndDistance(byte firstDetail, int firstDistance, byte secondDetail, int secondDistance)
{
int compareResult = Integer.compare(
secondDetail,
firstDetail);
if (compareResult == 0)
{
compareResult = Integer.compare(
firstDistance,
secondDistance);
}
return compareResult;
}
@SuppressWarnings("unused")
public static String toString(int[] levelPos)
{
return (getDetailLevel(levelPos) + " "
+ getPosX(levelPos) + " "
+ getPosZ(levelPos));
}
public static String toString(byte detailLevel, int posX, int posZ)
{
return (detailLevel + " " + posX + " " + posZ);
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import java.util.concurrent.ThreadFactory;
/**
* Just a simple ThreadFactory to name ExecutorService
* threads, which can be helpful when debugging.
* @author James Seibel
* @version 8-15-2021
*/
public class LodThreadFactory implements ThreadFactory
{
public final String threadName;
public LodThreadFactory(String newThreadName)
{
threadName = newThreadName + " Thread";
}
@Override
public Thread newThread(Runnable r)
{
return new Thread(r, threadName);
}
}
@@ -0,0 +1,499 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import java.awt.Color;
import java.io.File;
import java.util.HashSet;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.CompiledChunk;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
/**
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 10-20-2021
*/
public class LodUtil
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* Vanilla render distances less than or equal to this will not allow partial
* overdraw. The VanillaOverdraw will either be ALWAYS or NEVER.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4;
/**
* Vanilla render distances less than or equal to this will cause the overdraw to
* run at a smaller fraction of the vanilla render distance.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11;
/** The maximum number of LODs that can be rendered vertically */
public static final int MAX_NUMBER_OF_VERTICAL_LODS = 32;
/**
* alpha used when drawing chunks in debug mode
*/
public static final int DEBUG_ALPHA = 255; // 0 - 255
public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
public static final Color COLOR_INVISIBLE = new Color(0, 0, 0, 0);
public static final int CEILED_DIMENSION_MAX_RENDER_DISTANCE = 64; // 0 - 255
/**
* In order of nearest to farthest: <br>
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
*/
public static final Color[] DEBUG_DETAIL_LEVEL_COLORS = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
public static final byte DETAIL_OPTIONS = 10;
/** 512 blocks wide */
public static final byte REGION_DETAIL_LEVEL = DETAIL_OPTIONS - 1;
/** 16 blocks wide */
public static final byte CHUNK_DETAIL_LEVEL = 4;
/** 1 block wide */
public static final byte BLOCK_DETAIL_LEVEL = 0;
public static final short MAX_VERTICAL_DATA = 4;
/**
* measured in Blocks <br>
* detail level max - 1
*/
public static final short REGION_WIDTH = 1 << REGION_DETAIL_LEVEL;
/**
* measured in Blocks <br>
* detail level 4
*/
public static final short CHUNK_WIDTH = 16;
/**
* measured in Blocks <br>
* detail level 0
*/
public static final short BLOCK_WIDTH = 1;
/** number of chunks wide */
public static final int REGION_WIDTH_IN_CHUNKS = REGION_WIDTH / CHUNK_WIDTH;
/**
* If we ever need to use a heightmap for any reason, use this one.
*/
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
/**
* This regex finds any characters that are invalid for use in a windows
* (and by extension mac and linux) file path
*/
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
/**
* 64 MB by default is the maximum amount of memory that
* can be directly allocated. <br><br>
* <p>
* I know there are commands to change that amount
* (specifically "-XX:MaxDirectMemorySize"), but
* I have no idea how to access that amount. <br>
* So I guess this will be the hard limit for now. <br><br>
* <p>
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
/**
* Gets the first valid ServerWorld.
* @return null if there are no ServerWorlds
*/
public static ServerWorld getFirstValidServerWorld()
{
if (mc.hasSinglePlayerServer())
return null;
Iterable<ServerWorld> worlds = mc.getSinglePlayerServer().getAllLevels();
for (ServerWorld world : worlds)
return world;
return null;
}
/**
* Gets the ServerWorld for the relevant dimension.
* @return null if there is no ServerWorld for the given dimension
*/
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
{
IntegratedServer server = mc.getSinglePlayerServer();
if (server == null)
return null;
Iterable<ServerWorld> worlds = server.getAllLevels();
ServerWorld returnWorld = null;
for (ServerWorld world : worlds)
{
if (world.dimensionType() == dimension)
{
returnWorld = world;
break;
}
}
return returnWorld;
}
/** Convert a 2D absolute position into a quad tree relative position. */
public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel)
{
int relativePosX = Math.floorDiv(x, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
int relativePosZ = Math.floorDiv(z, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
return new RegionPos(relativePosX, relativePosZ);
}
/** Convert a 2D absolute position into a quad tree relative position. */
public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel)
{
return pos / (1 << (targetDetailLevel - currentDetailLevel));
}
/**
* Return whether the given chunk
* has any data in it.
*/
public static boolean chunkHasBlockData(IChunk chunk)
{
ChunkSection[] blockStorage = chunk.getSections();
for (ChunkSection section : blockStorage)
{
if (section != null && !section.isEmpty())
return true;
}
return false;
}
/**
* If on single player this will return the name of the user's
* world, if in multiplayer it will return the server name, IP,
* and game version.
*/
public static String getWorldID(IWorld world)
{
if (mc.hasSinglePlayerServer())
{
// chop off the dimension ID as it is not needed/wanted
String dimId = getDimensionIDFromWorld(world);
// get the world name
int saveIndex = dimId.indexOf("saves") + 1 + "saves".length();
int slashIndex = dimId.indexOf(File.separatorChar, saveIndex);
dimId = dimId.substring(saveIndex, slashIndex);
return dimId;
}
else
{
return getServerId();
}
}
/**
* If on single player this will return the name of the user's
* world and the dimensional save folder, if in multiplayer
* it will return the server name, ip, game version, and dimension.<br>
* <br>
* This can be used to determine where to save files for a given
* dimension.
*/
public static String getDimensionIDFromWorld(IWorld world)
{
if (mc.hasSinglePlayerServer())
{
// this will return the world save location
// and the dimension folder
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(world.dimensionType());
if (serverWorld == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.dimensionType().effectsLocation().getPath());
ServerChunkProvider provider = serverWorld.getChunkSource();
if (provider == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.dimensionType().effectsLocation().getPath());
return provider.dataStorage.dataFolder.toString();
}
else
{
return getServerId() + File.separatorChar + "dim_" + world.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/** returns the server name, IP and game version. */
public static String getServerId()
{
ServerData server = mc.getCurrentServer();
String serverName = server.name.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverIp = server.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverMcVersion = server.version.getString().replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
return serverName + ", IP " + serverIp + ", GameVersion " + serverMcVersion;
}
/** Convert a BlockColors int into a Color object */
public static Color intToColor(int num)
{
int filter = 0b11111111;
int red = (num >> 16) & filter;
int green = (num >> 8) & filter;
int blue = num & filter;
return new Color(red, green, blue);
}
/** Convert a Color into a BlockColors object. */
public static int colorToInt(Color color)
{
return color.getRGB();
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static int clamp(int min, int value, int max)
{
return Math.min(max, Math.max(value, min));
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static float clamp(float min, float value, float max)
{
return Math.min(max, Math.max(value, min));
}
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static double clamp(double min, double value, double max)
{
return Math.min(max, Math.max(value, min));
}
/**
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
public static HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.getRenderDistance();
ChunkPos centerChunk = new ChunkPos(playerPos);
int skipRadius;
VanillaOverdraw overdraw = LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get();
HorizontalResolution drawRes = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get();
// apply distance based rules for dynamic overdraw
if (overdraw == VanillaOverdraw.DYNAMIC
&& chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW)
{
// The vanilla render distance isn't far enough
// for partial skipping to make sense...
if (!lodDim.dimension.hasCeiling() && (drawRes == HorizontalResolution.BLOCK))
{
// ...and the dimension is open, so we don't have to worry about
// LODs rendering on top of the player,
// and the user is using a high horizontal resolution,
// so the overdraw shouldn't be noticeable
overdraw = VanillaOverdraw.ALWAYS;
}
else
{
// ...but we are underground, so we don't want
// LODs rendering on top of the player,
// Or the user is using a LOW horizontal resolution
// and overdraw would be very noticeable.
overdraw = VanillaOverdraw.NEVER;
}
}
// determine the skipping type based
// on the overdraw type
switch (overdraw)
{
case ALWAYS:
// don't skip any positions
return new HashSet<>();
case DYNAMIC:
if (chunkRenderDist > MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW
&& chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
{
// This is a small render distance (but greater than the minimum partial
// distance), skip positions that are greater than 2/3 the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (2.0/3.0));
}
else
{
// This is a large render distance. Skip positions that are greater than
// 4/5ths the render distance
skipRadius = (int) Math.ceil(chunkRenderDist * (4.0 / 5.0));
}
break;
default:
case BORDER:
case NEVER:
// skip chunks in render distance that are rendered
// by vanilla minecraft
skipRadius = 0;
break;
}
// get the chunks that are going to be rendered by Minecraft
HashSet<ChunkPos> posToSkip = getRenderedChunks();
// remove everything outside the skipRadius,
// if the skipRadius is being used
if (skipRadius != 0)
{
for (int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
{
for (int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
if (x <= centerChunk.x - skipRadius || x >= centerChunk.x + skipRadius
|| z <= centerChunk.z - skipRadius || z >= centerChunk.z + skipRadius)
posToSkip.remove(new ChunkPos(x, z));
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
public static HashSet<ChunkPos> getRenderedChunks()
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
// Wow, those are some long names!
// go through every RenderInfo to get the compiled chunks
WorldRenderer renderer = mc.getLevelRenderer();
for (WorldRenderer.LocalRenderInformationContainer worldRenderer$LocalRenderInformationContainer : renderer.renderChunks)
{
CompiledChunk compiledChunk = worldRenderer$LocalRenderInformationContainer.chunk.getCompiledChunk();
if (!compiledChunk.hasNoRenderableLayers())
{
// add the ChunkPos for every rendered chunk
BlockPos bpos = worldRenderer$LocalRenderInformationContainer.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos));
}
}
return loadedPos;
}
/**
* This method find if a given chunk is a border chunk of the renderable ones
* @param vanillaRenderedChunks matrix of the vanilla rendered chunks
* @param x relative (to the matrix) x chunk to check
* @param z relative (to the matrix) z chunk to check
* @return true if and only if the chunk is a border of the renderable chunks
*/
public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z)
{
if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length)
return false;
int tempX;
int tempZ;
for (Direction direction : Box.ADJ_DIRECTIONS)
{
tempX = x + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
tempZ = z + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
if (vanillaRenderedChunks[x][z] || (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length)
&& !vanillaRenderedChunks[tempX][tempZ]))
return true;
}
return false;
}
}
@@ -0,0 +1,219 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import static com.seibel.lod.util.LodUtil.DETAIL_OPTIONS;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import net.minecraft.util.Direction;
/**
* Holds data used by specific threads so
* the data doesn't have to be recreated every
* time it is needed.
*
* @author Leonardo Amato
* @version 9-25-2021
*/
public class ThreadMapUtil
{
public static final ConcurrentMap<String, long[]> threadSingleUpdateMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderVerticalArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> threadVerticalAddDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, byte[][]> saveContainer = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> verticalUpdate = new ConcurrentHashMap<>();
//________________________//
// used in BufferBuilder //
//________________________//
public static final ConcurrentMap<String, boolean[]> adjShadeDisabled = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Map<Direction, long[]>> adjDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Box> boxMap = new ConcurrentHashMap<>();
/** returns the array NOT cleared every time */
public static boolean[] getAdjShadeDisabledArray()
{
if (!adjShadeDisabled.containsKey(Thread.currentThread().getName())
|| (adjShadeDisabled.get(Thread.currentThread().getName()) == null))
{
adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[Box.DIRECTIONS.length]);
}
Arrays.fill(adjShadeDisabled.get(Thread.currentThread().getName()), false);
return adjShadeDisabled.get(Thread.currentThread().getName());
}
/** returns the array NOT cleared every time */
public static Map<Direction, long[]> getAdjDataArray(int verticalData)
{
if (!adjDataMap.containsKey(Thread.currentThread().getName())
|| (adjDataMap.get(Thread.currentThread().getName()) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH).length != verticalData))
{
adjDataMap.put(Thread.currentThread().getName(), new HashMap<Direction, long[]>());
adjDataMap.get(Thread.currentThread().getName()).put(Direction.UP, new long[1]);
adjDataMap.get(Thread.currentThread().getName()).put(Direction.DOWN, new long[1]);
for (Direction direction : Box.ADJ_DIRECTIONS)
adjDataMap.get(Thread.currentThread().getName()).put(direction, new long[verticalData]);
}
else
{
for (Direction direction : Box.ADJ_DIRECTIONS)
Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(direction), DataPointUtil.EMPTY_DATA);
}
return adjDataMap.get(Thread.currentThread().getName());
}
public static Box getBox()
{
if (!boxMap.containsKey(Thread.currentThread().getName())
|| (boxMap.get(Thread.currentThread().getName()) == null))
{
boxMap.put(Thread.currentThread().getName(), new Box());
}
boxMap.get(Thread.currentThread().getName()).reset();
return boxMap.get(Thread.currentThread().getName());
}
//________________________//
// used in DataPointUtil //
// mergeVerticalData //
//________________________//
//________________________//
// used in DataPointUtil //
// mergeSingleData //
//________________________//
/** returns the array filled with 0's */
public static long[] getBuilderVerticalArray(int detailLevel)
{
if (!threadBuilderVerticalArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderVerticalArrayMap.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[5][];
int size;
for (int i = 0; i < 5; i++)
{
size = 1 << i;
array[i] = new long[size * size * (DataPointUtil.worldHeight / 2 + 1)];
}
threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array);
}
Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array NOT cleared every time */
public static byte[] getSaveContainer(int detailLevel)
{
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
{
byte[][] array = new byte[DETAIL_OPTIONS][];
int size = 1;
for (int i = DETAIL_OPTIONS - 1; i >= 0; i--)
{
array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)];
size = size << 1;
}
saveContainer.put(Thread.currentThread().getName(), array);
}
//Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
return saveContainer.get(Thread.currentThread().getName())[detailLevel];
}
/** returns the array filled with 0's */
public static long[] getVerticalDataArray(int arrayLength)
{
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
{
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
}
else
{
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
}
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
}
/** returns the array NOT cleared every time */
public static short[] getHeightAndDepth(int arrayLength)
{
if (!heightAndDepthMap.containsKey(Thread.currentThread().getName()) || (heightAndDepthMap.get(Thread.currentThread().getName()) == null))
{
heightAndDepthMap.put(Thread.currentThread().getName(), new short[arrayLength]);
}
return heightAndDepthMap.get(Thread.currentThread().getName());
}
/** returns the array filled with 0's */
public static long[] getVerticalUpdateArray(int detailLevel)
{
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
{
long[][] array = new long[DETAIL_OPTIONS][];
for (int i = 1; i < DETAIL_OPTIONS; i++)
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
verticalUpdate.put(Thread.currentThread().getName(), array);
}
else
{
Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
}
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
}
/** clears all arrays so they will have to be rebuilt */
public static void clearMaps()
{
adjShadeDisabled.clear();
adjDataMap.clear();
boxMap.clear();
threadSingleUpdateMap.clear();
threadBuilderArrayMap.clear();
threadBuilderVerticalArrayMap.clear();
threadVerticalAddDataMap.clear();
saveContainer.clear();
projectionArrayMap.clear();
heightAndDepthMap.clear();
singleDataToMergeMap.clear();
verticalUpdate.clear();
}
}
@@ -0,0 +1,279 @@
package com.seibel.lod.wrappers.Block;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.block.*;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.client.model.data.ModelDataMap;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//This class wraps the minecraft Block class
public class BlockColorWrapper
{
//set of block which require tint
public static final ConcurrentMap<Block, BlockColorWrapper> blockColorWrapperMap = new ConcurrentHashMap<>();
public static final ModelDataMap dataMap = new ModelDataMap.Builder().build();
public static final BlockPos blockPos = new BlockPos(0,0,0);
public static final Random random = new Random(0);
//public static BlockColourWrapper WATER_COLOR = getBlockColorWrapper(Blocks.WATER);
public static final Direction[] directions = new Direction[] { Direction.UP, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.DOWN };
private final Block block;
private int color;
private boolean isColored;
private boolean toTint;
private boolean foliageTint;
private boolean grassTint;
private boolean waterTint;
/**Constructor only require for the block instance we are wrapping**/
public BlockColorWrapper(BlockState blockState, BlockPosWrapper blockPosWrapper)
{
this.block = blockState.getBlock();
this.color = 0;
this.isColored = true;
this.toTint = false;
this.foliageTint = false;
this.grassTint = false;
this.waterTint = false;
setupColorAndTint(blockState,blockPosWrapper);
//System.out.println(block + " color " + Integer.toHexString(color) + " to tint " + toTint + " folliageTint " + folliageTint + " grassTint " + grassTint + " waterTint " + waterTint);
}
/**
* this return a wrapper of the block in input
* @param blockState of the block to wrap
*/
static public BlockColorWrapper getBlockColorWrapper(BlockState blockState, BlockPosWrapper blockPosWrapper)
{
//first we check if the block has already been wrapped
if (blockColorWrapperMap.containsKey(blockState.getBlock()) && blockColorWrapperMap.get(blockState.getBlock()) != null)
return blockColorWrapperMap.get(blockState.getBlock());
//if it hasn't been created yet, we create it and save it in the map
BlockColorWrapper blockWrapper = new BlockColorWrapper(blockState, blockPosWrapper);
blockColorWrapperMap.put(blockState.getBlock(), blockWrapper);
//we return the newly created wrapper
return blockWrapper;
}
/**
* Generate the color of the given block from its texture
* and store it for later use.
*/
private void setupColorAndTint(BlockState blockState, BlockPosWrapper blockPosWrapper)
{
MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
TextureAtlasSprite texture;
List<BakedQuad> quads = null;
boolean isTinted = false;
int listSize = 0;
// first step is to check if this block has a tinted face
for (Direction direction : directions)
{
quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random, dataMap);
listSize = Math.max(listSize, quads.size());
for (BakedQuad bakedQuad : quads)
{
isTinted |= bakedQuad.isTinted();
}
}
//if it contains a tinted face then we store this block in the toTint set
if (isTinted)
this.toTint = true;
//now we get the first non empty face
for (Direction direction : directions)
{
quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random, dataMap);
if (!quads.isEmpty())
break;
}
//the quads list is not empty we extract the first one
if (!quads.isEmpty())
{
isColored = true;
texture = quads.get(0).getSprite();
}
else
{
isColored = true;
texture = mc.getModelManager().getBlockModelShaper().getTexture(block.defaultBlockState(), mc.getClientWorld(), blockPosWrapper.getBlockPos());
}
int count = 0;
int alpha = 0;
int red = 0;
int green = 0;
int blue = 0;
int numberOfGreyPixel = 0;
int tempColor;
int colorMultiplier;
// generate the block's color
for (int frameIndex = 0; frameIndex < texture.getFrameCount(); frameIndex++)
{
// textures normally use u and v instead of x and y
for (int u = 0; u < texture.getWidth(); u++)
{
for (int v = 0; v < texture.getHeight(); v++)
{
tempColor = texture.getPixelRGBA(frameIndex, u, v);
if (ColorUtil.getAlpha(texture.getPixelRGBA(frameIndex, u, v)) == 0)
continue;
// determine if this pixel is gray
int colorMax = Math.max(Math.max(ColorUtil.getBlue(tempColor), ColorUtil.getGreen(tempColor)), ColorUtil.getRed(tempColor));
int colorMin = 4 + Math.min(Math.min(ColorUtil.getBlue(tempColor), ColorUtil.getGreen(tempColor)), ColorUtil.getRed(tempColor));
boolean isGray = colorMax < colorMin;
if (isGray)
numberOfGreyPixel++;
// for flowers, weight their non-green color higher
if (block instanceof FlowerBlock && (!(ColorUtil.getGreen(tempColor) > (ColorUtil.getBlue(tempColor) + 30)) || !(ColorUtil.getGreen(tempColor) > (ColorUtil.getRed(tempColor) + 30))))
colorMultiplier = 5;
else
colorMultiplier = 1;
// add to the running averages
count += colorMultiplier;
alpha += ColorUtil.getAlpha(tempColor) * colorMultiplier;
red += ColorUtil.getBlue(tempColor) * colorMultiplier;
green += ColorUtil.getGreen(tempColor) * colorMultiplier;
blue += ColorUtil.getRed(tempColor) * colorMultiplier;
}
}
}
if (count == 0)
// this block is entirely transparent
tempColor = 0;
else
{
// determine the average color
alpha /= count;
red /= count;
green /= count;
blue /= count;
tempColor = ColorUtil.rgbToInt(alpha, red, green, blue);
}
// determine if this block should use the biome color tint
if ((grassInstance() || leavesInstance() || waterIstance()) && (float) numberOfGreyPixel / count > 0.75f)
this.toTint = true;
// we check which kind of tint we need to apply
this.grassTint = grassInstance() && toTint;
this.foliageTint = leavesInstance() && toTint;
this.waterTint = waterIstance() && toTint;
color = tempColor;
}
/** determine if the given block should use the biome's grass color */
private boolean grassInstance()
{
return block instanceof GrassBlock
|| block instanceof BushBlock
|| block instanceof IGrowable
|| block instanceof AbstractPlantBlock
|| block instanceof AbstractTopPlantBlock
|| block instanceof TallGrassBlock;
}
/** determine if the given block should use the biome's foliage color */
private boolean leavesInstance()
{
return block instanceof LeavesBlock
|| block == Blocks.VINE
|| block == Blocks.SUGAR_CANE;
}
/** determine if the given block should use the biome's foliage color */
private boolean waterIstance()
{
return block == Blocks.WATER;
}
//--------------//
//Colors getters//
//--------------//
public boolean hasColor()
{
return isColored;
}
public int getColor()
{
return color;
}
//------------//
//Tint getters//
//------------//
public boolean hasTint()
{
return toTint;
}
public boolean hasGrassTint()
{
return grassTint;
}
public boolean hasFolliageTint()
{
return foliageTint;
}
public boolean hasWaterTint()
{
return waterTint;
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BlockColorWrapper))
return false;
BlockColorWrapper that = (BlockColorWrapper) o;
return Objects.equals(block, that.block);
}
@Override public int hashCode()
{
return Objects.hash(block);
}
}
@@ -0,0 +1,64 @@
package com.seibel.lod.wrappers.Block;
import com.seibel.lod.util.ColorUtil;
import net.minecraft.block.*;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.client.model.data.ModelDataMap;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//This class wraps the minecraft BlockPos.Mutable (and BlockPos) class
public class BlockPosWrapper
{
private final BlockPos.Mutable blockPos;
public BlockPosWrapper()
{
this.blockPos = new BlockPos.Mutable(0,0,0);
}
public void set(int x, int y, int z)
{
blockPos.set(x, y, z);
}
public int getX()
{
return blockPos.getX();
}
public int getY()
{
return blockPos.getY();
}
public int getZ()
{
return blockPos.getZ();
}
public BlockPos.Mutable getBlockPos()
{
return blockPos;
}
@Override public boolean equals(Object o)
{
return blockPos.equals(o);
}
@Override public int hashCode()
{
return Objects.hash(blockPos);
}
}
@@ -0,0 +1,150 @@
package com.seibel.lod.wrappers.Block;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.SixWayBlock;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//This class wraps the minecraft Block class
public class BlockShapeWrapper
{
//set of block which require tint
public static final ConcurrentMap<Block, BlockShapeWrapper> blockShapeWrapperMap = new ConcurrentHashMap<>();
public static BlockShapeWrapper WATER_SHAPE = new BlockShapeWrapper();
private final Block block;
private final boolean toAvoid;
private boolean nonFull;
private boolean noCollision;
/**Constructor only require for the block instance we are wrapping**/
public BlockShapeWrapper(Block block, ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper)
{
this.block = block;
this.nonFull = false;
this.noCollision = false;
this.toAvoid = ofBlockToAvoid();
setupShapes(chunkWrapper, blockPosWrapper);
//System.out.println(block + " non full " + nonFull + " no collision " + noCollision + " to avoid " + toAvoid);
}
private BlockShapeWrapper()
{
this.block = Blocks.WATER;
this.nonFull = false;
this.noCollision = false;
this.toAvoid = false;
}
/**
* this return a wrapper of the block in input
* @param block Block object to wrap
*/
static public BlockShapeWrapper getBlockShapeWrapper(Block block, ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper)
{
//first we check if the block has already been wrapped
if (blockShapeWrapperMap.containsKey(block) && blockShapeWrapperMap.get(block) != null)
return blockShapeWrapperMap.get(block);
//if it hasn't been created yet, we create it and save it in the map
BlockShapeWrapper blockWrapper = new BlockShapeWrapper(block, chunkWrapper, blockPosWrapper);
blockShapeWrapperMap.put(block, blockWrapper);
//we return the newly created wrapper
return blockWrapper;
}
private void setupShapes(ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper)
{
IBlockReader chunk = chunkWrapper.getChunk();
BlockPos blockPos = blockPosWrapper.getBlockPos();
boolean noCollisionSetted = false;
boolean nonFullSetted = false;
if (!block.defaultBlockState().getFluidState().isEmpty() || block instanceof SixWayBlock)
{
noCollisionSetted = true;
nonFullSetted = true;
noCollision = false;
nonFull = false;
}
if (!nonFullSetted)
{
VoxelShape voxelShape = block.defaultBlockState().getShape(chunk, blockPos);
if (!voxelShape.isEmpty())
{
AxisAlignedBB bbox = voxelShape.bounds();
double xWidth = (bbox.maxX - bbox.minX);
double yWidth = (bbox.maxY - bbox.minY);
double zWidth = (bbox.maxZ - bbox.minZ);
nonFull = xWidth < 1 && zWidth < 1 && yWidth < 1;
}
else
{
nonFull = false;
}
}
if (!noCollisionSetted)
{
VoxelShape collisionShape = block.defaultBlockState().getCollisionShape(chunk, blockPos);
noCollision = collisionShape.isEmpty();
}
}
public boolean ofBlockToAvoid()
{
return block.equals(Blocks.AIR)
|| block.equals(Blocks.CAVE_AIR)
|| block.equals(Blocks.BARRIER)
|| block.equals(Blocks.VOID_AIR);
}
//-----------------//
//Avoidance getters//
//-----------------//
public boolean isNonFull()
{
return nonFull;
}
public boolean hasNoCollision()
{
return noCollision;
}
public boolean isToAvoid()
{
return toAvoid;
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BlockShapeWrapper))
return false;
BlockShapeWrapper that = (BlockShapeWrapper) o;
return Objects.equals(block, that.block);
}
@Override public int hashCode()
{
return Objects.hash(block);
}
}
@@ -0,0 +1,7 @@
package com.seibel.lod.wrappers.Chunk;
//This class will contain all methods usefull to generate the fake ChunkWrapper
public class ChunkGenerator
{
}
@@ -0,0 +1,64 @@
package com.seibel.lod.wrappers.Chunk;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import java.util.Objects;
//This class wraps the minecraft ChunkPos class
public class ChunkPosWrapper
{
private final ChunkPos chunkPos;
public ChunkPosWrapper(ChunkPos chunkPos)
{
this.chunkPos = chunkPos;
}
public int getX()
{
return chunkPos.x;
}
public int getZ()
{
return chunkPos.z;
}
public int getMinBlockX()
{
return chunkPos.getMinBlockX();
}
public int getMinBlockZ()
{
return chunkPos.getMinBlockZ();
}
public int getRegionX()
{
return chunkPos.getRegionX();
}
public int getRegionZ()
{
return chunkPos.getRegionZ();
}
public ChunkPos getChunkPos()
{
return chunkPos;
}
@Override public boolean equals(Object o)
{
return chunkPos.equals(o);
}
@Override public int hashCode()
{
return Objects.hash(chunkPos);
}
}
@@ -0,0 +1,85 @@
package com.seibel.lod.wrappers.Chunk;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockColorWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Block.BlockShapeWrapper;
import com.seibel.lod.wrappers.World.BiomeWrapper;
import net.minecraft.block.BlockState;
import net.minecraft.block.ILiquidContainer;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.world.chunk.IChunk;
public class ChunkWrapper
{
private final IChunk chunk;
private final ChunkPosWrapper chunkPos;
public int getHeight(){
return chunk.getMaxBuildHeight();
}
public boolean isPositionInWater(BlockPosWrapper blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos.getBlockPos());
//This type of block is always in water
//This type of block could be in water
return ((blockState.getBlock() instanceof ILiquidContainer) && !(blockState.getBlock() instanceof IWaterLoggable))
|| (blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get());
}
public int getHeightMapValue(int xRel, int zRel){
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
public BiomeWrapper getBiome(int xRel, int yAbs, int zRel)
{
return BiomeWrapper.getBiomeWrapper(chunk.getBiomes().getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2));
}
public BlockColorWrapper getBlockColorWrapper(BlockPosWrapper blockPos)
{
return BlockColorWrapper.getBlockColorWrapper(chunk.getBlockState(blockPos.getBlockPos()),blockPos);
}
public BlockShapeWrapper getBlockShapeWrapper(BlockPosWrapper blockPos)
{
return BlockShapeWrapper.getBlockShapeWrapper(chunk.getBlockState(blockPos.getBlockPos()).getBlock(), this, blockPos);
}
public ChunkWrapper(IChunk chunk)
{
this.chunk = chunk;
this.chunkPos = new ChunkPosWrapper(chunk.getPos());
}
public IChunk getChunk(){
return chunk;
}
public ChunkPosWrapper getPos(){
return chunkPos;
}
public boolean isLightCorrect(){
return chunk.isLightCorrect();
}
public boolean
isWaterLogged(BlockPosWrapper blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos.getBlockPos());
//This type of block is always in water
//This type of block could be in water
return ((blockState.getBlock() instanceof ILiquidContainer) && !(blockState.getBlock() instanceof IWaterLoggable))
|| (blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get());
}
public int getEmittedBrightness(BlockPosWrapper blockPos)
{
return chunk.getLightEmission(blockPos.getBlockPos());
}
}
@@ -0,0 +1,19 @@
package com.seibel.lod.wrappers;
import net.minecraft.client.renderer.texture.NativeImage;
public class LightMapWrapper
{
static NativeImage lightMap = null;
public static void setLightMap(NativeImage newlightMap)
{
lightMap = newlightMap;
}
public static int getLightValue(int skyLight, int blockLight)
{
return lightMap.getPixelRGBA(skyLight, blockLight);
}
}
@@ -0,0 +1,289 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.wrappers;
import java.awt.Color;
import java.io.File;
import com.seibel.lod.ModInfo;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.World.WorldWrapper;
import net.minecraft.client.GameSettings;
import net.minecraft.client.MainWindow;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.network.play.ClientPlayNetHandler;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.crash.CrashReport;
import net.minecraft.entity.Entity;
import net.minecraft.profiler.IProfiler;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.world.DimensionType;
import net.minecraft.world.server.ServerWorld;
/**
* A singleton that wraps the Minecraft class
* to allow for easier movement between Minecraft versions.
*
* @author James Seibel
* @version 9-16-2021
*/
public class MinecraftWrapper
{
public static final MinecraftWrapper INSTANCE = new MinecraftWrapper();
private final Minecraft mc = Minecraft.getInstance();
/**
* The lightmap for the current:
* Time, dimension, brightness setting, etc.
*/
private NativeImage lightMap = null;
private MinecraftWrapper()
{
}
//================//
// helper methods //
//================//
/**
* This should be called at the beginning of every frame to
* clear any Minecraft data that becomes out of date after a frame. <br> <br>
* <p>
* LightMaps and other time sensitive objects fall in this category. <br> <br>
* <p>
* This doesn't affect OpenGL objects in any way.
*/
public void clearFrameObjectCache()
{
lightMap = null;
}
//=================//
// method wrappers //
//=================//
public float getShading(Direction direction)
{
return mc.level.getShade(Direction.UP, true);
}
public boolean hasSinglePlayerServer()
{
return mc.hasSingleplayerServer();
}
public DimensionType getCurrentDimension()
{
return mc.player.level.dimensionType();
}
public String getCurrentDimensionId()
{
return LodUtil.getDimensionIDFromWorld(mc.level);
}
/**
* This texture changes every frame
*/
public NativeImage getCurrentLightMap()
{
// get the current lightMap if the cache is empty
if (lightMap == null)
{
LightTexture tex = mc.gameRenderer.lightTexture();
lightMap = tex.lightPixels;
}
return lightMap;
}
/**
* Returns the color int at the given pixel coordinates
* from the current lightmap.
* @param u x location in texture space
* @param v z location in texture space
*/
public int getColorIntFromLightMap(int u, int v)
{
if (lightMap == null)
{
// make sure the lightMap is up-to-date
getCurrentLightMap();
}
return lightMap.getPixelRGBA(u, v);
}
/**
* Returns the Color at the given pixel coordinates
* from the current lightmap.
* @param u x location in texture space
* @param v z location in texture space
*/
public Color getColorFromLightMap(int u, int v)
{
return LodUtil.intToColor(lightMap.getPixelRGBA(u, v));
}
//=============//
// Simple gets //
//=============//
public ClientPlayerEntity getPlayer()
{
return mc.player;
}
public GameSettings getOptions()
{
return mc.options;
}
public ModelManager getModelManager()
{
return mc.getModelManager();
}
public ClientWorld getClientWorld()
{
return mc.level;
}
public WorldWrapper getWrappedClientWorld()
{
return WorldWrapper.getWorldWrapper(mc.level);
}
public WorldWrapper getWrappedServerWorld()
{
if (mc.level == null)
return null;
DimensionType dimension = mc.level.dimensionType();
IntegratedServer server = mc.getSingleplayerServer();
if (server == null)
return null;
Iterable<ServerWorld> worlds = server.getAllLevels();
ServerWorld returnWorld = null;
for (ServerWorld world : worlds)
{
if (world.dimensionType() == dimension)
{
returnWorld = world;
break;
}
}
return WorldWrapper.getWorldWrapper(returnWorld);
}
/** Measured in chunks */
public int getRenderDistance()
{
return mc.options.renderDistance;
}
public File getGameDirectory()
{
return mc.gameDirectory;
}
public IProfiler getProfiler()
{
return mc.getProfiler();
}
public ClientPlayNetHandler getConnection()
{
return mc.getConnection();
}
public GameRenderer getGameRenderer()
{
return mc.gameRenderer;
}
public Entity getCameraEntity()
{
return mc.cameraEntity;
}
public MainWindow getWindow()
{
return mc.getWindow();
}
public float getSkyDarken(float partialTicks)
{
return mc.level.getSkyDarken(partialTicks);
}
public IntegratedServer getSinglePlayerServer()
{
return mc.getSingleplayerServer();
}
public ServerData getCurrentServer()
{
return mc.getCurrentServer();
}
public WorldRenderer getLevelRenderer()
{
return mc.levelRenderer;
}
/**
* Crashes Minecraft, displaying the given errorMessage <br> <br>
* In the following format: <br>
*
* The game crashed whilst <strong>errorMessage</strong> <br>
* Error: <strong>java.lang.ExceptionClass: exceptionErrorMessage</strong> <br>
* Exit Code: -1 <br>
*/
public void crashMinecraft(String errorMessage, Throwable exception)
{
ClientProxy.LOGGER.error(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft...");
CrashReport report = new CrashReport(errorMessage, exception);
Minecraft.crash(report);
}
}
@@ -0,0 +1,5 @@
package com.seibel.lod.wrappers.Vertex;
public class BufferBuilderWrapper
{
}
@@ -0,0 +1,5 @@
package com.seibel.lod.wrappers.Vertex;
public class VertexBufferWrapper
{
}
@@ -0,0 +1,33 @@
package com.seibel.lod.wrappers.World;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.block.Blocks;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeColors;
import java.awt.*;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class BiomeColorWrapper
{
public static int getGrassColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageGrassColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
public static int getWaterColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageWaterColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
public static int getFoliageColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageFoliageColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
}
@@ -0,0 +1,138 @@
package com.seibel.lod.wrappers.World;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockColorWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.block.Blocks;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeColors;
import java.awt.*;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
//This class wraps the minecraft BlockPos.Mutable (and BlockPos) class
public class BiomeWrapper
{
public static final ConcurrentMap<Biome, BiomeWrapper> biomeWrapperMap = new ConcurrentHashMap<>();
private final Biome biome;
public BiomeWrapper(Biome biome)
{
this.biome = biome;
}
static public BiomeWrapper getBiomeWrapper(Biome biome)
{
//first we check if the biome has already been wrapped
if(biomeWrapperMap.containsKey(biome) && biomeWrapperMap.get(biome) != null)
return biomeWrapperMap.get(biome);
//if it hasn't been created yet, we create it and save it in the map
BiomeWrapper biomeWrapper = new BiomeWrapper(biome);
biomeWrapperMap.put(biome, biomeWrapper);
//we return the newly created wrapper
return biomeWrapper;
}
/** Returns a color int for the given biome. */
public int getColorForBiome(int x, int z)
{
int colorInt;
switch (biome.getBiomeCategory())
{
case NETHER:
colorInt = Blocks.NETHERRACK.defaultBlockState().materialColor.col;
break;
case THEEND:
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
break;
case BEACH:
case DESERT:
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
break;
case EXTREME_HILLS:
colorInt = Blocks.STONE.defaultMaterialColor().col;
break;
case MUSHROOM:
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
break;
case ICY:
colorInt = Blocks.SNOW.defaultMaterialColor().col;
break;
case MESA:
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
break;
case OCEAN:
case RIVER:
colorInt = biome.getWaterColor();
break;
case NONE:
case FOREST:
case TAIGA:
case JUNGLE:
case PLAINS:
case SAVANNA:
case SWAMP:
default:
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
break;
}
return colorInt;
}
public int getGrassTint(int x, int z)
{
return biome.getGrassColor(x, z);
}
public int getFolliageTint()
{
return biome.getFoliageColor();
}
public int getWaterTint()
{
return biome.getWaterColor();
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BiomeWrapper))
return false;
BiomeWrapper that = (BiomeWrapper) o;
return Objects.equals(biome, that.biome);
}
@Override public int hashCode()
{
return Objects.hash(biome);
}
}
@@ -0,0 +1,37 @@
package com.seibel.lod.wrappers.World;
import net.minecraft.world.DimensionType;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class DimensionTypeWrapper
{
private static final ConcurrentMap<DimensionType, DimensionTypeWrapper> dimensionTypeWrapperMap = new ConcurrentHashMap<>();
private final DimensionType dimensionType;
public DimensionTypeWrapper(DimensionType dimensionType)
{
this.dimensionType = dimensionType;
}
public static DimensionTypeWrapper getDimensionTypeWrapper(DimensionType dimensionType)
{
//first we check if the biome has already been wrapped
if(dimensionTypeWrapperMap.containsKey(dimensionType) && dimensionTypeWrapperMap.get(dimensionType) != null)
return dimensionTypeWrapperMap.get(dimensionType);
//if it hasn't been created yet, we create it and save it in the map
DimensionTypeWrapper dimensionTypeWrapper = new DimensionTypeWrapper(dimensionType);
dimensionTypeWrapperMap.put(dimensionType, dimensionTypeWrapper);
//we return the newly created wrapper
return dimensionTypeWrapper;
}
public static void clearMap()
{
dimensionTypeWrapperMap.clear();
}
}
@@ -0,0 +1,7 @@
package com.seibel.lod.wrappers.World;
//We will use this class to get all the light information from the game like skylight, blocklight and light emission;
public class WorldLightWrapper
{
}
@@ -0,0 +1,79 @@
package com.seibel.lod.wrappers.World;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.world.IWorld;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class WorldWrapper
{
private static final ConcurrentMap<IWorld, WorldWrapper> worldWrapperMap = new ConcurrentHashMap<>();
private final IWorld world;
public WorldWrapper(IWorld world)
{
this.world = world;
}
public static WorldWrapper getWorldWrapper(IWorld world)
{
//first we check if the biome has already been wrapped
if(worldWrapperMap.containsKey(world) && worldWrapperMap.get(world) != null)
return worldWrapperMap.get(world);
//if it hasn't been created yet, we create it and save it in the map
WorldWrapper worldWrapper = new WorldWrapper(world);
worldWrapperMap.put(world, worldWrapper);
//we return the newly created wrapper
return worldWrapper;
}
public static void clearMap()
{
worldWrapperMap.clear();
}
public DimensionTypeWrapper getDimensionType()
{
return DimensionTypeWrapper.getDimensionTypeWrapper(world.dimensionType());
}
public int getBlockLight(BlockPosWrapper blockPos)
{
return world.getLightEngine().blockEngine.getLightValue(blockPos.getBlockPos());
}
public int getSkyLight(BlockPosWrapper blockPos)
{
return world.getLightEngine().skyEngine.getLightValue(blockPos.getBlockPos());
}
public BiomeWrapper getBiome(BlockPosWrapper blockPos)
{
return BiomeWrapper.getBiomeWrapper(world.getBiome(blockPos.getBlockPos()));
}
public IWorld getWorld()
{
return world;
}
public boolean hasCeiling()
{
return world.dimensionType().hasCeiling();
}
public boolean hasSkyLight()
{
return world.dimensionType().hasSkyLight();
}
public boolean isEmpty()
{
return world == null;
}
}
@@ -0,0 +1,72 @@
# Note: to update code in eclipse run the "eclipse" command in graldew
# used when creating the projection matrix
public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D # getFOVModifier
public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
public net.minecraft.client.renderer.GameRenderer func_228380_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # hurtCameraEffect
public net.minecraft.client.renderer.GameRenderer func_228383_b_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # applyBobbing
# used when accessing built byteBuffers
public net.minecraft.client.renderer.BufferBuilder field_179001_a # byteBuffer
# used when determining where to save files too
public net.minecraft.world.storage.DimensionSavedDataManager field_215759_d # folder
# used when generating LodChunks
public net.minecraft.block.AbstractBlock$AbstractBlockState field_235704_h_ # materialColor
# used when determining which chunks Vanilla Minecraft is going to render
public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer
public net.minecraft.client.renderer.WorldRenderer field_72755_R # renderInfos
public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer field_178036_a # renderChunk
# used in world generation
public net.minecraft.world.server.ServerWorld field_241106_P_ # structuremanager
public net.minecraft.world.gen.Heightmap func_202267_b(II)I # getDataArrayIndex
public net.minecraft.world.gen.Heightmap func_202272_a(III)V # set
public net.minecraft.world.chunk.Chunk field_76634_f # heightMap
public net.minecraft.world.chunk.Chunk field_76652_q # sections
public net.minecraft.world.chunk.ChunkPrimer field_201661_i # sections
public net.minecraft.world.server.ChunkManager field_219269_w # templateManager
public net.minecraft.world.server.ChunkManager field_219256_j # lightManager
public net.minecraft.world.gen.feature.template.TemplateManager field_186240_a # templates
public net.minecraft.world.biome.Biome field_242424_k # biomeGenerationSettings
public net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider field_227406_b_ # weightedStates
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215096_a # decorator
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215097_b # config
public net.minecraft.util.WeightedList field_220658_a # weightedEntries
public net.minecraft.world.gen.feature.FeatureSpread field_242250_b # base
public net.minecraft.world.gen.feature.FeatureSpread field_242251_c # spread
public net.minecraft.world.gen.feature.ConfiguredFeature func_242765_a(Lnet/minecraft/world/ISeedReader;Lnet/minecraft/world/gen/ChunkGenerator;Ljava/util/Random;Lnet/minecraft/util/math/BlockPos;)Z # place
public net.minecraft.world.server.ServerChunkProvider field_217244_j # dataStorage
public net.minecraft.world.lighting.WorldLightManager field_215576_a # blockEngine
public net.minecraft.world.lighting.WorldLightManager field_215577_b # skyEngine
public net.minecraft.world.gen.feature.Feature field_236290_a_ # configuredCodec
# used for uploading vertex buffers off the render thread
public net.minecraft.client.renderer.vertex.VertexBuffer field_177365_a # id
public net.minecraft.client.renderer.vertex.VertexBuffer field_177363_b # format
public net.minecraft.client.renderer.vertex.VertexBuffer field_177364_c # vertexCount
# used for accessing the lightmap
public net.minecraft.client.renderer.LightTexture field_205111_b # lightPixels
#=====================#
# Examples from Forge #
#=====================#
# Makes public the IScreenFactory class in ScreenManager
public net.minecraft.client.gui.ScreenManager$IScreenFactory
# Makes protected and removes the final modifier from 'random' in MinecraftServer
protected-f net.minecraft.server.MinecraftServer field_147146_q #random
# Makes public the 'createNamedService' method in Util,
# accepting a String and returns an ExecutorService
public net.minecraft.util.Util func_240979_a_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #createNamedService
# Makes public the 'func_239776_a_' method in UUIDCodec,
# accepting two longs and returning an int[]
public net.minecraft.util.UUIDCodec func_239776_a_(JJ)[I #func_239776_a_
+48
View File
@@ -0,0 +1,48 @@
#// This is an example mods.toml file. It contains the data relating to the loading mods.
#// There are several mandatory fields (#mandatory), and many more that are optional (#optional).
#// The overall format is standard TOML format, v0.5.0.
#// Note that there are a couple of TOML lists in this file.
#// Find more information on toml format here: https://github.com/toml-lang/toml
#// The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
#// A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[36,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
#// The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
#// Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="GNU GPLv3"
#// A URL to refer people to when problems occur with this mod
issueTrackerURL="https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues" #optional
#// A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
#// The modid of the mod
modId="lod" #mandatory
#// The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
#//${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
#// see the associated build.gradle script for how to populate this completely automatically during a build
version="a1.5.2" #mandatory
#// A display name for the mod
displayName="Distant Horizons" #mandatory
#// A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
#//updateJSONURL="https://change.me.example.invalid/updates.json" #optional
#// A URL for the "homepage" for this mod, displayed in the mod UI
displayURL="https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail" #optional
#// A file name (in the root of the mod JAR) containing a logo for display
logoFile="logo.png" #optional
#// A text field displayed in the mod UI
credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
#// A text field displayed in the mod UI
authors="James Seibel, Leonardo Amato, and Cola" #optional
#// The description text for the mod (multi line!) (#mandatory)
description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.'''
+10
View File
@@ -0,0 +1,10 @@
{
"required": true,
"package": "com.seibel.lod.mixin",
"compatibilityLevel": "JAVA_8",
"refmap": "lod.refmap.json",
"mixins": [
"MixinWorldRenderer"
],
"minVersion": "0.8"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+7
View File
@@ -0,0 +1,7 @@
{
"pack": {
"description": "",
"pack_format": 6,
"_comment": "A pack_format of 6 requires json lang files and some texture changes from 1.16.2. Note: we require v6 pack meta for all mods."
}
}