mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 17:55:08 +00:00
Compare commits
377
Commits
1.0.0-alpha52
...
2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb5e2ed7d4 | ||
|
|
e914ef7563 | ||
|
|
748dbd55a1 | ||
|
|
140c0c2285 | ||
|
|
af26442b98 | ||
|
|
ef07ee9f77 | ||
|
|
1b8087e8a3 | ||
|
|
9107ea9ae5 | ||
|
|
938a6bda90 | ||
|
|
e9f6f72741 | ||
|
|
379330f997 | ||
|
|
2fecb3a05b | ||
|
|
c085af35bd | ||
|
|
a36aec36a2 | ||
|
|
bdd6259c3f | ||
|
|
d7d2228a6c | ||
|
|
cf9993fce4 | ||
|
|
3ae57ca594 | ||
|
|
9816cb2148 | ||
|
|
b1e496f56d | ||
|
|
c8e50227fb | ||
|
|
066381a7c0 | ||
|
|
9a0ea1ad65 | ||
|
|
8e26a0179b | ||
|
|
1d96af8e3a | ||
|
|
f7eb86cdb6 | ||
|
|
b38fafa831 | ||
|
|
7dc2625f8c | ||
|
|
3d97977050 | ||
|
|
5f41c60bc2 | ||
|
|
0325a96bc2 | ||
|
|
ad123d0bd5 | ||
|
|
4b89997985 | ||
|
|
f5c9fa3301 | ||
|
|
d6a6d3b525 | ||
|
|
f079bed977 | ||
|
|
c9ec40e94b | ||
|
|
8380f3853d | ||
|
|
534ae6b05c | ||
|
|
68d523d3df | ||
|
|
2f22a7b3ad | ||
|
|
7b79b90dd6 | ||
|
|
417f23f826 | ||
|
|
ca3c46c873 | ||
|
|
82fa9ade2b | ||
|
|
aaab8fd0bc | ||
|
|
bd56ccc4b8 | ||
|
|
e17f60308d | ||
|
|
1dd98978ad | ||
|
|
dae986eb0b | ||
|
|
5fdf84edd0 | ||
|
|
b19d023c11 | ||
|
|
41eafbd151 | ||
|
|
dd970b4c6a | ||
|
|
f79ad7f22a | ||
|
|
6947ffa3e2 | ||
|
|
969d2c3710 | ||
|
|
c5988d29c3 | ||
|
|
dc68c2c265 | ||
|
|
51533a81ec | ||
|
|
90eabb5d40 | ||
|
|
74843cb1f9 | ||
|
|
5e0344a529 | ||
|
|
3cdd934987 | ||
|
|
9d90ff137c | ||
|
|
bee9541797 | ||
|
|
b75a5d8f7d | ||
|
|
8d3ab41c53 | ||
|
|
48d032e654 | ||
|
|
1968914582 | ||
|
|
a70bd76ff3 | ||
|
|
db919b5f37 | ||
|
|
cde47ea423 | ||
|
|
5ef6cb11c0 | ||
|
|
b7015bf10e | ||
|
|
46602ab921 | ||
|
|
63e9fa0df1 | ||
|
|
dcea970a77 | ||
|
|
b29240d6da | ||
|
|
9d5a02ed21 | ||
|
|
b454483aae | ||
|
|
72b13543c6 | ||
|
|
36d98fcb22 | ||
|
|
44c5b9617a | ||
|
|
f3f1677db8 | ||
|
|
d396b611ad | ||
|
|
08f4c0f9db | ||
|
|
50b0d22dc2 | ||
|
|
7fed54264e | ||
|
|
be87401be6 | ||
|
|
94254006df | ||
|
|
f79774df65 | ||
|
|
2f2fc22df3 | ||
|
|
26300bbb1b | ||
|
|
88e4d2fd6c | ||
|
|
bb52476d27 | ||
|
|
6d60477b93 | ||
|
|
362724610c | ||
|
|
ea4a3cdf0b | ||
|
|
65959268d0 | ||
|
|
58d820cd7e | ||
|
|
685d68bd9c | ||
|
|
ad63d2d62c | ||
|
|
d2730ccca8 | ||
|
|
e9257b8739 | ||
|
|
3984225faa | ||
|
|
52db6b56d6 | ||
|
|
34bfe513f1 | ||
|
|
42d7ed58aa | ||
|
|
9a4f0f9b99 | ||
|
|
be90a01725 | ||
|
|
e5e8250d44 | ||
|
|
062a03ffc2 | ||
|
|
9f5afaaec3 | ||
|
|
b05f17a477 | ||
|
|
57caf08212 | ||
|
|
085ec79c21 | ||
|
|
38641268ae | ||
|
|
fe8e723638 | ||
|
|
16debfd518 | ||
|
|
8cfc3e1f4a | ||
|
|
2c808fae43 | ||
|
|
1649c3c0c6 | ||
|
|
b84286ba3b | ||
|
|
ee6c79802a | ||
|
|
d3366ad31a | ||
|
|
92b6dcc010 | ||
|
|
89132f3ef9 | ||
|
|
f0d45b0d62 | ||
|
|
173d986a5b | ||
|
|
473a690054 | ||
|
|
2515b3520f | ||
|
|
a32f9ca244 | ||
|
|
941fc88484 | ||
|
|
4106dd3865 | ||
|
|
61f6aa19a8 | ||
|
|
77875f7f91 | ||
|
|
241c32def1 | ||
|
|
a8107f97e8 | ||
|
|
8bec433699 | ||
|
|
2d39fb2397 | ||
|
|
cfd8eb1411 | ||
|
|
59c270a5b9 | ||
|
|
87ac03281c | ||
|
|
36b82fa2ce | ||
|
|
2fc2e2e38d | ||
|
|
33de1de9c4 | ||
|
|
55811b9d7b | ||
|
|
a10d92f52e | ||
|
|
7d2fe12ce9 | ||
|
|
006ded801e | ||
|
|
03afd6939e | ||
|
|
adc5421e00 | ||
|
|
dfe60e4b3e | ||
|
|
72c6cdfa60 | ||
|
|
c5daea86f8 | ||
|
|
f8958ee990 | ||
|
|
073260060b | ||
|
|
771fee124d | ||
|
|
680285bf54 | ||
|
|
df8784536e | ||
|
|
c8991d810c | ||
|
|
e3b02642ac | ||
|
|
2759dd6689 | ||
|
|
996dbaef2c | ||
|
|
72ca675c93 | ||
|
|
e6e230e270 | ||
|
|
43d7282ba5 | ||
|
|
4b2d57a32d | ||
|
|
06bf71126f | ||
|
|
4c0f2f2685 | ||
|
|
78334717e7 | ||
|
|
a77765a2c9 | ||
|
|
1a9fe45ee9 | ||
|
|
21ef5905c8 | ||
|
|
5b67eaa474 | ||
|
|
c9d59c3b90 | ||
|
|
fa7ff74fec | ||
|
|
a34a3ebd39 | ||
|
|
2e7476d287 | ||
|
|
3244071012 | ||
|
|
7035dbde02 | ||
|
|
97ae04c2fa | ||
|
|
e7fa7f62d0 | ||
|
|
ea39712749 | ||
|
|
66dd3eaa94 | ||
|
|
270697cd10 | ||
|
|
2b53a851ef | ||
|
|
34b6efde7b | ||
|
|
86edb721da | ||
|
|
39c5f4bf7b | ||
|
|
5dc54bb1da | ||
|
|
7924769ab1 | ||
|
|
8afe2e2ccf | ||
|
|
e8dd60e088 | ||
|
|
9a621405ba | ||
|
|
45a677030d | ||
|
|
4d531022d2 | ||
|
|
48ba2160ad | ||
|
|
334ec81f9b | ||
|
|
86c726e50b | ||
|
|
e6d778dad3 | ||
|
|
dce5013ffb | ||
|
|
6eef63619c | ||
|
|
a8153fa0f7 | ||
|
|
49c04cf776 | ||
|
|
85b0277a66 | ||
|
|
0525b31262 | ||
|
|
fdb2053c50 | ||
|
|
31abbcce32 | ||
|
|
063b2242e8 | ||
|
|
577f905dc5 | ||
|
|
071d2d4a39 | ||
|
|
891715b113 | ||
|
|
30a35ef25e | ||
|
|
33453b3b8c | ||
|
|
62e699f6d0 | ||
|
|
f107d173b7 | ||
|
|
d0948b36f3 | ||
|
|
ae1211f280 | ||
|
|
3d76cb3073 | ||
|
|
45fb120223 | ||
|
|
d7a196eb5c | ||
|
|
4d53e2af9b | ||
|
|
cb6336a365 | ||
|
|
5095021e0e | ||
|
|
f88ecbf68d | ||
|
|
7da9b9eb1c | ||
|
|
02c394108f | ||
|
|
720aaca1b7 | ||
|
|
1e71b1c4d8 | ||
|
|
df4c423e93 | ||
|
|
78e0074e5a | ||
|
|
0350ea2cb6 | ||
|
|
bed724ba59 | ||
|
|
6fa973dbc8 | ||
|
|
c4ad460f34 | ||
|
|
2a8404c6c2 | ||
|
|
108db9031b | ||
|
|
d6d8a5dad6 | ||
|
|
ad2b73edf9 | ||
|
|
bb5526598e | ||
|
|
611bbf02c8 | ||
|
|
ca16036604 | ||
|
|
f118211257 | ||
|
|
81d03ea552 | ||
|
|
67c8628aeb | ||
|
|
f9b9873637 | ||
|
|
c66bf8658e | ||
|
|
052824017d | ||
|
|
6a5d914a43 | ||
|
|
208373deaa | ||
|
|
f22a3f612b | ||
|
|
9ff1015d19 | ||
|
|
0023fe3da2 | ||
|
|
7e2399714c | ||
|
|
9029182353 | ||
|
|
e6039691b5 | ||
|
|
5db1edba33 | ||
|
|
d450461726 | ||
|
|
95f8afc0f3 | ||
|
|
19635e20ec | ||
|
|
ead4e11376 | ||
|
|
b0ceb5306a | ||
|
|
99a81369f0 | ||
|
|
e2ee822948 | ||
|
|
9fe7e800f6 | ||
|
|
debfcdfda0 | ||
|
|
df662188f4 | ||
|
|
842916405f | ||
|
|
09afe81290 | ||
|
|
292809d8e8 | ||
|
|
47503ce254 | ||
|
|
f738adbdef | ||
|
|
70d15f2383 | ||
|
|
d66b5577f4 | ||
|
|
e293f12fb8 | ||
|
|
b69eca6749 | ||
|
|
03342f08c6 | ||
|
|
b85614f983 | ||
|
|
5afb5d8f5a | ||
|
|
c353c83cf3 | ||
|
|
78072236d2 | ||
|
|
350fdcd7e4 | ||
|
|
3d98e57178 | ||
|
|
55c1786811 | ||
|
|
356d826387 | ||
|
|
c34d8548a5 | ||
|
|
81a000af75 | ||
|
|
711c23c722 | ||
|
|
5aa1d504b0 | ||
|
|
60ab3b7900 | ||
|
|
912e0e83b8 | ||
|
|
104eda0b51 | ||
|
|
7fb8cde66d | ||
|
|
0e4421f2ce | ||
|
|
3cf3d096fd | ||
|
|
ffa1193dd2 | ||
|
|
d180023469 | ||
|
|
762275a6da | ||
|
|
0e66bcf42f | ||
|
|
979ad344a1 | ||
|
|
5f328e9b57 | ||
|
|
2dba62bf20 | ||
|
|
0fd4d457e2 | ||
|
|
b56e803622 | ||
|
|
3ab965ded7 | ||
|
|
538b38898c | ||
|
|
e43cd6f18e | ||
|
|
38285087c9 | ||
|
|
deb6c2b6e5 | ||
|
|
e11985a913 | ||
|
|
4bbf39eb4d | ||
|
|
8c90f79c17 | ||
|
|
e4e657846d | ||
|
|
2dace708b4 | ||
|
|
1df1ec3530 | ||
|
|
d96ac28043 | ||
|
|
72c13cff67 | ||
|
|
f233ba65e3 | ||
|
|
c0d636d4f7 | ||
|
|
bd5cd350c3 | ||
|
|
88936d3713 | ||
|
|
cf5bf2e2e7 | ||
|
|
cfd2388c85 | ||
|
|
fd78f63f12 | ||
|
|
6cf119dfee | ||
|
|
8c5f4d2543 | ||
|
|
9e1fa7b80f | ||
|
|
6c99872459 | ||
|
|
f0809410ee | ||
|
|
98299b69d2 | ||
|
|
fed0aa77c5 | ||
|
|
f7fb3186c8 | ||
|
|
ab243bb936 | ||
|
|
60cce7ea1d | ||
|
|
b837b0571d | ||
|
|
7abc6213f5 | ||
|
|
b46643ae8c | ||
|
|
e3d1fedd48 | ||
|
|
ebcee499eb | ||
|
|
ad26b1ad6f | ||
|
|
73338e3c3e | ||
|
|
2c89244fbe | ||
|
|
991f658819 | ||
|
|
1588c7000d | ||
|
|
260d57e308 | ||
|
|
3a1d1b03b3 | ||
|
|
66563f41c8 | ||
|
|
a52adb9190 | ||
|
|
1c6f9aa131 | ||
|
|
8c584fed05 | ||
|
|
558ea0fe12 | ||
|
|
1315d8971a | ||
|
|
e33f2ff7e3 | ||
|
|
29072ee3f8 | ||
|
|
ff8315270b | ||
|
|
54f276a460 | ||
|
|
feef745722 | ||
|
|
88cb3faf47 | ||
|
|
f477675ee9 | ||
|
|
4294059fbe | ||
|
|
e40c9f2441 | ||
|
|
52e886adcc | ||
|
|
f38fcd1d82 | ||
|
|
fbebbd4049 | ||
|
|
03a0406199 | ||
|
|
1ef691ecc8 | ||
|
|
b242a13fd6 | ||
|
|
2274bfabfd | ||
|
|
7e4292bdba | ||
|
|
f79e112700 | ||
|
|
ba46feb20b | ||
|
|
33804c67da | ||
|
|
3c9353e143 | ||
|
|
77eb3ad46a | ||
|
|
8a56ab8ca1 |
No files matched your search
@@ -18,6 +18,14 @@ body:
|
||||
placeholder: "Example: 1.0.0-alpha1"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: report
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log."
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: java
|
||||
attributes:
|
||||
@@ -31,6 +39,7 @@ body:
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Java 25
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
@@ -48,12 +57,25 @@ body:
|
||||
- Mesa/Software
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: report
|
||||
- type: dropdown
|
||||
id: shaders
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log."
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
label: Shaders
|
||||
description: Do you have a shaderpack enabled?
|
||||
options:
|
||||
- "No"
|
||||
- "Yes: Please put relevant information about shaderpack in the report(what shaderpack, any special settings of note, etc)"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: mcpf
|
||||
attributes:
|
||||
label: MCPF or other features disabled by default
|
||||
description: Do you have MCPF or any other features that are disabled by default enabled?
|
||||
options:
|
||||
- "No: Go ahead and create an issue"
|
||||
- "Yes: I will not create an issue"
|
||||
- "Yes: I don't read and shouldn't be creating an issue"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -76,6 +98,5 @@ body:
|
||||
required: true
|
||||
- label: "I am running an officially released version. (Or, if I've compiled it myself I plan to fix the issue)"
|
||||
required: true
|
||||
- label: "This issue is not related to a feature that is disabed by default - Shaders, MCPF, etc. [They'll be enabled when they're ready for testing]"
|
||||
required: true
|
||||
|
||||
|
||||
|
||||
@@ -29,5 +29,5 @@ body:
|
||||
required: true
|
||||
- label: "I have read the known incompatibilities and this is not related to one of those."
|
||||
required: true
|
||||
- label: "This issue is not related to a feature that is disabed by default - Shaders, MCPF, etc. [They'll be enabled when they're ready for testing]"
|
||||
required: true
|
||||
- label: "This issue is not related to a feature that is disabed by default - MCPF, etc. [They'll be enabled when they're ready for testing]"
|
||||
required: true
|
||||
@@ -1,6 +1,7 @@
|
||||
.gradle
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/run/
|
||||
/build/
|
||||
@@ -30,3 +31,9 @@ src/main/resources/mixins.*([!.]).json
|
||||
*.bat
|
||||
*.DS_Store
|
||||
!gradlew.bat
|
||||
layout.json
|
||||
addon.local.gradle
|
||||
addon.local.gradle.kts
|
||||
addon.late.local.gradle
|
||||
addon.late.local.gradle.kts
|
||||
local/
|
||||
@@ -5,34 +5,51 @@
|
||||
* The little Angle [sic] that supports shaders while saving your fps from a certain death -boubou
|
||||
* Is it obtuse, or accute? -Caedis
|
||||
* It's a cute angle -mitchej123
|
||||
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
|
||||
|
||||
**Alpha Quality - Here there be Dragons**
|
||||
## **Beta Quality - Here there be Dragons**
|
||||
* Shaders - Beta
|
||||
* Complementary + Euphoria Patches is the primary compatibility target
|
||||
* Sildurs, Seus, and any other shaders might work
|
||||
|
||||
## Core Profile
|
||||
Targets GL 3.3+ core profile
|
||||
* FFP emulation via generated GLSL shaders
|
||||
* Display list emulation
|
||||
* Software matrix stacks and state management through GLSM
|
||||
* GL_QUADS → GL_TRIANGLES conversion
|
||||
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
|
||||
|
||||
|
||||
# Required Dependencies
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.2.9
|
||||
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
|
||||
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
|
||||
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
|
||||
|
||||
# Known (temporary) Incompatibilities
|
||||
* Forge Relocation - Currently non Thread Safe; safe if not in use
|
||||
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
|
||||
|
||||
# Known - Incompatibilities
|
||||
* EndlessIDs - Not currently planned (Looking for volunteers)
|
||||
* FalseTweaks - Not currently planned (Looking for volunteers)
|
||||
* SmoothFont - Not currently planned (Looking for volunteers)
|
||||
# Potential Incompatibilities
|
||||
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
|
||||
|
||||
# Known Incompatibilities
|
||||
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
|
||||
* RPLE - Not currently planned (Looking for volunteers)
|
||||
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
|
||||
|
||||
# Permanent Incompatibilities
|
||||
* Optifine [Disabled, won't fix]
|
||||
* Fastcraft [Disable, won't fix]
|
||||
* BetterFPS [Won't fix]
|
||||
* LWJGL3ify 1.5.4 and below - Use the latest version available, 1.5.7+
|
||||
* ArchaicFix 0.6.2 and below - Use 0.7.0 or above
|
||||
* Hodgepodge 2.4.3 and below - [Use 2.4.4 or above](https://github.com/GTNewHorizons/Hodgepodge/releases)
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.2.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* OptiFine [Disabled, won't fix]
|
||||
* FastCraft [Disabled, won't fix]
|
||||
* BetterFPS [Disabled, won't fix]
|
||||
* OptimizationsAndTweaks [Disabled, won't fix]
|
||||
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
|
||||
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
|
||||
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
|
||||
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
|
||||
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
|
||||
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
|
||||
* SmoothFont - Equivalent functionality added
|
||||
|
||||
# Credits
|
||||
* ShadersMod
|
||||
@@ -47,20 +64,20 @@
|
||||
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
|
||||
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
|
||||
* Backported code under the LGPL
|
||||
* Sodium
|
||||
* Sodium / Celeritas
|
||||
* JellySquid & the CaffeineMC team, for making Sodium in the first place
|
||||
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
|
||||
* Embeddedt for Celeritas used under the LGPL 3.0
|
||||
* Reese's Sodium Options
|
||||
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
|
||||
* Neodymium
|
||||
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
|
||||
* MCPatcherForge
|
||||
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
|
||||
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
|
||||
* HUDCaching
|
||||
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
|
||||
* Angelica
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto for helping get Angelica up and running!
|
||||
* mitchej123, Omni, Makamys, Embeddedt, NanoLive, Caedis, Cardinalstar, Alexdoru, Eigenraven, mist475, Clepto, kurrycat for helping get Angelica up and running!
|
||||
* IMS, jss2a98aj, || dbp ||, roadhog360 for advice and moral support
|
||||
* Forge 1.12+
|
||||
* Some files have been taken from Minecraft Forge under the LGPL 2.1 license and are marked as such
|
||||
@@ -69,4 +86,15 @@
|
||||
license, see LICENSE in `com.gtnewhorizons.angelica.models`
|
||||
* Dynamic Lights
|
||||
* LambdAurora for [LambDynamicLights](https://github.com/LambdAurora/LambDynamicLights) under the MIT license, and the Forge port anthxnymc for [DynamicLightsReforged](https://github.com/anthxnymc/DynamicLightsReforged), also under the MIT license
|
||||
|
||||
* Embeddedt and Ferri_Arnus for [glsl-transformation-lib](https://github.com/TauMC/glsl-transformation-lib)
|
||||
* Also for work on [Monocle](https://github.com/ferriarnus/Monocle) which was used as a reference for implementing glsl-transformation-lib
|
||||
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
|
||||
* FalseTweaks
|
||||
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
|
||||
* Beddium
|
||||
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
|
||||
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
|
||||
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
+37
-2
@@ -1,9 +1,16 @@
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
|
||||
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
|
||||
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
@@ -11,17 +18,24 @@ tasks.test {
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
// On macOS ARM64, use an ARM64-compatible JVM for tests
|
||||
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") &&
|
||||
System.getProperty("os.arch") == "aarch64"
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (isMacOsArm64) {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
dependsOn(tasks.extractNatives2)
|
||||
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
inputs.property("version", project.version.toString())
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to project.version.toString())
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +45,24 @@ tasks.register<Copy>("copyDependencies") {
|
||||
from(configurations.default)
|
||||
into("testDependencies")
|
||||
}
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
// Downgrade embedOnly jars for the tests
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
dependsOn(downgradeEmbedOnlyForTest)
|
||||
classpath = classpath
|
||||
.minus(embedOnly)
|
||||
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
from(embedOnly.map(::zipTree))
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
}
|
||||
|
||||
+62
-29
@@ -46,66 +46,99 @@ configurations {
|
||||
// Can be enabled for testing if desired
|
||||
// runtimeClasspath.extendsFrom(transformedMod)
|
||||
// testRuntimeClasspath.extendsFrom(transformedMod)
|
||||
|
||||
// Embed in JAR without relocation (compile only, shadowed into JAR)
|
||||
embedOnly
|
||||
embedOnly.canBeConsumed = false
|
||||
embedOnly.canBeResolved = true
|
||||
compileClasspath.extendsFrom(embedOnly)
|
||||
testCompileClasspath.extendsFrom(embedOnly)
|
||||
testRuntimeClasspath.extendsFrom(embedOnly)
|
||||
}
|
||||
|
||||
|
||||
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
|
||||
configurations.all {
|
||||
exclude group: "com.github.GTNewHorizons", module: "Angelica"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.4.31")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
|
||||
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
|
||||
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.22")
|
||||
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.42")
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly('org.jetbrains:annotations:24.0.1')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.2.9:dev")
|
||||
compileOnly('org.jetbrains:annotations:26.0.2')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
|
||||
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
implementation("io.github.douira:glsl-transformer:1.0.0")
|
||||
shadowImplementation("io.github.douira:glsl-transformer:1.0.0") { // glsl-transformer Noncommercial License 1.0.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
|
||||
exclude module: "antlr4" // we only want to shadow the runtime module
|
||||
}
|
||||
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
|
||||
compileOnly "org.apache.ant:ant:1.8.2"
|
||||
|
||||
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.2-GTNH") { transitive = false }
|
||||
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.4.9-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.2.0:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
|
||||
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
|
||||
|
||||
// Notfine Deps
|
||||
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.5.16:dev") { transitive = false }
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
|
||||
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.0-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.6.1:dev")
|
||||
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.0:api") { transitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0002:dev") { transitive = false }
|
||||
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
|
||||
|
||||
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
|
||||
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
|
||||
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.0.3:dev") // Mixin Version
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
|
||||
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
|
||||
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
// HMMMMM
|
||||
compileOnly(rfg.deobf("curse.maven:journeymap-32274:2367915"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.4-GTNH:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
|
||||
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
// Better Crashes
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
|
||||
|
||||
afterEvaluate {
|
||||
dependencies {
|
||||
testImplementation(platform('org.junit:junit-bom:5.14.1'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
|
||||
// Tests
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
|
||||
testImplementation('org.junit.platform:junit-platform-engine')
|
||||
testImplementation('org.junit.platform:junit-platform-reporting')
|
||||
testImplementation('org.junit.platform:junit-platform-launcher')
|
||||
}
|
||||
}
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
}
|
||||
+55
-17
@@ -14,7 +14,7 @@ modId = angelica
|
||||
modGroup = com.gtnewhorizons.angelica
|
||||
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
|
||||
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
|
||||
developmentEnvironmentUserName = Developer
|
||||
|
||||
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
|
||||
# See https://github.com/bsideup/jabel for details on how this works.
|
||||
enableModernJavaSyntax = true
|
||||
# Enables modern Java syntax support. Valid values:
|
||||
# - false: No modern syntax, Java 8 only
|
||||
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
|
||||
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
|
||||
# - modern: Native modern Java bytecode, no downgrading
|
||||
enableModernJavaSyntax = jvmDowngrader
|
||||
|
||||
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
|
||||
# and should *not* be used in most situations. -1 disables this.
|
||||
# forceToolchainVersion = -1
|
||||
|
||||
# Target JVM version for JVM Downgrader bytecode downgrading.
|
||||
# Only used when enableModernJavaSyntax = jvmDowngrader
|
||||
# downgradeTargetVersion = 8
|
||||
|
||||
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
|
||||
# Classes will be available in META-INF/versions/N/ for each version N in this list.
|
||||
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
|
||||
jvmDowngraderMultiReleaseVersions = 17, 21, 25
|
||||
|
||||
# Specifies how JVM Downgrader API stubs are provided. Options:
|
||||
# - shade: Shade minimized stubs into the jar
|
||||
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
|
||||
# - external: Another dependency provides stubs (no constraint, no warning)
|
||||
# - (empty): Warning reminding you to configure stubs
|
||||
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
|
||||
jvmDowngraderStubsProvider = gtnhlib
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
@@ -55,22 +79,13 @@ generateGradleTokenClass = com.gtnewhorizons.angelica.Tags
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
|
||||
# [DEPRECATED] Mod ID replacement token.
|
||||
gradleTokenModId =
|
||||
|
||||
# [DEPRECATED] Mod name replacement token.
|
||||
gradleTokenModName =
|
||||
|
||||
# [DEPRECATED] Mod Group replacement token.
|
||||
gradleTokenGroupName =
|
||||
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
replaceGradleTokenInFile =
|
||||
# replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
@@ -85,13 +100,20 @@ accessTransformersFile = angelica_at.cfg notfine_at.cfg archaicfix_at.cfg mcpatc
|
||||
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
|
||||
usesMixins = true
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
# Mixin classes will have access to "main" classes, but not the other way around.
|
||||
separateMixinSourceSet = mixin
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
@@ -117,9 +139,15 @@ minimizeShadowedDependencies = true
|
||||
# If disabled, won't rename the shadowed classes.
|
||||
relocateShadowedDependencies = true
|
||||
|
||||
# Adds the GTNH maven, CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
includeWellKnownRepositories = true
|
||||
|
||||
# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated
|
||||
# list of strings, with the acceptable keys being(case does not matter):
|
||||
# cursemaven
|
||||
# modrinth
|
||||
excludeWellKnownRepositories =
|
||||
|
||||
# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven.
|
||||
# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables.
|
||||
# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle.
|
||||
@@ -140,7 +168,7 @@ modrinthProjectId = angelica
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations = required-project\:gtnhlib
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
@@ -161,6 +189,12 @@ curseForgeRelations = requiredDependency\:gtnhlib
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
@@ -190,3 +224,7 @@ disableSpotless = true
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
# Non-GTNH properties
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.jvmargs = -Xmx2g
|
||||
org.gradle.parallel = true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
|
||||
toolchainVersion=25
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -170,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+3
-2
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -68,11 +70,10 @@ goto fail
|
||||
: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 %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+24
-14
@@ -8,21 +8,31 @@ repositories {
|
||||
includeGroup 'com.falsepattern'
|
||||
}
|
||||
}
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
ivy {
|
||||
name = 'CoreTweaks releases'
|
||||
url = 'https://github.com/makamys/CoreTweaks/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = 'mega'
|
||||
url = 'https://mvn.falsepattern.com/gtmega_releases/'
|
||||
content {
|
||||
includeGroup 'mega'
|
||||
}
|
||||
filter {
|
||||
includeGroup('CoreTweaks')
|
||||
}
|
||||
maven {
|
||||
name = 'taumc'
|
||||
url = 'https://maven.taumc.org/releases'
|
||||
content {
|
||||
includeGroup 'org.taumc'
|
||||
}
|
||||
}
|
||||
ivy {
|
||||
name = 'modernsplash-github'
|
||||
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
|
||||
}
|
||||
content {
|
||||
includeModule 'com.github.MalTeeez', 'modernsplash'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
mavenLocal()
|
||||
|
||||
@@ -2,8 +2,8 @@ pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name "GTNH Maven"
|
||||
url "https://nexus.gtnewhorizons.com/repository/public/"
|
||||
name = "GTNH Maven"
|
||||
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
@@ -16,5 +16,5 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.21'
|
||||
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.23")
|
||||
}
|
||||
@@ -2,21 +2,26 @@ package com.gtnewhorizons.angelica;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
|
||||
import com.gtnewhorizon.gtnhmixins.LateMixin;
|
||||
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
|
||||
import com.gtnewhorizons.angelica.mixins.Mixins;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@LateMixin
|
||||
@SuppressWarnings("unused")
|
||||
public class AngelicaLateMixins implements ILateMixinLoader {
|
||||
|
||||
@Override
|
||||
public String getMixinConfig() {
|
||||
int v = Runtime.version().feature();
|
||||
if (v >= 21) return "mixins.angelica.late.j21.json";
|
||||
if (v >= 17) return "mixins.angelica.late.j17.json";
|
||||
return "mixins.angelica.late.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins(Set<String> loadedMods) {
|
||||
return Mixins.getLateMixins(loadedMods);
|
||||
return IMixins.getLateMixins(Mixins.class, loadedMods);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.gtnewhorizons.angelica;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.config.ConfigMigrator;
|
||||
import com.gtnewhorizons.angelica.proxy.CommonProxy;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
@@ -9,17 +11,39 @@ import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
|
||||
|
||||
@Mod(
|
||||
modid = "angelica",
|
||||
modid = MOD_ID,
|
||||
name = "Angelica",
|
||||
version = Tags.VERSION,
|
||||
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.4,);" + " after:CodeChickenCore@[1.2.0,);"
|
||||
+ " after:archaicfix@[0.7.0,);" + " required-after:gtnhlib@[0.2.9,);",
|
||||
dependencies = """
|
||||
required-after:gtnhlib@[0.8.21,);\
|
||||
before:lwjgl3ify@[1.5.3,);\
|
||||
after:hodgepodge@[2.4.4,);\
|
||||
after:CodeChickenCore@[1.2.0,);\
|
||||
after:archaicfix@[0.7.0,);\
|
||||
""",
|
||||
acceptedMinecraftVersions = "[1.7.10]",
|
||||
acceptableRemoteVersions = "*",
|
||||
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
|
||||
public class AngelicaMod {
|
||||
public static final String MOD_ID = "angelica";
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static SodiumGameOptions CONFIG;
|
||||
|
||||
public static SodiumGameOptions options() {
|
||||
if (CONFIG == null) {
|
||||
CONFIG = SodiumGameOptions.load(ConfigMigrator.handleConfigMigration("angelica-options.json"));
|
||||
}
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
|
||||
public static CommonProxy proxy;
|
||||
|
||||
@@ -27,10 +51,13 @@ public class AngelicaMod {
|
||||
|
||||
public static final ManagedEnum<AnimationMode> animationsMode = new ManagedEnum<>(AnimationMode.VISIBLE_ONLY);
|
||||
|
||||
public static BlockError blockError;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
ModStatus.preInit();
|
||||
proxy.preInit(event);
|
||||
blockError = new BlockError();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
- [x] Frustum (Seems related to Frustrum)
|
||||
- [X] NativeImage
|
||||
- [ ] PoseStack
|
||||
- [ ] RenderType
|
||||
- [ ] MultiBufferSource.BufferSource
|
||||
- [ ] VertexBuffer
|
||||
- [ ] VertexConsumer
|
||||
- [ ] VertextFormat
|
||||
- [ ] TextureAtlas (Seems related to TextureManager) - PBR related
|
||||
- [ ] GUI
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.mojang.BlockPosImpl;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import static com.google.common.math.IntMath.log2;
|
||||
|
||||
public interface BlockPos {
|
||||
|
||||
int SIZE_BITS_X = 1 + log2(MathHelper.roundUpToPowerOfTwo(30000000), RoundingMode.UNNECESSARY);
|
||||
int SIZE_BITS_Z = SIZE_BITS_X;
|
||||
int SIZE_BITS_Y = 64 - SIZE_BITS_X - SIZE_BITS_Z;
|
||||
long BITS_X = (1L << SIZE_BITS_X) - 1L;
|
||||
long BITS_Y = (1L << SIZE_BITS_Y) - 1L;
|
||||
long BITS_Z = (1L << SIZE_BITS_Z) - 1L;
|
||||
int BIT_SHIFT_Z = SIZE_BITS_Y;
|
||||
int BIT_SHIFT_X = SIZE_BITS_Y + SIZE_BITS_Z;
|
||||
|
||||
int getX();
|
||||
int getY();
|
||||
int getZ();
|
||||
BlockPosImpl offset(ForgeDirection d);
|
||||
BlockPosImpl down();
|
||||
BlockPosImpl up();
|
||||
long asLong();
|
||||
|
||||
static long asLong(int x, int y, int z) {
|
||||
long l = 0L;
|
||||
l |= ((long) x & BITS_X) << BIT_SHIFT_X;
|
||||
l |= ((long) y & BITS_Y) << 0;
|
||||
l |= ((long) z & BITS_Z) << BIT_SHIFT_Z;
|
||||
return l;
|
||||
}
|
||||
|
||||
public static int unpackLongX(long packedPos) {
|
||||
return (int)(packedPos << 64 - BIT_SHIFT_X - SIZE_BITS_X >> 64 - SIZE_BITS_X);
|
||||
}
|
||||
|
||||
public static int unpackLongY(long packedPos) {
|
||||
return (int)(packedPos << 64 - SIZE_BITS_Y >> 64 - SIZE_BITS_Y);
|
||||
}
|
||||
|
||||
public static int unpackLongZ(long packedPos) {
|
||||
return (int)(packedPos << 64 - BIT_SHIFT_Z - SIZE_BITS_Z >> 64 - SIZE_BITS_Z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IBlockAccessExtended extends IBlockAccess {
|
||||
World getWorld();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
/**
|
||||
* To be used for modded items that don't have blocks. Baubles for example.
|
||||
*/
|
||||
public interface IDynamicLightProducer {
|
||||
int getLuminance();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
public interface MutableBlockPos extends BlockPos {
|
||||
|
||||
MutableBlockPos set(int x, int y, int z);
|
||||
|
||||
MutableBlockPos set(long packedPos);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface QuadProvider {
|
||||
|
||||
int R_MASK = 0xFF << 16;
|
||||
int G_MASK = 0xFF << 8;
|
||||
int B_MASK = 0xFF;
|
||||
|
||||
/**
|
||||
* Returns the color packed as ABGR. If you want to vary colors within a block, just ignore the color passed into
|
||||
* {@link #getQuads} and use whatever you want. You might want to override this to return a constant if you're
|
||||
* extending a block with an expensive color call, like leaves.
|
||||
*/
|
||||
default int getColor(IBlockAccess world, BlockPos pos, Block block, int meta, Random random) {
|
||||
|
||||
final int cin = block.colorMultiplier(world, pos.getX(), pos.getY(), pos.getZ());
|
||||
return (0xFF << 24) | ((cin & B_MASK) << 16) | (cin & G_MASK) | ((cin & R_MASK) >>> 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* If you need to allocate new quads, set this to true. If true, the quads returned by {@link #getQuads} are
|
||||
* recycled, and you should not keep a reference to them. Example: stone can return a static list every time, but a
|
||||
* modded block which adds or removes quads based on location would need dynamic quads.
|
||||
*/
|
||||
default boolean isDynamic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide quads to render. If you need new quads, they should be obtained with the passed supplier and
|
||||
* {@link #isDynamic} overridden to return true. If so, all quads in the list are recycled and references to them
|
||||
* should not be kept.
|
||||
*/
|
||||
List<QuadView> getQuads(IBlockAccess world, BlockPos pos, Block block, int meta, ForgeDirection dir, Random random, int color, Supplier<QuadView> quadPool);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.model.quad.ModelQuadViewMutable;
|
||||
import me.jellysquid.mods.sodium.client.render.pipeline.BlockRenderer;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface QuadView extends ModelQuadViewMutable {
|
||||
|
||||
boolean isShade();
|
||||
boolean isDeleted();
|
||||
ForgeDirection getFace();
|
||||
void copyFrom(QuadView src);
|
||||
void setRaw(int[] data, boolean shade, @Nullable ForgeDirection face, int colorIndex, int flags);
|
||||
int[] getRawVertexes();
|
||||
|
||||
/**
|
||||
* Present for compatibility with the Tesselator, not recommended for general use.
|
||||
*/
|
||||
void setState(int[] rawBuffer, int offset, BlockRenderer.Flags flags, int drawMode, float offsetX, float offsetY, float offsetZ);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
/**
|
||||
* Exposes a method to the API that allow mods to add texture update compat
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TextureServices {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateBlockTextureAnimation(IIcon icon, RenderBlocks renderBlocks) {
|
||||
if (renderBlocks instanceof ITexturesCache texturesCache) {
|
||||
texturesCache.getRenderedTextures().add(icon);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void updateTextureAnimation(IIcon icon) {
|
||||
if (icon instanceof SpriteExtension ext) {
|
||||
ext.celeritas$markActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
@@ -31,131 +39,190 @@ public class BatchingFontRenderer {
|
||||
|
||||
/** The underlying FontRenderer object that's being accelerated */
|
||||
protected FontRenderer underlying;
|
||||
/** Cached locations for each unicode page atlas */
|
||||
private final ResourceLocation[] unicodePageLocations;
|
||||
/** Array of width of all the characters in default.png */
|
||||
protected int[] charWidth = new int[256];
|
||||
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
|
||||
protected byte[] glyphWidth;
|
||||
protected int[] charWidth;
|
||||
/**
|
||||
* Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
|
||||
* drop shadows.
|
||||
*/
|
||||
private int[] colorCode;
|
||||
private final int[] colorCode;
|
||||
/** Location of the primary font atlas to bind. */
|
||||
protected final ResourceLocation locationFontTexture;
|
||||
/** The RenderEngine used to load and setup glyph textures. */
|
||||
private final TextureManager renderEngine;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
private final int AAMode;
|
||||
private final int AAStrength;
|
||||
private final int mvpMatrixLocation;
|
||||
private final int fontShaderId;
|
||||
|
||||
private static final short[] MCFONT_ASCII_LUT = new short[512];
|
||||
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
|
||||
final boolean isSGA;
|
||||
final boolean isSplash;
|
||||
|
||||
static {
|
||||
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
char ch = MCFONT_CHARS.charAt(i);
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
MCFONT_ASCII_LUT[ch] = i;
|
||||
} else {
|
||||
MCFONT_UNI_LUT.put(ch, i);
|
||||
private static class FontAAShader {
|
||||
|
||||
private static Program fontShader = null;
|
||||
public static Program getProgram() {
|
||||
if (fontShader == null) {
|
||||
String vsh, fsh;
|
||||
try {
|
||||
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
|
||||
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
|
||||
fontShader = builder.build();
|
||||
}
|
||||
return fontShader;
|
||||
}
|
||||
}
|
||||
|
||||
public static int lookupMcFontPosition(char ch) {
|
||||
if (ch < MCFONT_ASCII_LUT.length) {
|
||||
return MCFONT_ASCII_LUT[ch];
|
||||
} else {
|
||||
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
|
||||
}
|
||||
}
|
||||
|
||||
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
|
||||
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
|
||||
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, int[] colorCode, ResourceLocation locationFontTexture) {
|
||||
this.underlying = underlying;
|
||||
this.unicodePageLocations = unicodePageLocations;
|
||||
this.charWidth = charWidth;
|
||||
this.glyphWidth = glyphWidth;
|
||||
this.colorCode = colorCode;
|
||||
this.locationFontTexture = locationFontTexture;
|
||||
this.renderEngine = renderEngine;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
batchCommandPool.add(new FontDrawCmd());
|
||||
}
|
||||
|
||||
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
|
||||
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
//noinspection deprecation
|
||||
fontShaderId = FontAAShader.getProgram().getProgramId();
|
||||
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
|
||||
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
|
||||
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
|
||||
if (ebo == null) {
|
||||
ebo = new IndexBuffer();
|
||||
vbo = GL15.glGenBuffers();
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
// === Batched rendering
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vtxWriterIndex = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
private static final int INITIAL_BATCH_SIZE = 256;
|
||||
private static final int INITIAL_BATCH_SIZE = 2048;
|
||||
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
|
||||
"this is invalid!");
|
||||
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
|
||||
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
|
||||
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
|
||||
|
||||
// Layout in data:
|
||||
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
|
||||
// v, t and tb are floats, c is bytes; 36 bytes total
|
||||
private static final int VERTEX_SIZE = 36;
|
||||
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
|
||||
private static long vertexDataAddress = nmemAllocChecked(rawCapacity);
|
||||
|
||||
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
|
||||
private static int fontVAO = 0;
|
||||
private static int vbo;
|
||||
private static IndexBuffer ebo;
|
||||
|
||||
private int batchDepth = 0;
|
||||
|
||||
private int vertexDataPos = 0;
|
||||
private int idxWriterIndex = 0;
|
||||
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
|
||||
|
||||
/** */
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v) {
|
||||
final int oldCap = batchVtxPositions.capacity() / 2;
|
||||
if (vtxWriterIndex >= oldCap) {
|
||||
final int newCap = oldCap * 2;
|
||||
batchVtxPositions = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
|
||||
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
|
||||
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 2);
|
||||
final int oldIdxCap = batchIndices.capacity();
|
||||
final int newIdxCap = oldIdxCap * 2;
|
||||
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
|
||||
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
|
||||
private void allocateBuffers() {
|
||||
populateEBO(rawCapacity / VERTEX_SIZE);
|
||||
}
|
||||
|
||||
private void populateEBO(int capacity) {
|
||||
final int quadCount = capacity * 6;
|
||||
final ByteBuffer data = memAlloc(quadCount * 6 * 2);
|
||||
long ptr = memAddress0(data);
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
int base = (i * 4);
|
||||
|
||||
// triangle 1
|
||||
memPutShort(ptr, (short) base);
|
||||
memPutShort(ptr + 2, (short) (base + 1));
|
||||
memPutShort(ptr + 4, (short) (base + 2));
|
||||
|
||||
// triangle 2
|
||||
memPutShort(ptr + 6, (short) (base + 2));
|
||||
memPutShort(ptr + 8, (short) (base + 1));
|
||||
memPutShort(ptr + 10, (short) (base + 3));
|
||||
ptr += 12;
|
||||
}
|
||||
final int idx = vtxWriterIndex;
|
||||
final int idx2 = idx * 2;
|
||||
final int idx4 = idx * 4;
|
||||
batchVtxPositions.put(idx2, x);
|
||||
batchVtxPositions.put(idx2 + 1, y);
|
||||
|
||||
ebo.upload(data);
|
||||
|
||||
memFree(data);
|
||||
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
|
||||
rawCapacity *= 2;
|
||||
vertexDataAddress = nmemReallocChecked(vertexDataAddress, rawCapacity);
|
||||
|
||||
allocateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
|
||||
final long ptr = vertexDataAddress + vertexDataPos;
|
||||
|
||||
// v, v
|
||||
memPutFloat(ptr, x);
|
||||
memPutFloat(ptr + 4, y);
|
||||
|
||||
// t, t
|
||||
memPutFloat(ptr + 8, u);
|
||||
memPutFloat(ptr + 12, v);
|
||||
|
||||
// c, c, c, c
|
||||
// 0xAARRGGBB
|
||||
batchVtxColors.put(idx4, (byte) ((rgba >> 16) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 1, (byte) ((rgba >> 8) & 0xFF));
|
||||
batchVtxColors.put(idx4 + 2, (byte) (rgba & 0xFF));
|
||||
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
|
||||
batchVtxTexCoords.put(idx2, u);
|
||||
batchVtxTexCoords.put(idx2 + 1, v);
|
||||
vtxWriterIndex++;
|
||||
memPutByte(ptr + 16, (byte) ((rgba >> 16) & 0xFF));
|
||||
memPutByte(ptr + 17, (byte) ((rgba >> 8) & 0xFF));
|
||||
memPutByte(ptr + 18, (byte) (rgba & 0xFF));
|
||||
memPutByte(ptr + 19, (byte) ((rgba >> 24) & 0xFF));
|
||||
|
||||
// tb, tb, tb, tb
|
||||
memPutFloat(ptr + 20, uMin);
|
||||
memPutFloat(ptr + 24, uMax);
|
||||
memPutFloat(ptr + 28, vMin);
|
||||
memPutFloat(ptr + 32, vMax);
|
||||
|
||||
vertexDataPos += VERTEX_SIZE;
|
||||
}
|
||||
|
||||
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
|
||||
final int vtxId = vtxWriterIndex;
|
||||
pushVtx(x, y, rgba, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0);
|
||||
pushQuadIdx(vtxId);
|
||||
ensureCapacity();
|
||||
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private int pushQuadIdx(int startV) {
|
||||
final int idx = idxWriterIndex;
|
||||
batchIndices.put(idx, startV);
|
||||
batchIndices.put(idx + 1, startV + 1);
|
||||
batchIndices.put(idx + 2, startV + 2);
|
||||
//
|
||||
batchIndices.put(idx + 3, startV + 2);
|
||||
batchIndices.put(idx + 4, startV + 1);
|
||||
batchIndices.put(idx + 5, startV + 3);
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
|
||||
ensureCapacity();
|
||||
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private void pushQuadIdx() {
|
||||
idxWriterIndex += 6;
|
||||
return idx;
|
||||
}
|
||||
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
|
||||
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
if (!batchCommands.isEmpty()) {
|
||||
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
|
||||
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
|
||||
@@ -171,7 +238,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
final FontDrawCmd cmd = batchCommandPool.pop();
|
||||
cmd.reset(startIdx, idxCount, texture);
|
||||
cmd.reset(startIdx, idxCount, texture, isUnicode);
|
||||
batchCommands.add(cmd);
|
||||
}
|
||||
|
||||
@@ -179,12 +246,14 @@ public class BatchingFontRenderer {
|
||||
|
||||
public int startVtx;
|
||||
public int idxCount;
|
||||
public boolean isUnicode;
|
||||
public ResourceLocation texture;
|
||||
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
|
||||
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
|
||||
this.startVtx = startVtx;
|
||||
this.idxCount = vtxCount;
|
||||
this.texture = texture;
|
||||
this.isUnicode = isUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,9 +294,6 @@ public class BatchingFontRenderer {
|
||||
* allow for easier optimizing of blocks of font rendering code.
|
||||
*/
|
||||
public void beginBatch() {
|
||||
if (batchDepth == Integer.MAX_VALUE) {
|
||||
throw new StackOverflowError("More than Integer.MAX_VALUE nested font rendering batch operations");
|
||||
}
|
||||
batchDepth++;
|
||||
}
|
||||
|
||||
@@ -243,7 +309,23 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Matrix4f scratchMvp = new Matrix4f();
|
||||
private int fontAAModeLast = -1;
|
||||
private int fontAAStrengthLast = -1;
|
||||
|
||||
private void flushBatch() {
|
||||
if (vertexDataPos == 0) {
|
||||
clearBatch();
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload first (to reduce stalls)
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
streamUpload(vertexDataAddress, vertexDataPos);
|
||||
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
// Sort&Draw
|
||||
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
@@ -255,16 +337,51 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL_FLAT);
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
|
||||
glTexCoordPointer(2, 0, batchVtxTexCoords);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, 0, batchVtxColors);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glVertexPointer(2, 0, batchVtxPositions);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glUseProgram(fontShaderId);
|
||||
if (FontConfig.fontAAMode != fontAAModeLast) {
|
||||
fontAAModeLast = FontConfig.fontAAMode;
|
||||
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
|
||||
}
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
}
|
||||
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = GL30.glGenVertexArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
|
||||
ebo.bind();
|
||||
|
||||
// position
|
||||
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
// texcoords
|
||||
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 8);
|
||||
GL20.glEnableVertexAttribArray(1);
|
||||
|
||||
// color
|
||||
GL20.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, VERTEX_SIZE, 16);
|
||||
GL20.glEnableVertexAttribArray(2);
|
||||
|
||||
// tex bounds
|
||||
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, VERTEX_SIZE, 20);
|
||||
GL20.glEnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
|
||||
// Use plain for loop to avoid allocations
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
@@ -283,14 +400,14 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
batchIndices.limit(cmd.startVtx + cmd.idxCount);
|
||||
batchIndices.position(cmd.startVtx);
|
||||
glDrawElements(GL_TRIANGLES, batchIndices);
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_SHORT, (long) cmd.startVtx * 2L);
|
||||
}
|
||||
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
GLStateManager.glUseProgram(prevProgram);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
@@ -299,13 +416,21 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
|
||||
clearBatch();
|
||||
}
|
||||
|
||||
private void clearBatch() {
|
||||
// Clear for the next batch
|
||||
batchCommandPool.addAll(batchCommands);
|
||||
batchCommands.clear();
|
||||
vtxWriterIndex = 0;
|
||||
vertexDataPos = 0;
|
||||
idxWriterIndex = 0;
|
||||
batchIndices.limit(batchIndices.capacity());
|
||||
batchIndices.position(0);
|
||||
}
|
||||
|
||||
private static void streamUpload(long address, int bytes) {
|
||||
final ByteBuffer data = memByteBuffer(address, bytes);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bytes, GL15.GL_STREAM_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
@@ -314,17 +439,28 @@ public class BatchingFontRenderer {
|
||||
return (what >= fromInclusive) && (what <= toInclusive);
|
||||
}
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
public boolean forceDefaults() {
|
||||
return this.isSGA || this.isSplash;
|
||||
}
|
||||
|
||||
public float getGlyphScaleX() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphScaleY() {
|
||||
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect));
|
||||
}
|
||||
|
||||
public float getGlyphSpacing() {
|
||||
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
|
||||
}
|
||||
|
||||
public float getWhitespaceScale() {
|
||||
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
|
||||
}
|
||||
|
||||
public float getShadowOffset() {
|
||||
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
private static final char FORMATTING_CHAR = 167; // §
|
||||
@@ -337,6 +473,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
|
||||
|
||||
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
this.beginBatch();
|
||||
float curX = anchorX;
|
||||
try {
|
||||
@@ -356,10 +495,15 @@ public class BatchingFontRenderer {
|
||||
boolean curStrikethrough = false;
|
||||
boolean curUnderline = false;
|
||||
|
||||
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
|
||||
float glyphScaleY = getGlyphScaleY();
|
||||
float glyphScaleX = getGlyphScaleX();
|
||||
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
|
||||
|
||||
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
|
||||
float underlineStartX = 0.0f;
|
||||
float underlineEndX = 0.0f;
|
||||
final float strikethroughY = anchorY + (float) (underlying.FONT_HEIGHT / 2);
|
||||
|
||||
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
|
||||
float strikethroughStartX = 0.0f;
|
||||
float strikethroughEndX = 0.0f;
|
||||
|
||||
@@ -371,8 +515,8 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
underlineStartX = underlineEndX;
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
@@ -381,9 +525,9 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
strikethroughStartX = strikethroughEndX;
|
||||
}
|
||||
|
||||
@@ -428,148 +572,89 @@ public class BatchingFontRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (curRandom && lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
chr = MCFONT_CHARS.charAt(lutIndex);
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
|
||||
if (chrReplacement != null) {
|
||||
char replacement = chrReplacement.charAt(0);
|
||||
boolean isReplacementCharAvailable =
|
||||
FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement);
|
||||
if (isReplacementCharAvailable) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
|
||||
if (curRandom) {
|
||||
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
|
||||
}
|
||||
|
||||
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
|
||||
// Check ASCII space, NBSP, NNBSP
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
curX += 4;
|
||||
curX += 4 * this.getWhitespaceScale();
|
||||
continue;
|
||||
}
|
||||
|
||||
final float uStart;
|
||||
final float vStart;
|
||||
final float xAdvance;
|
||||
final float glyphW;
|
||||
final float uSz;
|
||||
final float vSz;
|
||||
final float uStart = fontProvider.getUStart(chr);
|
||||
final float vStart = fontProvider.getVStart(chr);
|
||||
final float xAdvance = fontProvider.getXAdvance(chr) * glyphScaleX;
|
||||
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
|
||||
final float uSz = fontProvider.getUSize(chr);
|
||||
final float vSz = fontProvider.getVSize(chr);
|
||||
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
|
||||
final ResourceLocation texture;
|
||||
|
||||
if (lutIndex == -1 || unicodeFlag) {
|
||||
if (glyphWidth[chr] == 0) {
|
||||
continue;
|
||||
}
|
||||
// Draw unicode char
|
||||
final int uniPage = chr / 256;
|
||||
texture = getUnicodePageLocation(uniPage);
|
||||
final int startColumn = this.glyphWidth[chr] >>> 4;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
|
||||
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
glyphW = chrWidth / 2.0f + 1.0f;
|
||||
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
uSz = chrWidth / 256.0f;
|
||||
vSz = 15.98f / 256.0f;
|
||||
|
||||
} else {
|
||||
// Draw "ASCII" char
|
||||
uStart = ((lutIndex % 16) * 8) / 128.0F;
|
||||
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
xAdvance = this.charWidth[lutIndex];
|
||||
if (xAdvance == 0) {
|
||||
continue;
|
||||
}
|
||||
glyphW = xAdvance - 0.01F;
|
||||
uSz = (glyphW - 1.0F) / 128.0F;
|
||||
vSz = 7.99F / 128.0F;
|
||||
texture = locationFontTexture;
|
||||
}
|
||||
|
||||
final int vtxId = vtxWriterIndex;
|
||||
final float shadowOffset = fontProvider.getShadowOffset();
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final ResourceLocation texture = fontProvider.getTexture(chr);
|
||||
final int idxId = idxWriterIndex;
|
||||
|
||||
int vtxCount = 0;
|
||||
|
||||
if (enableShadow) {
|
||||
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= shadowCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
final float shadowOffset2 = 2.0f * shadowOffset;
|
||||
pushVtx(curX + itOff + shadowOffset2, anchorY + shadowOffset, curShadowColor, uStart, vStart);
|
||||
pushVtx(
|
||||
curX - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart,
|
||||
vStart + vSz);
|
||||
pushVtx(
|
||||
curX + glyphW - 1.0F + itOff + shadowOffset2,
|
||||
anchorY + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
|
||||
anchorY + 7.99F + shadowOffset,
|
||||
curShadowColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
if (curBold) {
|
||||
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushVtx(curX + itOff, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
|
||||
if (curBold) {
|
||||
pushVtx(shadowOffset + curX + itOff, anchorY, curColor, uStart, vStart);
|
||||
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
|
||||
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
|
||||
anchorY + 7.99F,
|
||||
curColor,
|
||||
uStart + uSz,
|
||||
vStart + vSz);
|
||||
pushQuadIdx(vtxId + vtxCount);
|
||||
vtxCount += 4;
|
||||
for (int n = 1; n <= boldCopies; n++) {
|
||||
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
|
||||
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
|
||||
}
|
||||
}
|
||||
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
|
||||
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
|
||||
/*
|
||||
Vertex-per-char counts for different configurations
|
||||
default: 4
|
||||
shadow only: 4(1 + shadowCopies)
|
||||
bold only: 4(1 + boldCopies)
|
||||
both: 4(1 + 2 * shadowCopies + boldCopies)
|
||||
*/
|
||||
int charCount = 1;
|
||||
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
|
||||
if (curBold) { charCount += boldCopies; }
|
||||
final int vtxCount = 4 * charCount;
|
||||
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
|
||||
|
||||
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
|
||||
underlineEndX = curX;
|
||||
strikethroughEndX = curX;
|
||||
}
|
||||
|
||||
if (curUnderline && underlineStartX != underlineEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
|
||||
final int ulIdx = idxWriterIndex;
|
||||
@@ -577,9 +662,9 @@ public class BatchingFontRenderer {
|
||||
strikethroughStartX,
|
||||
strikethroughY,
|
||||
strikethroughEndX - strikethroughStartX,
|
||||
1.0f,
|
||||
glyphScaleY,
|
||||
curColor);
|
||||
pushDrawCmd(ulIdx, 6, null);
|
||||
pushDrawCmd(ulIdx, 6, null, false);
|
||||
}
|
||||
|
||||
} finally {
|
||||
@@ -588,4 +673,25 @@ public class BatchingFontRenderer {
|
||||
return curX + (enableShadow ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
public float getCharWidthFine(char chr) {
|
||||
if (chr == FORMATTING_CHAR) { return -1; }
|
||||
|
||||
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
}
|
||||
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
|
||||
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
public void overrideBlendFunc(int srcRgb, int dstRgb) {
|
||||
blendSrcRGB = srcRgb;
|
||||
blendDstRGB = dstRgb;
|
||||
}
|
||||
|
||||
public void resetBlendFunc() {
|
||||
blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public interface FontProvider {
|
||||
|
||||
/**
|
||||
* For use with §k. Should fetch a character of the same width as provided.
|
||||
*/
|
||||
char getRandomReplacement(char chr);
|
||||
boolean isGlyphAvailable(char chr);
|
||||
float getUStart(char chr);
|
||||
float getVStart(char chr);
|
||||
float getXAdvance(char chr);
|
||||
float getGlyphW(char chr);
|
||||
float getUSize(char chr);
|
||||
float getVSize(char chr);
|
||||
float getShadowOffset();
|
||||
ResourceLocation getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import lombok.Setter;
|
||||
import lombok.Value;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.ITextureObject;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FontProviderCustom implements FontProvider {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
public static final String FONT_DIR = "fonts/custom/";
|
||||
static final int ATLAS_SIZE = 128;
|
||||
static final int ATLAS_COUNT = 512;
|
||||
private final byte id; // 0 - primary font, 1 - fallback font
|
||||
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
private float currentFontQuality = FontConfig.customFontQuality;
|
||||
@Setter
|
||||
private Font font;
|
||||
|
||||
private FontProviderCustom(byte id) {
|
||||
this.id = id;
|
||||
Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
String myFontName = switch (this.id) {
|
||||
case 0 -> FontConfig.customFontNamePrimary;
|
||||
case 1 -> FontConfig.customFontNameFallback;
|
||||
default -> null;
|
||||
};
|
||||
if (Objects.equals(myFontName, "(none)")) {
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
int fontPos = -1;
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
|
||||
fontPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fontPos == -1) {
|
||||
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
|
||||
this.font = null;
|
||||
return;
|
||||
}
|
||||
this.font = availableFonts[fontPos].deriveFont(this.currentFontQuality);
|
||||
}
|
||||
private static class InstLoader {
|
||||
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
|
||||
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
|
||||
}
|
||||
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
|
||||
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
this.currentFontQuality = FontConfig.customFontQuality;
|
||||
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
|
||||
|
||||
File[] files = new File(getFontDir()).listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!Files.isSymbolicLink(f.toPath())) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
|
||||
Map mapTextureObjects = tm.mapTextureObjects;
|
||||
for (int i = 0; i < ATLAS_COUNT; i++) {
|
||||
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
|
||||
if (mapTextureObjects.containsKey(key)) {
|
||||
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
|
||||
TextureUtil.deleteTexture(obj.getGlTextureId());
|
||||
mapTextureObjects.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
|
||||
}
|
||||
|
||||
private String getFontDir() {
|
||||
return FONT_DIR + "f" + this.id + "/";
|
||||
}
|
||||
|
||||
private String getAtlasFilename(int atlasId) {
|
||||
return "f" + this.id + "p" + atlasId;
|
||||
}
|
||||
|
||||
String getAtlasResourceName(int atlasId) {
|
||||
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
|
||||
}
|
||||
|
||||
String getAtlasFullPath(int atlasId) {
|
||||
return getFontDir() + getAtlasFilename(atlasId) + ".png";
|
||||
}
|
||||
|
||||
@Value
|
||||
private class GlyphData {
|
||||
float uStart;
|
||||
float vStart;
|
||||
float xAdvance;
|
||||
float glyphW;
|
||||
float uSz;
|
||||
float vSz;
|
||||
}
|
||||
|
||||
private class FontAtlas {
|
||||
|
||||
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
|
||||
private ResourceLocation texture;
|
||||
private final int id;
|
||||
|
||||
FontAtlas(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
void construct(Font font) {
|
||||
int atlasChars = 0;
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) { atlasChars++; }
|
||||
}
|
||||
if (atlasChars == 0) { return; }
|
||||
|
||||
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
g2d.setFont(font);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
g2d.dispose();
|
||||
|
||||
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
|
||||
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
|
||||
final float charSeparator = currentFontQuality / 3f;
|
||||
int rowWidth = 0;
|
||||
int maxRowWidth = 0;
|
||||
atlasChars = 0;
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
if (atlasChars % atlasTilesX == 0) {
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
rowWidth = 0;
|
||||
}
|
||||
final char ch = (char)(i + ATLAS_SIZE * this.id);
|
||||
if (font.canDisplay(ch)) {
|
||||
rowWidth += (int) (charSeparator + fm.charWidth(ch));
|
||||
atlasChars++;
|
||||
}
|
||||
}
|
||||
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
||||
|
||||
final int lineHeight = fm.getHeight();
|
||||
final float desc = fm.getDescent();
|
||||
|
||||
final int imageWidth = (int) (maxRowWidth + charSeparator);
|
||||
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
|
||||
|
||||
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
g2d = image.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setFont(font);
|
||||
fm = g2d.getFontMetrics();
|
||||
|
||||
int tileX = 0, tileY = 0; // position in atlas tiles
|
||||
int imgX = (int) charSeparator; // position in pixels
|
||||
|
||||
for (int i = 0; i < ATLAS_SIZE; i++) {
|
||||
final char ch = (char) (i + ATLAS_SIZE * this.id);
|
||||
if (!font.canDisplay(ch)) { continue; }
|
||||
|
||||
if (tileX >= atlasTilesX) {
|
||||
tileX = 0;
|
||||
imgX = (int) charSeparator;
|
||||
tileY++;
|
||||
}
|
||||
|
||||
final int charWidth = fm.charWidth(ch);
|
||||
final float charAspectRatio = (float) charWidth / lineHeight;
|
||||
final float inset = currentFontQuality / 16;
|
||||
g2d.drawString(Character.toString(ch), imgX, (lineHeight + charSeparator) * (tileY + 1) - desc);
|
||||
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
|
||||
final float vStart = ((lineHeight + charSeparator) * (tileY + 1) - lineHeight - inset) / imageHeight;
|
||||
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
|
||||
final float glyphW = charAspectRatio * 8 + 1;
|
||||
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
|
||||
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
|
||||
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
|
||||
imgX += (int) (charWidth + charSeparator);
|
||||
tileX++;
|
||||
}
|
||||
g2d.dispose();
|
||||
try {
|
||||
Files.createDirectories(Paths.get(getFontDir()));
|
||||
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
|
||||
}
|
||||
}
|
||||
|
||||
private FontAtlas getAtlas(char chr) {
|
||||
int id = chr / ATLAS_SIZE;
|
||||
FontAtlas fa = this.fontAtlases[id];
|
||||
if (fa == null) {
|
||||
fa = new FontAtlas(id);
|
||||
fa.construct(this.font);
|
||||
this.fontAtlases[id] = fa;
|
||||
}
|
||||
return fa;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
if (this.font == null) { return false; }
|
||||
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return getAtlas(chr).texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return FontConfig.customFontScale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public final class FontProviderMC implements FontProvider {
|
||||
|
||||
/**
|
||||
* The full list of characters present in the default Minecraft font, excluding the Unicode font
|
||||
*/
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
|
||||
private FontProviderMC() {}
|
||||
private static class InstLoader {
|
||||
static final FontProviderMC instance = new FontProviderMC();
|
||||
static final FontProviderMC instanceSGA = new FontProviderMC();
|
||||
}
|
||||
public static FontProviderMC get(boolean isSGA) {
|
||||
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
|
||||
}
|
||||
|
||||
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
|
||||
ResourceLocation locationFontTexture = null;
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
|
||||
}
|
||||
}
|
||||
|
||||
public int lookupMcFontPosition(char ch) {
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return MCFONT_ASCII_MAP.containsKey(chr);
|
||||
}
|
||||
|
||||
public char getRandomReplacement(char chr){
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
if (lutIndex != -1) {
|
||||
int randomReplacementIndex;
|
||||
do {
|
||||
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
|
||||
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
|
||||
|
||||
lutIndex = randomReplacementIndex;
|
||||
return MCFONT_CHARS.charAt(lutIndex);
|
||||
}
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return ((lutIndex % 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (float) ((lutIndex / 16) * 8) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return charWidth[lutIndex] - 0.01F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
int lutIndex = lookupMcFontPosition(chr);
|
||||
return (charWidth[lutIndex] - 1.01F) / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return 7.99F / 128.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
return locationFontTexture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public final class FontProviderUnicode implements FontProvider {
|
||||
|
||||
private FontProviderUnicode() {
|
||||
try {
|
||||
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
|
||||
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputstream.read(this.glyphWidth);
|
||||
}
|
||||
catch (IOException ioexception) {
|
||||
throw new RuntimeException(ioexception);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
|
||||
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
|
||||
private final byte[] glyphWidth = new byte[65536];
|
||||
|
||||
private ResourceLocation getUnicodePageLocation(int page) {
|
||||
final ResourceLocation lookup = unicodePageLocations[page];
|
||||
if (lookup == null) {
|
||||
final ResourceLocation rl = new ResourceLocation(String.format(
|
||||
"textures/font/unicode_page_%02x.png",
|
||||
page));
|
||||
unicodePageLocations[page] = rl;
|
||||
return rl;
|
||||
} else {
|
||||
return lookup;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
return chr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
|
||||
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
return (endColumnF - startColumnF) / 2.0F + 1.0F;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return chrWidth / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
|
||||
final int endColumn = this.glyphWidth[chr] & 15;
|
||||
final float startColumnF = (float) startColumn;
|
||||
final float endColumnF = (float) (endColumn + 1);
|
||||
final float chrWidth = endColumnF - startColumnF - 0.02F;
|
||||
return (chrWidth - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return (16.0f - 0.42f) / 256.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
return getUnicodePageLocation(uniPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
|
||||
import cpw.mods.fml.client.SplashProgress;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.resources.DefaultResourcePack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FontStrategist {
|
||||
|
||||
@Getter
|
||||
private static final Font[] availableFonts;
|
||||
public static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
static {
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
|
||||
availableFonts = new Font[0];
|
||||
} else {
|
||||
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
|
||||
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
|
||||
HashMap<String, Font> fontSet = new HashMap<>();
|
||||
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
|
||||
|
||||
for (Font font : availableFontsDirty) {
|
||||
String fontName = font.getFontName();
|
||||
if (fontSet.containsKey(fontName)) {
|
||||
duplicates.add(fontName);
|
||||
} else {
|
||||
fontSet.put(fontName, font);
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicates.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
|
||||
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
|
||||
String dupe = iter.next();
|
||||
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(". Some fonts may be missing from the font selection menu.");
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
|
||||
|
||||
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
|
||||
}
|
||||
|
||||
// create and add the resource pack that provides fonts
|
||||
HashMap<String, File> packMap = new HashMap<>();
|
||||
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
|
||||
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
|
||||
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
|
||||
}
|
||||
|
||||
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
|
||||
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
|
||||
defaultResourcePacks.add(fontResourcePack);
|
||||
}
|
||||
|
||||
/**
|
||||
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
|
||||
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
|
||||
*/
|
||||
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
|
||||
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(true);
|
||||
}
|
||||
if (customFontEnabled && !me.isSplash) {
|
||||
FontProvider fp;
|
||||
fp = FontProviderCustom.getPrimary();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = FontProviderCustom.getFallback();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
return FontProviderUnicode.get();
|
||||
} else {
|
||||
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(false);
|
||||
} else {
|
||||
return FontProviderUnicode.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reloadCustomFontProviders() {
|
||||
FontProviderCustom.getPrimary().setFont(null);
|
||||
FontProviderCustom.getFallback().setFont(null);
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getPrimary().reloadFont(i);
|
||||
}
|
||||
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
|
||||
FontProviderCustom.getFallback().reloadFont(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
|
||||
// noinspection deprecation
|
||||
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
|
||||
|
||||
try {
|
||||
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
|
||||
active = active || customSplashClass.isInstance(fontRenderer);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfigStorage;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityTypeEntry;
|
||||
import jss.notfine.core.Settings;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DynamicLightsOptionPages {
|
||||
|
||||
public static OptionPage dynamicLights() {
|
||||
if (!DynamicLights.configEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<OptionGroup> groups = new ArrayList<>();
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SHADER_FORCE.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_FRUSTUM_CULLING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_ADAPTIVE_TICKING.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_CULL_TIMEOUT.option)
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOW_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_SLOWER_DIST.option)
|
||||
.add(Settings.DYNAMIC_LIGHTS_BACKGROUND_DIST.option)
|
||||
.build());
|
||||
|
||||
// Entity type toggles - grouped by mod ID
|
||||
List<EntityTypeEntry> allEntities = EntityLightConfig.getAllEntityTypes();
|
||||
Map<String, List<EntityTypeEntry>> byMod = new LinkedHashMap<>();
|
||||
|
||||
for (EntityTypeEntry entry : allEntities) {
|
||||
byMod.computeIfAbsent(entry.getModId(), _ -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
// Create a group for each mod's entities
|
||||
for (Map.Entry<String, List<EntityTypeEntry>> modEntry : byMod.entrySet()) {
|
||||
String modId = modEntry.getKey();
|
||||
List<EntityTypeEntry> entities = modEntry.getValue();
|
||||
|
||||
OptionGroup.Builder groupBuilder = OptionGroup.createBuilder();
|
||||
|
||||
for (EntityTypeEntry entity : entities) {
|
||||
Option<Boolean> option = createEntityOption(entity);
|
||||
if (option != null) {
|
||||
groupBuilder.add(option);
|
||||
}
|
||||
}
|
||||
|
||||
// Only add group if it has options
|
||||
OptionGroup group = groupBuilder.build();
|
||||
if (!group.getOptions().isEmpty()) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
return new OptionPage(I18n.format("options.dynamiclights.page"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
private static Option<Boolean> createEntityOption(EntityTypeEntry entry) {
|
||||
final String className = entry.getClassName();
|
||||
|
||||
// Resolve class once during option creation, not on every get/set
|
||||
Class<?> resolvedClass;
|
||||
try {
|
||||
resolvedClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Class not loaded, skip this option
|
||||
return null;
|
||||
}
|
||||
|
||||
final Class<?> entityClass = resolvedClass;
|
||||
|
||||
return OptionImpl.createBuilder(Boolean.class, EntityLightConfigStorage.INSTANCE)
|
||||
.setName(entry.getDisplayName() + " (" + entry.getModId() + ")")
|
||||
.setTooltip(I18n.format("options.dynamiclights.entity.tooltip", entry.getDisplayName()))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding(
|
||||
(_, value) -> EntityLightConfig.setEntityTypeEnabled(entityClass, value),
|
||||
storage -> EntityLightConfig.isEntityTypeEnabled(entityClass)
|
||||
)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigException;
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.client.font.FontStrategist;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import net.coderbot.iris.gui.element.widget.IrisButton;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiSlot;
|
||||
import net.minecraft.client.gui.GuiTextField;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FontConfigScreen extends GuiScreen {
|
||||
|
||||
private static final Font[] availableFonts = FontStrategist.getAvailableFonts();
|
||||
private final GuiScreen parent;
|
||||
private final String title;
|
||||
private final String searchPrompt;
|
||||
private final String testAreaPrompt;
|
||||
private List<String> testAreaInfo;
|
||||
private List<String> noFontsWarning;
|
||||
private String currentPrimaryFontName;
|
||||
private String currentFallbackFontName;
|
||||
private FontList fontList;
|
||||
private int selectedPrimaryFontListPos = -1;
|
||||
private int selectedFallbackFontListPos = -1;
|
||||
private ArrayList<Font> displayedFonts;
|
||||
private GuiTextField searchBox;
|
||||
private GuiTextField testArea;
|
||||
private int sliderPages = 0;
|
||||
private int displayedSliderPage = 0;
|
||||
private final int sliderWidth = 160;
|
||||
private final int sliderHeight = 20;
|
||||
|
||||
// need to save these separately
|
||||
private IrisButton fwdButton;
|
||||
private IrisButton backButton;
|
||||
|
||||
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
|
||||
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
|
||||
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
|
||||
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
|
||||
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
|
||||
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
|
||||
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
|
||||
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
|
||||
|
||||
public FontConfigScreen(GuiScreen parent) {
|
||||
this.title = I18n.format("options.angelica.fontconfig.title");
|
||||
this.searchPrompt = I18n.format("options.angelica.fontconfig.searchprompt");
|
||||
this.testAreaPrompt = I18n.format("options.angelica.fontconfig.testareaprompt");
|
||||
this.parent = parent;
|
||||
this.currentPrimaryFontName = FontConfig.customFontNamePrimary;
|
||||
this.currentFallbackFontName = FontConfig.customFontNameFallback;
|
||||
this.displayedFonts = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
for (int i = 0; i < availableFonts.length; i++) {
|
||||
if (Objects.equals(this.currentPrimaryFontName, availableFonts[i].getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(this.currentFallbackFontName, availableFonts[i].getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
|
||||
searchBox.setMaxStringLength(64);
|
||||
fontList = new FontList();
|
||||
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
|
||||
testArea.setMaxStringLength(512);
|
||||
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
|
||||
initButtons();
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y3 = this.height - 65 + 2 * (sliderHeight + sliderSpacing);
|
||||
|
||||
this.buttonList.add(new IrisButton(x1 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
FontConfig.enableCustomFont ? I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font"), this::toggleCustomFont));
|
||||
this.buttonList.add(new IrisButton(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
|
||||
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
|
||||
I18n.format("gui.done"), button -> this.onClose()));
|
||||
|
||||
backButton = new IrisButton(halfWidth - 20 - 224, this.height - 89 - 10, 40, 20,
|
||||
"<", button -> this.switchPage(-1));
|
||||
fwdButton = new IrisButton(halfWidth - 20 + 224, this.height - 89 - 10, 40, 20,
|
||||
">", button -> this.switchPage(1));
|
||||
this.buttonList.add(fwdButton);
|
||||
this.buttonList.add(backButton);
|
||||
|
||||
initSliders();
|
||||
}
|
||||
|
||||
private void initSliders() {
|
||||
ArrayList<SliderClone> sliders = new ArrayList<>();
|
||||
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontQuality)
|
||||
.initialValue(FontConfig.customFontQuality)
|
||||
.setter(this::setFontQuality)
|
||||
.langKey("options.angelica.fontconfig.font_quality")
|
||||
.formatString("%2.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowOffset)
|
||||
.initialValue(FontConfig.fontShadowOffset)
|
||||
.setter(value -> FontConfig.fontShadowOffset = value)
|
||||
.langKey("options.angelica.fontconfig.shadow_offset")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphAspect)
|
||||
.initialValue(FontConfig.glyphAspect)
|
||||
.setter(value -> FontConfig.glyphAspect = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_aspect")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphScale)
|
||||
.initialValue(FontConfig.glyphScale)
|
||||
.setter(value -> FontConfig.glyphScale = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAStrength)
|
||||
.initialValue(FontConfig.fontAAStrength)
|
||||
.setter(value -> FontConfig.fontAAStrength = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.font_aa_strength")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optFontAAMode)
|
||||
.initialValue(FontConfig.fontAAMode)
|
||||
.setter(value -> FontConfig.fontAAMode = value.intValue())
|
||||
.formatter(this::fontAAModeFormat)
|
||||
.langKey("options.angelica.fontconfig.aamode")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optWhitespaceScale)
|
||||
.initialValue(FontConfig.whitespaceScale)
|
||||
.setter(value -> FontConfig.whitespaceScale = value)
|
||||
.langKey("options.angelica.fontconfig.whitespace_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optGlyphSpacing)
|
||||
.initialValue(FontConfig.glyphSpacing)
|
||||
.setter(value -> FontConfig.glyphSpacing = value)
|
||||
.langKey("options.angelica.fontconfig.glyph_spacing")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optShadowCopies)
|
||||
.initialValue(FontConfig.shadowCopies)
|
||||
.setter(value -> FontConfig.shadowCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.shadow_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optBoldCopies)
|
||||
.initialValue(FontConfig.boldCopies)
|
||||
.setter(value -> FontConfig.boldCopies = value.intValue())
|
||||
.langKey("options.angelica.fontconfig.bold_copies")
|
||||
.formatString("%.0f")
|
||||
.build()
|
||||
);
|
||||
sliders.add(new SliderClone.SliderCloneBuilder()
|
||||
.width(sliderWidth)
|
||||
.height(sliderHeight)
|
||||
.option(optCustomFontScale)
|
||||
.initialValue(FontConfig.customFontScale)
|
||||
.setter(value -> FontConfig.customFontScale = value)
|
||||
.langKey("options.angelica.fontconfig.custom_font_scale")
|
||||
.formatString("x%3.2f")
|
||||
.build()
|
||||
);
|
||||
|
||||
final int halfWidth = this.width / 2;
|
||||
final int sliderSpacing = 4;
|
||||
|
||||
final int x1 = halfWidth - sliderWidth - sliderSpacing;
|
||||
final int x2 = halfWidth;
|
||||
final int x3 = halfWidth + sliderWidth + sliderSpacing;
|
||||
final int y1 = this.height - 65;
|
||||
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
|
||||
|
||||
this.sliderPages = sliders.size() / 6;
|
||||
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
|
||||
|
||||
this.backButton.enabled = (this.displayedSliderPage != 0);
|
||||
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
|
||||
|
||||
final int start = 6 * this.displayedSliderPage;
|
||||
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
|
||||
List<SliderClone> s = sliders.subList(start, end);
|
||||
try {
|
||||
s.get(0).setCenterPosition(x1, y1);
|
||||
s.get(1).setCenterPosition(x1, y2);
|
||||
s.get(2).setCenterPosition(x2, y1);
|
||||
s.get(3).setCenterPosition(x2, y2);
|
||||
s.get(4).setCenterPosition(x3, y1);
|
||||
s.get(5).setCenterPosition(x3, y2);
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
// we're on the last page and it has less than 6 sliders to display
|
||||
}
|
||||
this.buttonList.addAll(s);
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
applyChanges(true);
|
||||
this.mc.displayGuiScreen(parent);
|
||||
}
|
||||
|
||||
private void applyChanges(boolean finalApply) {
|
||||
int pos;
|
||||
pos = selectedPrimaryFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
pos = selectedFallbackFontListPos;
|
||||
if (pos >= 0 && pos < displayedFonts.size()) {
|
||||
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
|
||||
}
|
||||
|
||||
FontStrategist.reloadCustomFontProviders();
|
||||
|
||||
if (finalApply) {
|
||||
ConfigurationManager.save(FontConfig.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetValues(IrisButton button) {
|
||||
try {
|
||||
ConfigurationManager.getConfigElements(FontConfig.class).forEach(elem -> elem.set(elem.getDefault()));
|
||||
} catch (ConfigException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
currentPrimaryFontName = null;
|
||||
currentFallbackFontName = null;
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
super.buttonList.clear();
|
||||
this.initButtons();
|
||||
}
|
||||
|
||||
private void toggleCustomFont(IrisButton button) {
|
||||
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
|
||||
applyChanges(false);
|
||||
button.displayString = FontConfig.enableCustomFont ?
|
||||
I18n.format("options.angelica.fontconfig.disable_custom_font") :
|
||||
I18n.format("options.angelica.fontconfig.enable_custom_font");
|
||||
}
|
||||
|
||||
private void switchPage(int offset) {
|
||||
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
|
||||
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
|
||||
this.initSliders();
|
||||
}
|
||||
|
||||
private int qualityLast = FontConfig.customFontQuality;
|
||||
|
||||
private void setFontQuality(float quality) {
|
||||
FontConfig.customFontQuality = (int) quality;
|
||||
if (qualityLast != (int) quality) {
|
||||
applyChanges(false);
|
||||
}
|
||||
qualityLast = (int) quality;
|
||||
}
|
||||
|
||||
private String fontAAModeFormat(float AAmode) {
|
||||
return switch ((int) AAmode) {
|
||||
case 2 -> I18n.format("options.angelica.fontconfig.aamode.aa_16x");
|
||||
case 1 -> I18n.format("options.angelica.fontconfig.aamode.aa_4x");
|
||||
default -> I18n.format("options.angelica.fontconfig.aamode.aa_none");
|
||||
};
|
||||
}
|
||||
|
||||
private float lastMouseX = 0;
|
||||
private float lastMouseY = 0;
|
||||
private long lastStillTime = 0;
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float delta) {
|
||||
drawBackground(0);
|
||||
|
||||
this.fontList.drawScreen(mouseX, mouseY, delta);
|
||||
if (availableFonts.length == 0) {
|
||||
if (this.noFontsWarning == null) {
|
||||
this.noFontsWarning = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.no_fonts_warning"), this.width / 2
|
||||
);
|
||||
}
|
||||
int y = 80;
|
||||
for (String s : this.noFontsWarning) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width / 3, y, 0xFFFFFF);
|
||||
y += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
this.searchBox.drawTextBox();
|
||||
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
|
||||
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
|
||||
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
|
||||
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
|
||||
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
|
||||
|
||||
if (this.testAreaInfo == null) {
|
||||
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
|
||||
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
|
||||
);
|
||||
}
|
||||
int cumulativeY = 73;
|
||||
for (String s : this.testAreaInfo) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
cumulativeY += 4;
|
||||
this.testArea.yPosition = cumulativeY;
|
||||
this.testArea.drawTextBox();
|
||||
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
|
||||
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
|
||||
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
|
||||
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
|
||||
}
|
||||
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
|
||||
this.testArea.getText().replace('&', '§'), this.width / 4
|
||||
);
|
||||
cumulativeY += this.testArea.height + 4;
|
||||
for (String s : testText) {
|
||||
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
|
||||
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
|
||||
}
|
||||
|
||||
super.drawScreen(mouseX, mouseY, delta);
|
||||
|
||||
for (GuiButton guiButton : buttonList) {
|
||||
if (!(guiButton instanceof SliderClone slider)) { continue; }
|
||||
final int top = slider.yPosition;
|
||||
final int bot = slider.yPosition + slider.height;
|
||||
final int left = slider.xPosition;
|
||||
final int right = slider.xPosition + slider.width;
|
||||
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
|
||||
if (mouseX == lastMouseX && mouseY == lastMouseY) {
|
||||
if (lastStillTime == 0) {
|
||||
lastStillTime = System.currentTimeMillis();
|
||||
}
|
||||
if (lastStillTime + 500L < System.currentTimeMillis()) {
|
||||
displayTooltip(mouseX, mouseY, slider.tooltipKey);
|
||||
}
|
||||
} else {
|
||||
lastStillTime = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
lastMouseX = mouseX;
|
||||
lastMouseY = mouseY;
|
||||
}
|
||||
|
||||
private void displayTooltip(int x, int y, String langKey) {
|
||||
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
|
||||
this.drawHoveringText(lines, x, y, this.fontRendererObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton guiButton) {
|
||||
if (guiButton.enabled) {
|
||||
if (guiButton instanceof IrisButton irisButton) {
|
||||
irisButton.onPress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char typedChar, int keyCode) {
|
||||
if (this.searchBox.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.searchBox.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.searchBox.textboxKeyTyped(typedChar, keyCode);
|
||||
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
|
||||
}
|
||||
if (this.testArea.isFocused()) {
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.testArea.setFocused(false);
|
||||
return;
|
||||
}
|
||||
this.testArea.textboxKeyTyped(typedChar, keyCode);
|
||||
}
|
||||
if (keyCode == Keyboard.KEY_ESCAPE) {
|
||||
this.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
this.searchBox.updateCursorCounter();
|
||||
this.testArea.updateCursorCounter();
|
||||
}
|
||||
|
||||
// this could be more efficient, but it's good enough as is
|
||||
private ArrayList<Font> filterFonts(String search) {
|
||||
ArrayList<Font> results;
|
||||
if (search == null || search.isEmpty()) {
|
||||
results = new ArrayList<>(Arrays.asList(availableFonts));
|
||||
} else {
|
||||
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
selectedPrimaryFontListPos = -1;
|
||||
selectedFallbackFontListPos = -1;
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
|
||||
selectedPrimaryFontListPos = i;
|
||||
}
|
||||
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
|
||||
selectedFallbackFontListPos = i;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
class FontList extends GuiSlot {
|
||||
|
||||
public FontList() {
|
||||
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
|
||||
}
|
||||
|
||||
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
if (mouseY < this.top || mouseY > this.bottom) { return; }
|
||||
|
||||
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
|
||||
int listRightBound = this.width / 2 + this.getListWidth() / 2;
|
||||
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
|
||||
|
||||
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
|
||||
int slotIndex = mousePosInList / this.slotHeight;
|
||||
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
|
||||
|
||||
if (mouseButton == 0) {
|
||||
onElemClicked(slotIndex, false);
|
||||
} else if (mouseButton == 1) {
|
||||
onElemClicked(slotIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onElemClicked(int index, boolean rightClick) {
|
||||
if (!rightClick) {
|
||||
selectedPrimaryFontListPos = index;
|
||||
currentPrimaryFontName = displayedFonts.get(index).getFontName();
|
||||
} else {
|
||||
selectedFallbackFontListPos = index;
|
||||
currentFallbackFontName = displayedFonts.get(index).getFontName();
|
||||
}
|
||||
applyChanges(false);
|
||||
}
|
||||
|
||||
protected boolean isSelected(int index) {
|
||||
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
|
||||
}
|
||||
|
||||
protected int getSize() {
|
||||
return displayedFonts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentHeight() {
|
||||
return this.getSize() * 18;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX() {
|
||||
return this.width * 15 / 16 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getListWidth() {
|
||||
return this.width * 2 / 3;
|
||||
}
|
||||
|
||||
// see onElemClicked for the proper version that supports right-clicking
|
||||
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
|
||||
|
||||
protected void drawBackground() {
|
||||
// do nothing, the font list doesn't occupy the entire screen
|
||||
}
|
||||
|
||||
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
|
||||
int color = 0xffffff;
|
||||
if (index == selectedPrimaryFontListPos) {
|
||||
color &= 0xffff55;
|
||||
}
|
||||
if (index == selectedFallbackFontListPos) {
|
||||
color &= 0x55ffff;
|
||||
}
|
||||
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.gtnewhorizons.angelica.client.gui;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SliderClone extends GuiButton {
|
||||
|
||||
private final Function<Float, String> displayStringFormatter;
|
||||
private final Consumer<Float> valueSetter;
|
||||
private final Option option;
|
||||
private float sliderPercentage;
|
||||
public final String tooltipKey;
|
||||
public boolean mouseDragging;
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class Option {
|
||||
float min;
|
||||
float max;
|
||||
float step;
|
||||
|
||||
float denormalize(float alpha) {
|
||||
float lerp = min * (1 - alpha) + max * alpha;
|
||||
return Math.round(lerp / step) * step;
|
||||
}
|
||||
float normalize(float value) {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
}
|
||||
|
||||
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
|
||||
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
|
||||
super(0, x, y, width, height, "");
|
||||
this.option = option;
|
||||
this.sliderPercentage = option.normalize(initialValue);
|
||||
this.displayString = formatter.apply(initialValue);
|
||||
this.valueSetter = setter;
|
||||
this.displayStringFormatter = formatter;
|
||||
this.tooltipKey = tooltipKey;
|
||||
}
|
||||
|
||||
public static SliderCloneBuilder builder() {
|
||||
return new SliderCloneBuilder();
|
||||
}
|
||||
|
||||
public void setCenterPosition(int x, int y) {
|
||||
this.xPosition = x - this.width / 2;
|
||||
this.yPosition = y - this.height / 2;
|
||||
}
|
||||
|
||||
@Accessors(fluent = true) @Setter
|
||||
public static class SliderCloneBuilder {
|
||||
private int x = 0, y = 0; // center position, not a corner
|
||||
private int width = 144, height = 20;
|
||||
private Option option;
|
||||
private float initialValue;
|
||||
private Consumer<Float> setter;
|
||||
private String formatString = "%3.2f";
|
||||
private String langKey;
|
||||
|
||||
// overrides: if null, these get set to default values
|
||||
private String tooltipKey;
|
||||
private Function<Float, String> formatter;
|
||||
|
||||
public SliderClone build() {
|
||||
if (this.formatter == null) {
|
||||
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
|
||||
}
|
||||
|
||||
if (this.tooltipKey == null) {
|
||||
this.tooltipKey = this.langKey + ".tooltip";
|
||||
}
|
||||
|
||||
return new SliderClone(
|
||||
this.x - this.width / 2,
|
||||
this.y - this.height / 2,
|
||||
this.width,
|
||||
this.height,
|
||||
this.option,
|
||||
this.initialValue,
|
||||
this.setter,
|
||||
this.formatter,
|
||||
this.tooltipKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
|
||||
* this button.
|
||||
*/
|
||||
public int getHoverState(boolean mouseOver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
|
||||
*/
|
||||
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (this.visible) {
|
||||
if (this.mouseDragging) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
}
|
||||
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
|
||||
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
|
||||
*/
|
||||
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (super.mousePressed(mc, mouseX, mouseY)) {
|
||||
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
|
||||
|
||||
if (this.sliderPercentage < 0.0F) {
|
||||
this.sliderPercentage = 0.0F;
|
||||
}
|
||||
|
||||
if (this.sliderPercentage > 1.0F) {
|
||||
this.sliderPercentage = 1.0F;
|
||||
}
|
||||
|
||||
float f = this.option.denormalize(this.sliderPercentage);
|
||||
valueSetter.accept(f);
|
||||
this.sliderPercentage = this.option.normalize(f);
|
||||
this.displayString = displayStringFormatter.apply(f);
|
||||
this.mouseDragging = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
|
||||
*/
|
||||
public void mouseReleased(int mouseX, int mouseY) {
|
||||
this.mouseDragging = false;
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.client.renderer;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.BlockPosImpl;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormat;
|
||||
import me.jellysquid.mods.sodium.client.model.quad.Quad;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vector3dStack;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ITessellatorInstance;
|
||||
import com.gtnewhorizons.angelica.utils.ObjectPooler;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import me.jellysquid.mods.sodium.client.render.pipeline.BlockRenderer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minecraft.util.MathHelper.clamp_int;
|
||||
|
||||
/*
|
||||
* To be used in conjunction with the TessellatorManager
|
||||
*
|
||||
* Used to capture the quads generated by the Tessellator across multiple draw calls and make the quad
|
||||
* list available for usage.
|
||||
*
|
||||
* NOTE: This will _not_ (currently) capture, integrate, or stop any GL calls made around the tessellator draw calls.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class CapturingTessellator extends Tessellator implements ITessellatorInstance {
|
||||
|
||||
// Access Transformers don't work on Forge Fields :rage:
|
||||
private static final MethodHandle sRawBufferSize;
|
||||
private static final MethodHandle gRawBufferSize;
|
||||
private final BlockRenderer.Flags FLAGS = new BlockRenderer.Flags(true, true, true, true);
|
||||
private final ObjectPooler<QuadView> quadBuf = new ObjectPooler<>(Quad::new);
|
||||
private final List<QuadView> collectedQuads = new ObjectArrayList<>();
|
||||
|
||||
// Any offset we need to the Tesselator's offset!
|
||||
private final BlockPosImpl offset = new BlockPosImpl();
|
||||
|
||||
private final Vector3dStack storedTranslation = new Vector3dStack();
|
||||
|
||||
public void setOffset(BlockPosImpl pos) {
|
||||
this.offset.set(pos);
|
||||
}
|
||||
public void resetOffset() {
|
||||
this.offset.zero();
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
final Field rbs = ReflectionHelper.findField(Tessellator.class, "rawBufferSize");
|
||||
rbs.setAccessible(true);
|
||||
try {
|
||||
sRawBufferSize = MethodHandles.lookup().unreflectSetter(rbs);
|
||||
gRawBufferSize = MethodHandles.lookup().unreflectGetter(rbs);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRawBufferSize(int size) {
|
||||
try {
|
||||
sRawBufferSize.invokeExact((Tessellator)this, size);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
public int getRawBufferSize() {
|
||||
try {
|
||||
return (int) gRawBufferSize.invokeExact((Tessellator)this);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int draw() {
|
||||
// Adapted from Neodymium
|
||||
|
||||
FLAGS.hasBrightness = this.hasBrightness;
|
||||
FLAGS.hasColor = this.hasColor;
|
||||
FLAGS.hasNormals = this.hasNormals;
|
||||
|
||||
final int verticesPerPrimitive = this.drawMode == GL11.GL_QUADS ? 4 : 3;
|
||||
|
||||
|
||||
for(int quadI = 0; quadI < this.vertexCount / verticesPerPrimitive; quadI++) {
|
||||
final QuadView quad = quadBuf.getInstance();
|
||||
quad.setState(this.rawBuffer, quadI * (verticesPerPrimitive * 8), FLAGS, this.drawMode, -offset.x, -offset.y, -offset.z);
|
||||
|
||||
if(quad.isDeleted()) {
|
||||
quadBuf.releaseInstance(quad);
|
||||
} else {
|
||||
this.collectedQuads.add(quad);
|
||||
}
|
||||
}
|
||||
|
||||
final int i = this.rawBufferIndex * 4;
|
||||
this.discard();
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discard() {
|
||||
isDrawing = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
public List<QuadView> getQuads() {
|
||||
return collectedQuads;
|
||||
}
|
||||
|
||||
public void clearQuads() {
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for(int i = 0; i < this.collectedQuads.size(); i++) {
|
||||
this.quadBuf.releaseInstance(this.collectedQuads.get(i));
|
||||
}
|
||||
this.collectedQuads.clear();
|
||||
}
|
||||
|
||||
public static ByteBuffer quadsToBuffer(List<QuadView> quads, VertexFormat format) {
|
||||
if(!format.canWriteQuads()) {
|
||||
throw new IllegalStateException("Vertex format has no quad writer: " + format);
|
||||
}
|
||||
final ByteBuffer byteBuffer = BufferUtils.createByteBuffer(format.getVertexSize() * quads.size() * 4);
|
||||
// noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0, quadsSize = quads.size(); i < quadsSize; i++) {
|
||||
format.writeQuad(quads.get(i), byteBuffer);
|
||||
}
|
||||
byteBuffer.rewind();
|
||||
return byteBuffer;
|
||||
}
|
||||
|
||||
public void storeTranslation() {
|
||||
storedTranslation.push();
|
||||
|
||||
this.storedTranslation.set(xOffset, yOffset, zOffset);
|
||||
}
|
||||
|
||||
public void restoreTranslation() {
|
||||
|
||||
xOffset = storedTranslation.x;
|
||||
yOffset = storedTranslation.y;
|
||||
zOffset = storedTranslation.z;
|
||||
storedTranslation.pop();
|
||||
}
|
||||
|
||||
public static int createBrightness(int sky, int block) {
|
||||
return sky << 20 | block << 4;
|
||||
}
|
||||
|
||||
// API from newer MC
|
||||
public CapturingTessellator pos(double x, double y, double z) {
|
||||
ensureBuffer();
|
||||
|
||||
this.rawBuffer[this.rawBufferIndex + 0] = Float.floatToRawIntBits((float)(x + this.xOffset));
|
||||
this.rawBuffer[this.rawBufferIndex + 1] = Float.floatToRawIntBits((float)(y + this.yOffset));
|
||||
this.rawBuffer[this.rawBufferIndex + 2] = Float.floatToRawIntBits((float)(z + this.zOffset));
|
||||
|
||||
return this;
|
||||
}
|
||||
public CapturingTessellator tex(double u, double v) {
|
||||
this.rawBuffer[this.rawBufferIndex + 3] = Float.floatToRawIntBits((float)u);
|
||||
this.rawBuffer[this.rawBufferIndex + 4] = Float.floatToRawIntBits((float)v);
|
||||
this.hasTexture = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CapturingTessellator color(float red, float green, float blue, float alpha) {
|
||||
return this.color((int)(red * 255.0F), (int)(green * 255.0F), (int)(blue * 255.0F), (int)(alpha * 255.0F));
|
||||
}
|
||||
|
||||
public CapturingTessellator color(int red, int green, int blue, int alpha) {
|
||||
if(this.isColorDisabled) return this;
|
||||
red = clamp_int(red, 0, 255);
|
||||
green = clamp_int(green, 0, 255);
|
||||
blue = clamp_int(blue, 0, 255);
|
||||
alpha = clamp_int(alpha, 0, 255);
|
||||
|
||||
final int color;
|
||||
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
|
||||
color = alpha << 24 | blue << 16 | green << 8 | red;
|
||||
} else {
|
||||
color = red << 24 | green << 16 | blue << 8 | alpha;
|
||||
}
|
||||
this.rawBuffer[this.rawBufferIndex + 5] = color;
|
||||
this.hasColor = true;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public CapturingTessellator normal(float x, float y, float z) {
|
||||
final byte b0 = (byte)((int)(x * 127.0F));
|
||||
final byte b1 = (byte)((int)(y * 127.0F));
|
||||
final byte b2 = (byte)((int)(z * 127.0F));
|
||||
|
||||
this.rawBuffer[this.rawBufferIndex + 6] = b0 & 255 | (b1 & 255) << 8 | (b2 & 255) << 16;
|
||||
this.hasNormals = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CapturingTessellator lightmap(int skyLight, int blockLight) {
|
||||
return brightness(createBrightness(skyLight, blockLight));
|
||||
}
|
||||
|
||||
public CapturingTessellator brightness(int brightness) {
|
||||
this.rawBuffer[this.rawBufferIndex + 7] = brightness;
|
||||
this.hasBrightness = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CapturingTessellator endVertex() {
|
||||
this.rawBufferIndex += 8;
|
||||
++this.vertexCount;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void ensureBuffer() {
|
||||
int rawBufferSize = getRawBufferSize();
|
||||
if (rawBufferIndex >= rawBufferSize - 32) {
|
||||
if (rawBufferSize == 0) {
|
||||
rawBufferSize = 0x10000;
|
||||
setRawBufferSize(rawBufferSize);
|
||||
rawBuffer = new int[rawBufferSize];
|
||||
} else {
|
||||
rawBufferSize *= 2;
|
||||
setRawBufferSize(rawBufferSize);
|
||||
rawBuffer = Arrays.copyOf(rawBuffer, rawBufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.gtnewhorizons.angelica.commands;
|
||||
|
||||
// Debug commands adapted from Beddium by Ven and FalsePattern
|
||||
|
||||
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AngelicaCommand extends CommandBase {
|
||||
|
||||
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "help");
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "angelica";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/angelica <wireframe|fog|minimap|help>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0; // Available to all players (client-side only)
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return getListOfStringsMatchingLastWord(args, SUBCOMMANDS.toArray(new String[0]));
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0) {
|
||||
sendHelp(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "wireframe" -> handleWireframe(sender);
|
||||
case "fog" -> handleFog(sender);
|
||||
case "minimap" -> handleMinimap(sender);
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWireframe(ICommandSender sender) {
|
||||
CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE = !CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE;
|
||||
final String state = CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Wireframe mode: " + state));
|
||||
}
|
||||
|
||||
private void handleFog(ICommandSender sender) {
|
||||
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
|
||||
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
|
||||
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
|
||||
}
|
||||
|
||||
private void handleMinimap(ICommandSender sender) {
|
||||
ChunkDebugMinimap.toggle();
|
||||
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private void sendHelp(ICommandSender sender) {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (o instanceof ICommand cmd) {
|
||||
return this.getCommandName().compareTo(cmd.getCommandName());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* This Block is not actually getting registered. A static instance is created in AngelicaMod.
|
||||
* Actually registering the block on the client but not the server can cause block IDs to
|
||||
* different between client/server, which is not good.
|
||||
|
||||
* We're essentially injecting the block's texture, so that we may use this block as if it were
|
||||
* any normal block for rendering purposes with RenderBlocks and such, but not have to actually
|
||||
* register it as a block.
|
||||
*/
|
||||
|
||||
public class BlockError extends Block {
|
||||
|
||||
public static final IIcon[] icons = new IIcon[2];
|
||||
|
||||
public BlockError() {
|
||||
super(Material.rock);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(new EventHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public IIcon getIcon(int side, int meta) {
|
||||
return meta >= 0 && meta < icons.length ? icons[meta] : null;
|
||||
}
|
||||
|
||||
public static class EventHandler{
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onTextureStitch(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() == 0) {
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.BlockPos;
|
||||
import com.gtnewhorizons.angelica.api.QuadProvider;
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.models.json.JsonModel;
|
||||
import com.gtnewhorizons.angelica.models.json.Loader;
|
||||
import com.gtnewhorizons.angelica.models.json.Variant;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;
|
||||
import static com.gtnewhorizon.gtnhlib.client.model.ModelISBRH.JSON_ISBRH_ID;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class BlockTest extends Block implements QuadProvider {
|
||||
|
||||
public static final Variant[] modelId = {
|
||||
new Variant(
|
||||
new ResourceLocation("blocks/lectern"),
|
||||
0,
|
||||
0,
|
||||
false
|
||||
),
|
||||
new Variant(
|
||||
new ResourceLocation("blocks/lectern"),
|
||||
180,
|
||||
0,
|
||||
false
|
||||
),
|
||||
new Variant(
|
||||
new ResourceLocation("blocks/lectern"),
|
||||
90,
|
||||
0,
|
||||
false
|
||||
),
|
||||
new Variant(
|
||||
new ResourceLocation("blocks/lectern"),
|
||||
270,
|
||||
0,
|
||||
false
|
||||
),
|
||||
};
|
||||
private static final List<QuadView> EMPTY = ObjectImmutableList.of();
|
||||
private static final JsonModel[] model = new JsonModel[4];
|
||||
/// Unline {@link BlockError}, this *is* registered on both sides. It's intended to test functionality in a SP
|
||||
/// development environment.
|
||||
public class BlockTest extends Block {
|
||||
|
||||
public BlockTest() {
|
||||
|
||||
super(Material.rock);
|
||||
}
|
||||
|
||||
public static void loadModel() {
|
||||
for (int i = 0; i < 4; ++i)
|
||||
model[i] = Loader.getModel(modelId[i]);
|
||||
super(Material.wood);
|
||||
setHardness(0.7f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,25 +22,6 @@ public class BlockTest extends Block implements QuadProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBlockIcons(IIconRegister reg) {
|
||||
|
||||
reg.registerIcon("angelica:test_block");
|
||||
reg.registerIcon("lectern_base");
|
||||
reg.registerIcon("lectern_front");
|
||||
reg.registerIcon("lectern_sides");
|
||||
reg.registerIcon("lectern_top");
|
||||
reg.registerIcon("oak_planks");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuadView> getQuads(IBlockAccess world, BlockPos pos, Block block, int meta, ForgeDirection dir, Random random, int color, Supplier<QuadView> quadPool) {
|
||||
|
||||
if (meta < 2 || meta > 5) meta = 2;
|
||||
|
||||
return (model[meta - 2] != null) ? model[meta - 2].getQuads(world, pos, block, meta, dir, random, color, quadPool) : EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
|
||||
*/
|
||||
@@ -92,11 +29,16 @@ public class BlockTest extends Block implements QuadProvider {
|
||||
public int onBlockPlaced(@NotNull World worldIn, int x, int y, int z, int side, float subX, float subY, float subZ, int meta) {
|
||||
|
||||
// Face NORTH if placed up or down
|
||||
final ForgeDirection s = ForgeDirection.values()[side];
|
||||
final var s = ForgeDirection.getOrientation(side);
|
||||
if (s == ForgeDirection.UP || s == ForgeDirection.DOWN)
|
||||
return 2;
|
||||
|
||||
// Face the placed side
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return JSON_ISBRH_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.ARBDebugOutput;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
* Checks for known problematic driver configurations and applies workarounds. Adapted from Embeddium/Sodium's LateDriverScanner.
|
||||
*
|
||||
* <p>The NVIDIA workaround can be disabled via: {@code -Dangelica.disableNvidiaWorkaround=true}
|
||||
*/
|
||||
public class DriverCompatabilityCheck {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
|
||||
/**
|
||||
* Should be called after GL context is created and GLStateManager is initialized.
|
||||
*/
|
||||
public static void checkDriverCompatibility() {
|
||||
final String vendor = GL11.glGetString(GL11.GL_VENDOR);
|
||||
final String renderer = GL11.glGetString(GL11.GL_RENDERER);
|
||||
final String version = GL11.glGetString(GL11.GL_VERSION);
|
||||
|
||||
LOGGER.info("OpenGL Vendor: {}", vendor);
|
||||
LOGGER.info("OpenGL Renderer: {}", renderer);
|
||||
LOGGER.info("OpenGL Version: {}", version);
|
||||
|
||||
if (vendor != null && vendor.contains("NVIDIA")) {
|
||||
if (DISABLE_NVIDIA_WORKAROUND) {
|
||||
LOGGER.info("NVIDIA workaround disabled via -Dangelica.disableNvidiaWorkaround=true");
|
||||
} else {
|
||||
applyNvidiaWorkaround();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/godotengine/godot/issues/33969#issuecomment-917846774
|
||||
* Attempt to force the driver to disable its submission thread by enabling synchronous debug output.
|
||||
* This usually suppresses bad behavior from NVIDIA's "Threaded Optimizations" feature.
|
||||
*/
|
||||
private static void applyNvidiaWorkaround() {
|
||||
if (!GLStateManager.capabilities.GL_ARB_debug_output) {
|
||||
LOGGER.warn("Cannot apply NVIDIA workaround: ARB_debug_output not available");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Enabling workaround for NVIDIA threaded optimizations");
|
||||
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.falsepattern.chunk.api.DataRegistry;
|
||||
import com.gtnewhorizons.angelica.mixins.early.sodium.MixinExtendedBlockStorage;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
|
||||
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
|
||||
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.NibbleArray;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
import ru.fewizz.idextender.Hooks;
|
||||
|
||||
public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
public boolean hasSky;
|
||||
@@ -19,7 +16,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
}
|
||||
|
||||
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
|
||||
super(((MixinExtendedBlockStorage) storage).getYBase(), storage.getSkylightArray() != null);
|
||||
super(storage.yBase, storage.getSkylightArray() != null);
|
||||
|
||||
if (ModStatus.isChunkAPILoaded) {
|
||||
if (storage.getSkylightArray() != null) {
|
||||
@@ -33,7 +30,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = block16BArray.length;
|
||||
if (ModStatus.isNEIDMetadataExtended) {
|
||||
@@ -41,42 +38,36 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
|
||||
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BMetaArray(), 0, block16BMetaArray, 0, block16BMetaArray.length);
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isOldNEIDLoaded){
|
||||
final short[] blockLSBArray = Hooks.get(this);
|
||||
System.arraycopy(Hooks.get(storage), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
// getBlockMSBArray is nuked in asm version
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
else {
|
||||
final byte[] blockLSBArray = this.getBlockLSBArray();
|
||||
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
|
||||
if(storage.getBlockMSBArray() != null) {
|
||||
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
|
||||
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
|
||||
}
|
||||
arrayLen = blockLSBArray.length;
|
||||
}
|
||||
|
||||
|
||||
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
|
||||
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
|
||||
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
|
||||
if(storage.getSkylightArray() != null) {
|
||||
hasSky = true;
|
||||
if(this.getSkylightArray() == null) {
|
||||
this.setSkylightArray(new NibbleArray(arrayLen, 4));
|
||||
}
|
||||
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
|
||||
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
|
||||
}
|
||||
}
|
||||
((MixinExtendedBlockStorage) this).setBlockRefCount(((MixinExtendedBlockStorage) storage).getBlockRefCount());
|
||||
this.blockRefCount = storage.blockRefCount;
|
||||
}
|
||||
|
||||
|
||||
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
|
||||
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
|
||||
if (srcArray == null || dstArray == null) {
|
||||
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
|
||||
}
|
||||
final byte[] data = srcArray.getData();
|
||||
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
|
||||
final byte[] data = srcArray.data;
|
||||
System.arraycopy(data, 0, dstArray.data, 0, data.length);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,62 @@
|
||||
package com.gtnewhorizons.angelica.compat;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.versioning.ArtifactVersion;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import net.dries007.holoInventory.HoloInventory;
|
||||
import mods.battlegear2.Battlegear;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class ModStatus {
|
||||
/**
|
||||
* Mixin Version
|
||||
*/
|
||||
public static boolean isNEIDLoaded;
|
||||
/**
|
||||
* ASM Version
|
||||
*/
|
||||
public static boolean isOldNEIDLoaded;
|
||||
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
|
||||
|
||||
public static boolean isBetterCrashesLoaded;
|
||||
public static boolean isNEIDLoaded;
|
||||
public static boolean isNEIDMetadataExtended;
|
||||
public static boolean isLotrLoaded;
|
||||
public static boolean isChunkAPILoaded;
|
||||
public static boolean isEIDBiomeLoaded;
|
||||
public static boolean isXaerosMinimapLoaded;
|
||||
public static boolean isHoloInventoryLoaded;
|
||||
public static boolean isBattlegearLoaded;
|
||||
public static boolean isBackhandLoaded;
|
||||
public static boolean isThaumcraftLoaded;
|
||||
public static boolean isThaumicHorizonsLoaded;
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
|
||||
public static void preInit() {
|
||||
if (Loader.isModLoaded("backhand")) {
|
||||
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
|
||||
}
|
||||
|
||||
public static void preInit(){
|
||||
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
|
||||
isNEIDLoaded = Loader.isModLoaded("neid");
|
||||
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
|
||||
isLotrLoaded = Loader.isModLoaded("lotr");
|
||||
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
|
||||
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
|
||||
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
|
||||
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
|
||||
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
|
||||
isBaublesLoaded = Loader.isModLoaded("Baubles");
|
||||
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
|
||||
|
||||
if (isHoloInventoryLoaded){
|
||||
isHoloInventoryLoaded = new DefaultArtifactVersion("2.4.4-GTNH")
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
|
||||
// remove compat with original release of BG2
|
||||
if (isBattlegearLoaded){
|
||||
isBattlegearLoaded = new DefaultArtifactVersion("1.2.0")
|
||||
.compareTo(
|
||||
LoadControllerHelper.getOwningMod(HoloInventory.class).getProcessedVersion()
|
||||
LoadControllerHelper.getOwningMod(Battlegear.class).getProcessedVersion()
|
||||
) <= 0;
|
||||
}
|
||||
|
||||
|
||||
isNEIDMetadataExtended = false;
|
||||
if (isNEIDLoaded) {
|
||||
int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
|
||||
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
|
||||
if (majorVersion >= 2) {
|
||||
isNEIDMetadataExtended = true;
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.compat.backhand;
|
||||
|
||||
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class BackhandReflectionCompat {
|
||||
private static final String BACKHAND_CLASS = "xonin.backhand.Backhand";
|
||||
private static final String ITEM_RENDERER_HOOKS_CLASS = "xonin.backhand.client.hooks.ItemRendererHooks";
|
||||
private static final String BACKHAND_UTILS_CLASS = "xonin.backhand.api.core.BackhandUtils";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderOffhandReturn;
|
||||
private static final MethodHandle getOffhandItem;
|
||||
|
||||
static {
|
||||
MethodHandle renderOffhandReturnTemp = null;
|
||||
MethodHandle getOffhandItemTemp = null;
|
||||
|
||||
try {
|
||||
final Class<?> backhandClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_CLASS);
|
||||
final boolean versionCheck = new DefaultArtifactVersion("1.6.9").compareTo(LoadControllerHelper.getOwningMod(backhandClass).getProcessedVersion()) <= 0;
|
||||
|
||||
if (versionCheck) {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
final Class<?> itemRendererHooksClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), ITEM_RENDERER_HOOKS_CLASS);
|
||||
final Class<?> backhandUtilsClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_UTILS_CLASS);
|
||||
|
||||
renderOffhandReturnTemp = lookup.findStatic(itemRendererHooksClass, "renderOffhandReturn", MethodType.methodType(void.class, float.class));
|
||||
getOffhandItemTemp = lookup.findStatic(backhandUtilsClass, "getOffhandItem", MethodType.methodType(ItemStack.class, EntityPlayer.class));
|
||||
isLoaded = true;
|
||||
LOGGER.info("Backhand compat loaded");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load Backhand compat", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderOffhandReturn = renderOffhandReturnTemp;
|
||||
getOffhandItem = getOffhandItemTemp;
|
||||
}
|
||||
|
||||
public static boolean isBackhandLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
public static void renderOffhand(float partialTicks) {
|
||||
if (isLoaded && renderOffhandReturn != null) {
|
||||
try {
|
||||
renderOffhandReturn.invokeExact(partialTicks);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack getOffhandItem(EntityPlayer player) {
|
||||
if (isLoaded && getOffhandItem != null) {
|
||||
try {
|
||||
return (ItemStack) getOffhandItem.invokeExact(player);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.compat.battlegear2;
|
||||
|
||||
import mods.battlegear2.api.core.IInventoryPlayerBattle;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* This class exists to isolate classes that exist in Battlegear 2 but not
|
||||
* in Battlegear 2 for Backhand in order to avoid class loading exceptions.
|
||||
*/
|
||||
public class Battlegear2Compat {
|
||||
public static ItemStack getBattlegear2Offhand(EntityPlayer player) {
|
||||
return ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ public class BetterCrashesCompat implements StateManager.IResettable {
|
||||
@Override
|
||||
public void resetState() {
|
||||
LOGGER.info("Reloading SodiumRenderer");
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).reload();
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
|
||||
LOGGER.info("Resetting GLStateManager");
|
||||
GLStateManager.reset();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.compat.endlessids;
|
||||
|
||||
import com.falsepattern.endlessids.mixin.helpers.ChunkBiomeHook;
|
||||
import lombok.val;
|
||||
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import static com.falsepattern.endlessids.constants.ExtendedConstants.biomeIDMask;
|
||||
import static com.falsepattern.endlessids.constants.ExtendedConstants.biomeIDNull;
|
||||
|
||||
public class EndlessIDsCompat {
|
||||
public static void sodium$populateBiomes(Chunk chunk) {
|
||||
if (!(chunk instanceof ChunkBiomeHook hook))
|
||||
return;
|
||||
|
||||
val biomes = hook.getBiomeShortArray();
|
||||
if (biomes == null)
|
||||
return;
|
||||
|
||||
val cX = chunk.xPosition << 4;
|
||||
val cZ = chunk.zPosition << 4;
|
||||
|
||||
val manager = chunk.worldObj.getWorldChunkManager();
|
||||
for(var z = 0; z < 16; z++) {
|
||||
for(var x = 0; x < 16; x++) {
|
||||
val idx = (z << 4) + x;
|
||||
val biome = biomes[idx] & biomeIDMask;
|
||||
if(biome == biomeIDNull) {
|
||||
val generated = manager.getBiomeGenAt(cX + x, cZ + z);
|
||||
if (generated == null)
|
||||
continue;
|
||||
biomes[idx] = (short)(generated.biomeID & biomeIDMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.gtnewhorizons.angelica.compat.holoinventory;
|
||||
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
|
||||
|
||||
public class HoloInventoryReflectionCompat {
|
||||
private static final String RENDERER_CLASS = "net.dries007.holoInventory.client.Renderer";
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static final MethodHandle renderEventPostMethod;
|
||||
private static final MethodHandle renderEventMethod;
|
||||
private static final MethodHandle angelicaOverrideSetter;
|
||||
|
||||
static {
|
||||
MethodHandle renderEventPostMethodTemp = null;
|
||||
MethodHandle renderEventMethodTemp = null;
|
||||
MethodHandle angelicaOverrideSetterTemp = null;
|
||||
final Object rendererInstance;
|
||||
final Class<?> rendererClass;
|
||||
|
||||
try {
|
||||
rendererClass = Class.forName(RENDERER_CLASS);
|
||||
|
||||
// Get and cache the Renderer.INSTANCE
|
||||
final Field instanceField = rendererClass.getDeclaredField("INSTANCE");
|
||||
instanceField.setAccessible(true);
|
||||
rendererInstance = instanceField.get(null);
|
||||
|
||||
final MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
try {
|
||||
renderEventPostMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.Post.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Fallback for 2.5.0-GTNH and earlier
|
||||
renderEventMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.class));
|
||||
}
|
||||
|
||||
final Field angelicaOverrideField = rendererClass.getDeclaredField("angelicaOverride");
|
||||
angelicaOverrideField.setAccessible(true);
|
||||
angelicaOverrideSetterTemp = lookup.unreflectSetter(angelicaOverrideField);
|
||||
|
||||
// Bind the method handles to the instance
|
||||
if (rendererInstance != null) {
|
||||
if (renderEventPostMethodTemp != null) {
|
||||
renderEventPostMethodTemp = renderEventPostMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
if (renderEventMethodTemp != null) {
|
||||
renderEventMethodTemp = renderEventMethodTemp.bindTo(rendererInstance);
|
||||
}
|
||||
angelicaOverrideSetterTemp = angelicaOverrideSetterTemp.bindTo(rendererInstance);
|
||||
}
|
||||
|
||||
isLoaded = true;
|
||||
LOGGER.info("Successfully initialized HoloInventory compatibility layer");
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to initialize HoloInventory compatibility layer", e);
|
||||
isLoaded = false;
|
||||
}
|
||||
|
||||
renderEventPostMethod = renderEventPostMethodTemp;
|
||||
renderEventMethod = renderEventMethodTemp;
|
||||
angelicaOverrideSetter = angelicaOverrideSetterTemp;
|
||||
}
|
||||
|
||||
public static void renderEvent(RenderGameOverlayEvent event) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
if (event instanceof RenderGameOverlayEvent.Post && renderEventPostMethod != null) {
|
||||
renderEventPostMethod.invokeExact((RenderGameOverlayEvent.Post) event);
|
||||
} else {
|
||||
renderEventMethod.invokeExact(event);
|
||||
}
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAngelicaOverride(boolean value) {
|
||||
if (!isLoaded) return;
|
||||
try {
|
||||
angelicaOverrideSetter.invokeExact(value);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
|
||||
isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
/**
|
||||
* Interface implemented by BiomeGenBase via mixin to cache biome category.
|
||||
* This avoids recomputing the category every frame for shader uniforms.
|
||||
*/
|
||||
public interface BiomeCategoryCache {
|
||||
/**
|
||||
* Gets the cached biome category ordinal, or -1 if not yet computed.
|
||||
*/
|
||||
int iris$getCachedCategory();
|
||||
|
||||
/**
|
||||
* Sets the cached biome category ordinal.
|
||||
*/
|
||||
void iris$setCachedCategory(int category);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.gtnewhorizons.angelica.compat.iris;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import net.coderbot.iris.parsing.BiomeCategories;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Detects and categorizes biomes from popular modded biome mods using cached class checks.
|
||||
* Currently supports: BiomesOPlenty (BOP), Realistic World Gen (RWG), lotr (b36.15)
|
||||
*/
|
||||
public class ModdedBiomeDetector {
|
||||
private static boolean initialized = false;
|
||||
|
||||
// BiomesOPlenty (BOP) cache - dimension-specific base classes
|
||||
// BOP 1.7.10 hierarchy: BOPBiome -> BOPEndBiome/BOPNetherBiome/BOPOverworldBiome -> specific biomes
|
||||
private static Class<?> bopEndBiomeClass = null;
|
||||
private static Class<?> bopNetherBiomeClass = null;
|
||||
private static Class<?> bopOceanBiomeClass = null;
|
||||
|
||||
// Realistic World Gen (RWG) cache - base biome classes that extend BiomeGenBase
|
||||
// LinkedHashMap maintains insertion order for deterministic behavior
|
||||
private static final Map<Class<?>, BiomeCategories> rwgClassMap = new LinkedHashMap<>();
|
||||
|
||||
private static Class<?> lotrBiomeClass = null;
|
||||
|
||||
private static synchronized void initialize() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
initializeBOP();
|
||||
initializeRWG();
|
||||
initializeLOTR();
|
||||
}
|
||||
|
||||
private static void initializeBOP() {
|
||||
try {
|
||||
bopEndBiomeClass = Class.forName("biomesoplenty.common.biome.BOPEndBiome");
|
||||
bopNetherBiomeClass = Class.forName("biomesoplenty.common.biome.BOPNetherBiome");
|
||||
bopOceanBiomeClass = Class.forName("biomesoplenty.common.biome.BOPOceanBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// BOP not installed
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeRWG() {
|
||||
// RWG adds actual BiomeGenBase subclasses in rwg.biomes.base package
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeOcean", BiomeCategories.OCEAN);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRiver", BiomeCategories.RIVER);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomePlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotPlains", BiomeCategories.PLAINS);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotDesert", BiomeCategories.DESERT);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowDesert", BiomeCategories.DESERT); // Cold desert, but still desert
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeJungle", BiomeCategories.JUNGLE);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTropicalIsland", BiomeCategories.JUNGLE); // Tropical islands are jungle-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeRedwood", BiomeCategories.FOREST); // Redwood/Sequoia forests
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeTemperateForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdForest", BiomeCategories.TAIGA); // Cold forests are taiga-like
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotForest", BiomeCategories.FOREST);
|
||||
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowForest", BiomeCategories.TAIGA); // Snow forests are taiga
|
||||
}
|
||||
|
||||
private static void initializeLOTR() {
|
||||
if (ModStatus.isLotrLoaded) {
|
||||
try {
|
||||
lotrBiomeClass = Class.forName("lotr.common.world.biome.LOTRBiome");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// lotr not installed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryAddRWGClass(String className, BiomeCategories category) {
|
||||
try {
|
||||
final Class<?> clazz = Class.forName(className);
|
||||
rwgClassMap.put(clazz, category);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Class not available, RWG not installed or different version
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the biome category for modded biomes.
|
||||
* Returns null if no modded biome detection applies.
|
||||
*/
|
||||
public static BiomeCategories detectModdedBiome(BiomeGenBase biome) {
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// BiomesOPlenty detection - dimension-specific classes give us clear categories
|
||||
if (bopEndBiomeClass != null && bopEndBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.THE_END;
|
||||
}
|
||||
if (bopNetherBiomeClass != null && bopNetherBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.NETHER;
|
||||
}
|
||||
if (bopOceanBiomeClass != null && bopOceanBiomeClass.isInstance(biome)) {
|
||||
return BiomeCategories.OCEAN;
|
||||
}
|
||||
// For other BOP overworld biomes (e.g., Alps, Arctic, BambooForest),
|
||||
// fall through to name/property-based detection
|
||||
|
||||
// Realistic World Gen detection
|
||||
if (biome.getBiomeClass().getName().contains("rwg.biomes")) {
|
||||
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
|
||||
if (entry.getKey().isInstance(biome)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lotr
|
||||
if (lotrBiomeClass!= null && lotrBiomeClass.isInstance(biome)) {
|
||||
return detectLOTRBiome(biome);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BiomeCategories detectLOTRBiome(BiomeGenBase biome) {
|
||||
return switch (biome.biomeName) {
|
||||
case "river", "farHaradJungleLake", "lake" -> BiomeCategories.RIVER;
|
||||
case "rohan", "rivendell", "rhunIsland", "rhunLandHills", "rhunLandSteppe", "rhunLand", "andrast",
|
||||
"blackrootVale", "lamedon", "imlothMelui", "towerHills", "dorwinion", "dale", "shireMoors", "wold",
|
||||
"anduinVale", "rhun", "lebennin", "pertorogwaith", "breeland", "island", "pinnathGelin",
|
||||
"minhiriath", "pelennor", "adornland", "nanCurunir", "ithilienWasteland", "fangornClearing",
|
||||
"barrowDowns", "dorEnErnilHills", "dorEnErnil", "nurn", "wilderland", "eastBight", "lindon",
|
||||
"eregion", "enedwaith", "meneltarma", "anduinHills", "eriador", "angle", "loneLandsHills",
|
||||
"loneLands", "pelargir", "rohanUrukHighlands", "celebrant", "gondor", "shire", "nurnen", "farHarad" ->
|
||||
BiomeCategories.PLAINS;
|
||||
case "mistyMountains", "windMountains", "farHaradVolcano", "redMountains", "haradMountains",
|
||||
"angmarMountains", "blueMountains", "greyMountains", "whiteMountains", "mordorMountains" ->
|
||||
BiomeCategories.MOUNTAIN;
|
||||
case "shireWoodlands", "rhunIslandForest", "rhunRedForest", "pukel", "lossarnach", "gulfHaradForest",
|
||||
"nearHaradFertileForest", "dolGuldur", "rhunForest", "tauredainClearing", "umbarForest",
|
||||
"farHaradForest", "chetwood", "woodlandRealmHills", "mirkwoodNorth", "whiteDowns", "ithilienHills",
|
||||
"gondorWoodlands", "rohanWoodlands", "fangornWasteland", "mirkwoodMountains", "lindonWoodlands",
|
||||
"lothlorienEdge", "erynVorn", "oldForest", "fangorn", "dunland", "ithilien", "mirkwoodCorrupted",
|
||||
"woodlandRealm", "trollshaws", "lothlorien", "farHaradKanuka" -> BiomeCategories.FOREST;
|
||||
case "mordor", "lastDesert", "harnedor", "easternDesolation", "morgulVale", "gorgoroth", "udun",
|
||||
"nearHaradRedDesert", "nearHaradSemiDesert", "lostladen",
|
||||
"nearHaradHills", "nearHarad", "nanUngol", "dagorlad", "brownLands" -> BiomeCategories.DESERT;
|
||||
case "ironHills", "rivendellHills", "windMountainsFoothills", "lamedonHills", "redMountainsFoothills",
|
||||
"tolfalas", "dorwinionHills", "whiteMountainsFoothills", "blueMountainsFoothills",
|
||||
"greyMountainsFoothills", "mistyMountainsFoothills", "erebor", "angmar", "eriadorDowns",
|
||||
"ettenmoors", "emynMuil" -> BiomeCategories.EXTREME_HILLS;
|
||||
case "deadMarshes", "shireMarshes", "farHaradMangrove", "farHaradSwamp", "swanfleet", "nindalf",
|
||||
"longMarshes", "entwashMouth", "nurnMarshes", "gladdenFields", "midgewater", "anduinMouth" ->
|
||||
BiomeCategories.SWAMP;
|
||||
case "harondor", "nearHaradOasis", "nearHaradRiverbank", "farHaradBushlandHills", "farHaradBushland",
|
||||
"gulfHarad", "nearHaradFertile", "umbarHills", "umbar", "farHaradAridHills", "farHaradArid" ->
|
||||
BiomeCategories.SAVANNA;
|
||||
case "ocean" -> BiomeCategories.OCEAN;
|
||||
case "forodwaith", "forodwaithGlacier", "tundra", "forodwaithMountains" -> BiomeCategories.ICY;
|
||||
case "lindonCoast", "beachWhite", "farHaradCoast", "forodwaithCoast", "beachGravel", "beach" ->
|
||||
BiomeCategories.BEACH;
|
||||
case "coldfells", "wilderlandNorth", "taiga" -> BiomeCategories.TAIGA;
|
||||
case "farHaradJungle", "halfTrollForest", "farHaradJungleMountains",
|
||||
"farHaradCloudForest", "farHaradJungleEdge" -> BiomeCategories.JUNGLE;
|
||||
case "utumno" -> BiomeCategories.NONE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.lwjgl;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class CompatMemoryUtil {
|
||||
public static ByteBuffer memReallocDirect(ByteBuffer old, int capacity) {
|
||||
ByteBuffer newBuf = BufferUtils.createByteBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static IntBuffer memReallocDirect(IntBuffer old, int capacity) {
|
||||
IntBuffer newBuf = BufferUtils.createIntBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
public static FloatBuffer memReallocDirect(FloatBuffer old, int capacity) {
|
||||
FloatBuffer newBuf = BufferUtils.createFloatBuffer(capacity);
|
||||
int oldPos = old.position();
|
||||
old.rewind();
|
||||
newBuf.put(old);
|
||||
newBuf.position(Math.min(capacity, oldPos));
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backported from LWJGL3 under the BSD 3-Clause "New" or "Revised" License license
|
||||
*
|
||||
* <p>This class provides functionality for managing native memory.
|
||||
*
|
||||
* <p>All methods in this class will make use of {@link sun.misc.Unsafe} if it's available, for performance. If Unsafe is not available, the fallback
|
||||
* implementations make use of reflection and, in the worst-case, JNI.</p>
|
||||
*
|
||||
* <p>Method names in this class are prefixed with {@code mem} to avoid ambiguities when used with static imports.</p>
|
||||
*/
|
||||
|
||||
static final sun.misc.Unsafe UNSAFE;
|
||||
|
||||
static {
|
||||
UNSAFE = getUnsafeInstance();
|
||||
}
|
||||
|
||||
private static sun.misc.Unsafe getUnsafeInstance() {
|
||||
java.lang.reflect.Field[] fields = sun.misc.Unsafe.class.getDeclaredFields();
|
||||
|
||||
/*
|
||||
Different runtimes use different names for the Unsafe singleton,
|
||||
so we cannot use .getDeclaredField and we scan instead. For example:
|
||||
|
||||
Oracle: theUnsafe
|
||||
PERC : m_unsafe_instance
|
||||
Android: THE_ONE
|
||||
*/
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
if (!field.getType().equals(sun.misc.Unsafe.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int modifiers = field.getModifiers();
|
||||
if (!(java.lang.reflect.Modifier.isStatic(modifiers) && java.lang.reflect.Modifier.isFinal(modifiers))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return (sun.misc.Unsafe)field.get(null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("LWJGL requires sun.misc.Unsafe to be available.");
|
||||
}
|
||||
|
||||
|
||||
public static void memPutByte(long ptr, byte value) { UNSAFE.putByte(null, ptr, value); }
|
||||
public static void memPutShort(long ptr, short value) { UNSAFE.putShort(null, ptr, value); }
|
||||
public static void memPutInt(long ptr, int value) { UNSAFE.putInt(null, ptr, value); }
|
||||
public static void memPutLong(long ptr, long value) { UNSAFE.putLong(null, ptr, value); }
|
||||
public static void memPutFloat(long ptr, float value) { UNSAFE.putFloat(null, ptr, value); }
|
||||
public static void memPutDouble(long ptr, double value) { UNSAFE.putDouble(null, ptr, value); }
|
||||
|
||||
public static boolean memGetBoolean(long ptr) { return UNSAFE.getByte(null, ptr) != 0; }
|
||||
public static byte memGetByte(long ptr) { return UNSAFE.getByte(null, ptr); }
|
||||
public static short memGetShort(long ptr) { return UNSAFE.getShort(null, ptr); }
|
||||
public static int memGetInt(long ptr) { return UNSAFE.getInt(null, ptr); }
|
||||
public static long memGetLong(long ptr) { return UNSAFE.getLong(null, ptr); }
|
||||
public static float memGetFloat(long ptr) { return UNSAFE.getFloat(null, ptr); }
|
||||
public static double memGetDouble(long ptr) { return UNSAFE.getDouble(null, ptr); }
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
public enum Axis {
|
||||
X,
|
||||
Y,
|
||||
Z;
|
||||
|
||||
public static Axis fromDirection(ModelQuadFacing dir) {
|
||||
return switch (dir) {
|
||||
case DOWN, UP -> Y;
|
||||
case NORTH, SOUTH -> Z;
|
||||
case WEST, EAST -> X;
|
||||
case UNASSIGNED -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static Axis fromDirection(ForgeDirection dir) {
|
||||
return switch (dir) {
|
||||
case DOWN, UP -> Y;
|
||||
case NORTH, SOUTH -> Z;
|
||||
case WEST, EAST -> X;
|
||||
case UNKNOWN -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static Axis fromName(String dir) {
|
||||
return switch (dir) {
|
||||
case "y" -> Y;
|
||||
case "z" -> Z;
|
||||
case "x" -> X;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
POSITIVE,
|
||||
NEGATIVE;
|
||||
|
||||
public static Direction fromDirection(ForgeDirection dir) {
|
||||
return switch (dir) {
|
||||
case DOWN, NORTH, WEST -> NEGATIVE;
|
||||
case UP, SOUTH, EAST -> POSITIVE;
|
||||
case UNKNOWN -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.BlockPos;
|
||||
import com.gtnewhorizons.angelica.api.MutableBlockPos;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import static com.gtnewhorizons.angelica.api.BlockPos.unpackLongX;
|
||||
import static com.gtnewhorizons.angelica.api.BlockPos.unpackLongY;
|
||||
import static com.gtnewhorizons.angelica.api.BlockPos.unpackLongZ;
|
||||
|
||||
// Should we keep this?
|
||||
public class BlockPosImpl extends Vector3i implements MutableBlockPos {
|
||||
|
||||
public BlockPosImpl() {
|
||||
super();
|
||||
}
|
||||
public BlockPosImpl(int x, int y, int z) {
|
||||
super(x, y, z);
|
||||
}
|
||||
|
||||
public BlockPosImpl(ChunkPosition chunkPosition) {
|
||||
super(chunkPosition.chunkPosX, chunkPosition.chunkPosY, chunkPosition.chunkPosZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
@Override
|
||||
public int getY() {
|
||||
return this.y;
|
||||
}
|
||||
@Override
|
||||
public int getZ() {
|
||||
return this.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPosImpl offset(ForgeDirection d) {
|
||||
return new BlockPosImpl(this.x + d.offsetX, this.y + d.offsetY, this.z + d.offsetZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPosImpl down() {
|
||||
return offset(ForgeDirection.DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPosImpl up() {
|
||||
return offset(ForgeDirection.UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asLong() {
|
||||
return BlockPos.asLong(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPosImpl set(int x, int y, int z) {
|
||||
super.set(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPosImpl set(long packedPos) {
|
||||
return set(unpackLongX(packedPos), unpackLongY(packedPos), unpackLongZ(packedPos));
|
||||
}
|
||||
}
|
||||
+1
@@ -15,6 +15,7 @@ public class ByteBufferBackedInputStream extends InputStream {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -8,35 +9,65 @@ import net.minecraft.util.MathHelper;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Singleton camera instance that tracks the current camera position and orientation.
|
||||
*/
|
||||
@Getter
|
||||
public class Camera {
|
||||
final Vector3d pos = new Vector3d();
|
||||
final BlockPosImpl blockPos = new BlockPosImpl();
|
||||
float pitch;
|
||||
float yaw;
|
||||
EntityLivingBase entity;
|
||||
boolean thirdPerson;
|
||||
final float partialTicks;
|
||||
public static final Camera INSTANCE = new Camera();
|
||||
|
||||
public Camera(EntityLivingBase entity, float partialTicks) {
|
||||
private final Vector3d pos = new Vector3d();
|
||||
private final Vector3d entityPos = new Vector3d();
|
||||
private final BlockPos blockPos = new BlockPos();
|
||||
private final Vector3f offset = new Vector3f();
|
||||
private final Matrix4f inverseModelView = new Matrix4f();
|
||||
|
||||
private float pitch;
|
||||
private float yaw;
|
||||
private EntityLivingBase entity;
|
||||
private boolean thirdPerson;
|
||||
private float partialTicks;
|
||||
|
||||
private Camera() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the camera position and orientation based on the current entity and partial ticks.
|
||||
*
|
||||
* @param entity The entity to track (usually the render view entity)
|
||||
* @param partialTicks The partial tick time for interpolation
|
||||
*/
|
||||
public void update(EntityLivingBase entity, float partialTicks) {
|
||||
this.partialTicks = partialTicks;
|
||||
final Vector4f offset = new Vector4f(); // third person offset
|
||||
final Matrix4f inverseModelView = new Matrix4f(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transform(offset);
|
||||
|
||||
final double camX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + offset.x;
|
||||
final double camY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + offset.y;
|
||||
final double camZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + offset.z;
|
||||
this.entity = entity;
|
||||
|
||||
// Entity position (interpolated, at eye level)
|
||||
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
|
||||
final double entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
|
||||
final double entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
|
||||
final double eyeHeight = entity.getEyeHeight();
|
||||
|
||||
entityPos.set(entityX, entityY + eyeHeight, entityZ);
|
||||
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
|
||||
|
||||
final double camX, camY, camZ;
|
||||
offset.set(0, 0, 0);
|
||||
|
||||
if (thirdPerson) {
|
||||
// Third person: use inverse modelview to find camera offset
|
||||
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
|
||||
inverseModelView.transformPosition(offset);
|
||||
}
|
||||
camX = entityX + offset.x;
|
||||
camY = entityY + eyeHeight + offset.y;
|
||||
camZ = entityZ + offset.z;
|
||||
|
||||
pos.set(camX, camY, camZ);
|
||||
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
|
||||
pitch = entity.cameraPitch;
|
||||
yaw = entity.rotationYaw;
|
||||
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.ints.IntPriorityQueue;
|
||||
import me.jellysquid.mods.sodium.common.util.DirectionUtil;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import java.util.BitSet;
|
||||
@@ -17,7 +17,7 @@ public class ChunkOcclusionDataBuilder {
|
||||
private static final int STEP_X = (int)Math.pow(16.0, 0.0);
|
||||
private static final int STEP_Z = (int)Math.pow(16.0, 1.0);
|
||||
private static final int STEP_Y = (int)Math.pow(16.0, 2.0);
|
||||
private static final ForgeDirection[] DIRECTIONS = DirectionUtil.ALL_DIRECTIONS;
|
||||
private static final ForgeDirection[] DIRECTIONS = ForgeDirection.VALID_DIRECTIONS;
|
||||
private final BitSet closed = new BitSet(4096);
|
||||
private static final int[] EDGE_POINTS = new int[1352];
|
||||
|
||||
@@ -36,12 +36,12 @@ public class ChunkOcclusionDataBuilder {
|
||||
}
|
||||
private int openCount = 4096;
|
||||
|
||||
public void markClosed(BlockPosImpl pos) {
|
||||
public void markClosed(BlockPos pos) {
|
||||
this.closed.set(pack(pos), true);
|
||||
--this.openCount;
|
||||
}
|
||||
|
||||
private static int pack(BlockPosImpl pos) {
|
||||
private static int pack(BlockPos pos) {
|
||||
return pack(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
|
||||
// See if we can merge/mixin/extend ChunkCoordIntPair?
|
||||
public class ChunkPos {
|
||||
public static long INT_MASK = (1L << Integer.SIZE) - 1;
|
||||
@@ -12,7 +14,7 @@ public class ChunkPos {
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public ChunkPos(BlockPosImpl pos) {
|
||||
public ChunkPos(BlockPos pos) {
|
||||
this.x = pos.getX() >> 4;
|
||||
this.z = pos.getZ() >> 4;
|
||||
}
|
||||
@@ -38,6 +40,7 @@ public class ChunkPos {
|
||||
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int i = 1664525 * this.x + 1013904223;
|
||||
@@ -45,6 +48,7 @@ public class ChunkPos {
|
||||
return i ^ j;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
// See if we can merge/mixin/extend ChunkPosition maybe?
|
||||
@@ -19,7 +20,7 @@ public class ChunkSectionPos extends Vector3i {
|
||||
return new ChunkSectionPos(x, y, z);
|
||||
}
|
||||
|
||||
public static ChunkSectionPos from(BlockPosImpl pos) {
|
||||
public static ChunkSectionPos from(BlockPos pos) {
|
||||
return new ChunkSectionPos(getSectionCoord(pos.getX()), getSectionCoord(pos.getY()), getSectionCoord(pos.getZ()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement.Type;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.compat.nd.writers.ItemVBOQuadWriter;
|
||||
import com.gtnewhorizons.angelica.compat.nd.writers.PositionColorTextureQuadWriter;
|
||||
import com.gtnewhorizons.angelica.compat.nd.writers.PositionQuadWriter;
|
||||
import com.gtnewhorizons.angelica.compat.nd.writers.PositionTextureColorQuadWriter;
|
||||
import com.gtnewhorizons.angelica.compat.nd.writers.PositionTextureQuadWriter;
|
||||
|
||||
public class DefaultVertexFormat {
|
||||
|
||||
public static final VertexFormatElement POSITION_ELEMENT = new VertexFormatElement(0, Type.FLOAT, Usage.POSITION, 3);
|
||||
public static final VertexFormatElement COLOR_ELEMENT = new VertexFormatElement(0, Type.UBYTE, Usage.COLOR, 4);
|
||||
public static final VertexFormatElement TEXTURE_ELEMENT = new VertexFormatElement(0, Type.FLOAT, Usage.UV, 2);
|
||||
public static final VertexFormatElement LIGHT_ELEMENT = new VertexFormatElement(1, Type.SHORT, Usage.UV, 2);
|
||||
// TODO: The overlay element index is wrong, need to find a 1.7.10 equivalent
|
||||
public static final VertexFormatElement OVERLAY_ELEMENT = new VertexFormatElement(1, Type.SHORT, Usage.UV, 2);
|
||||
public static final VertexFormatElement NORMAL_ELEMENT = new VertexFormatElement(0, Type.BYTE, Usage.NORMAL, 3);
|
||||
public static final VertexFormatElement PADDING_ELEMENT = new VertexFormatElement(0, Type.BYTE, Usage.PADDING, 1);
|
||||
public static final VertexFormat POSITION_TEXTURE_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build(), new ItemVBOQuadWriter());
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE_LIGHT_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_ELEMENT).add(LIGHT_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_ELEMENT).add(OVERLAY_ELEMENT).add(LIGHT_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR_LIGHT = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).add(COLOR_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).build(), new PositionQuadWriter());
|
||||
public static final VertexFormat POSITION_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_COLOR_LIGHT = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).build(), new PositionTextureQuadWriter());
|
||||
public static final VertexFormat POSITION_COLOR_TEXTURE = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_ELEMENT).build(), new PositionColorTextureQuadWriter());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).add(COLOR_ELEMENT).build(), new PositionTextureColorQuadWriter());
|
||||
public static final VertexFormat POSITION_COLOR_TEX_LIGHTMAP = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(COLOR_ELEMENT).add(TEXTURE_ELEMENT).add(LIGHT_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_LIGHT_COLOR = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).add(LIGHT_ELEMENT).add(COLOR_ELEMENT).build());
|
||||
public static final VertexFormat POSITION_TEXTURE_COLOR_NORMAL = new VertexFormat(new ImmutableList.Builder<VertexFormatElement>().add(POSITION_ELEMENT).add(TEXTURE_ELEMENT).add(COLOR_ELEMENT).add(NORMAL_ELEMENT).add(PADDING_ELEMENT).build());
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.PlayerControllerMP;
|
||||
|
||||
public final class GameModeUtil {
|
||||
private GameModeUtil() {
|
||||
}
|
||||
|
||||
public static boolean isSpectator() {
|
||||
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
return controller.currentGameType.getID() == 3;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public enum InteractionHand {
|
||||
MAIN_HAND,
|
||||
OFF_HAND;
|
||||
|
||||
InteractionHand() {
|
||||
public ItemStack getItemInHand(EntityPlayer player) {
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
|
||||
return BackhandReflectionCompat.getOffhandItem(player);
|
||||
} else { // main hand
|
||||
return player.getHeldItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
// TBD
|
||||
public class NativeImage extends BufferedImage {
|
||||
@@ -59,13 +59,20 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
|
||||
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(size);
|
||||
int[] data = new int[size];
|
||||
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
|
||||
buffer.get(data);
|
||||
setRGB(0, 0, width, height, data, 0, width);
|
||||
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
|
||||
|
||||
try {
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
|
||||
|
||||
int[] data = new int[size];
|
||||
buffer.get(data);
|
||||
setRGB(0, 0, width, height, data, 0, width);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToFile(File file) throws IOException{
|
||||
try {
|
||||
ImageIO.write(this, "png", file);
|
||||
@@ -103,18 +110,15 @@ public class NativeImage extends BufferedImage {
|
||||
|
||||
|
||||
public enum Format {
|
||||
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
|
||||
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
|
||||
RGBA(4, GL11.GL_RGBA),
|
||||
RGB(3, GL11.GL_RGB);
|
||||
|
||||
private final int components;
|
||||
private final int glFormat;
|
||||
private final int imageType;
|
||||
|
||||
|
||||
Format(int components, int glFormat, int imageType) {
|
||||
Format(int components, int glFormat) {
|
||||
this.components = components;
|
||||
this.glFormat = glFormat;
|
||||
this.imageType = imageType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
|
||||
public class VertexBuffer implements AutoCloseable {
|
||||
private int id;
|
||||
private int vertexCount;
|
||||
private VertexFormat format;
|
||||
private int drawMode;
|
||||
|
||||
public VertexBuffer() {
|
||||
this.id = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
public VertexBuffer(VertexFormat format, int drawMode) {
|
||||
this();
|
||||
this.format = format;
|
||||
this.drawMode = drawMode;
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.id);
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public void upload(ByteBuffer buffer, int vertexCount) {
|
||||
if (this.id == -1) return;
|
||||
this.vertexCount = vertexCount;
|
||||
this.bind();
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
|
||||
this.unbind();
|
||||
}
|
||||
|
||||
public VertexBuffer upload(ByteBuffer buffer) {
|
||||
if(format == null) throw new IllegalStateException("No format specified for VBO upload");
|
||||
upload(buffer, buffer.remaining() / format.getVertexSize());
|
||||
return this;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.id >= 0) {
|
||||
GL15.glDeleteBuffers(this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(FloatBuffer floatBuffer) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glMultMatrix(floatBuffer);
|
||||
draw();
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
GL11.glDrawArrays(drawMode, 0, this.vertexCount);
|
||||
}
|
||||
|
||||
public void setupState() {
|
||||
if(format == null) throw new IllegalStateException("No format specified for VBO setup");
|
||||
bind();
|
||||
format.setupBufferState(0L);
|
||||
}
|
||||
|
||||
public void cleanupState() {
|
||||
format.clearBufferState();
|
||||
unbind();
|
||||
}
|
||||
public void render() {
|
||||
setupState();
|
||||
draw();
|
||||
cleanupState();
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.vertices.ImmediateState;
|
||||
import net.coderbot.iris.vertices.IrisVertexFormats;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public class VertexFormat {
|
||||
@Getter
|
||||
protected final ImmutableList<VertexFormatElement> elements;
|
||||
protected final IntList offsets = new IntArrayList();
|
||||
@Getter
|
||||
protected final int vertexSize;
|
||||
|
||||
protected IWriteQuads quadWriter;
|
||||
|
||||
public VertexFormat(ImmutableList<VertexFormatElement> elements) {
|
||||
this(elements, null);
|
||||
}
|
||||
|
||||
public VertexFormat(ImmutableList<VertexFormatElement> elements, IWriteQuads quadWriter) {
|
||||
this.elements = elements;
|
||||
int i = 0;
|
||||
for (VertexFormatElement element : elements) {
|
||||
offsets.add(i);
|
||||
i += element.getByteSize();
|
||||
}
|
||||
vertexSize = i;
|
||||
this.quadWriter = quadWriter;
|
||||
}
|
||||
|
||||
public void setupBufferState(long l) {
|
||||
if (BlockRenderingSettings.INSTANCE.shouldUseExtendedVertexFormat() && ImmediateState.renderWithExtendedVertexFormat) {
|
||||
if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL || this == DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP) {
|
||||
IrisVertexFormats.TERRAIN.setupBufferState(l);
|
||||
return;
|
||||
} else if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL) {
|
||||
IrisVertexFormats.ENTITY.setupBufferState(l);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final int i = this.getVertexSize();
|
||||
final List<VertexFormatElement> list = this.getElements();
|
||||
final int listSize = list.size();
|
||||
|
||||
for(int j = 0; j < listSize; ++j) {
|
||||
list.get(j).setupBufferState(l + this.offsets.getInt(j), i);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBufferState() {
|
||||
if (BlockRenderingSettings.INSTANCE.shouldUseExtendedVertexFormat() && ImmediateState.renderWithExtendedVertexFormat) {
|
||||
if (this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL || this == DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP) {
|
||||
IrisVertexFormats.TERRAIN.clearBufferState();
|
||||
return;
|
||||
} else if ( this == DefaultVertexFormat.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL) {
|
||||
IrisVertexFormats.ENTITY.clearBufferState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
final List<VertexFormatElement> list = this.getElements();
|
||||
final int listSize = list.size();
|
||||
|
||||
for(int i = 0 ; i < listSize; ++i) {
|
||||
list.get(i).clearBufferState();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canWriteQuads() {
|
||||
return quadWriter != null;
|
||||
}
|
||||
|
||||
public void writeQuad(QuadView quad, ByteBuffer byteBuffer) {
|
||||
if(quadWriter == null) {
|
||||
throw new IllegalStateException("No quad writer set");
|
||||
}
|
||||
quadWriter.writeQuad(quad, byteBuffer);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
|
||||
@Getter
|
||||
public class VertexFormatElement {
|
||||
protected final Type type;
|
||||
protected final Usage usage;
|
||||
protected final int index;
|
||||
protected final int count;
|
||||
protected final int byteSize;
|
||||
|
||||
public VertexFormatElement(int index, Type type, Usage usage, int count) {
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this.usage = usage;
|
||||
this.count = count;
|
||||
this.byteSize = type.getSize() * count;
|
||||
}
|
||||
|
||||
public void setupBufferState(long offset, int stride) {
|
||||
this.usage.setupBufferState(this.count, this.type.getGlType(), stride, offset, this.index);
|
||||
}
|
||||
|
||||
public void clearBufferState() {
|
||||
this.usage.clearBufferState(this.index);
|
||||
}
|
||||
|
||||
public enum Usage {
|
||||
POSITION("Position", (size, type, stride, pointer, index) -> {
|
||||
GL11.glVertexPointer(size, type, stride, pointer);
|
||||
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
}, index -> GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY)),
|
||||
NORMAL("Normal", (size, type, stride, pointer, index) -> {
|
||||
GL11.glNormalPointer(type, stride, pointer);
|
||||
GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
|
||||
}, index -> GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY)),
|
||||
COLOR("Vertex Color", (size, type, stride, pointer, index) -> {
|
||||
GL11.glColorPointer(size, type, stride, pointer);
|
||||
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
}, index -> GL11.glDisableClientState(GL11.GL_COLOR_ARRAY)),
|
||||
UV("UV", (size, type, stride, pointer, index) -> {
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0 + index);
|
||||
GL11.glTexCoordPointer(size, type, stride, pointer);
|
||||
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
}, index -> {
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0 + index);
|
||||
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
|
||||
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
}),
|
||||
PADDING("Padding", (size, type, stride, pointer, index) -> {}, index-> {}),
|
||||
GENERIC("Generic", (size, type, stride, pointer, index) -> {
|
||||
GL20.glEnableVertexAttribArray(index);
|
||||
GL20.glVertexAttribPointer(index, size, type, false, stride, pointer);
|
||||
}, GL20::glDisableVertexAttribArray);
|
||||
|
||||
@Getter private final String name;
|
||||
private final SetupState setupState;
|
||||
private final IntConsumer clearState;
|
||||
|
||||
Usage(String name, SetupState setupState, IntConsumer clearState) {
|
||||
this.name = name;
|
||||
this.setupState = setupState;
|
||||
this.clearState = clearState;
|
||||
}
|
||||
|
||||
private void setupBufferState(int size, int type, int stride, long pointer, int index) {
|
||||
this.setupState.setupBufferState(size, type, stride, pointer, index);
|
||||
}
|
||||
|
||||
public void clearBufferState(int index) {
|
||||
this.clearState.accept(index);
|
||||
}
|
||||
|
||||
interface SetupState {
|
||||
void setupBufferState(int size, int type, int stride, long pointer, int index);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum Type {
|
||||
FLOAT(4, "Float", GL11.GL_FLOAT),
|
||||
UBYTE(1, "Unsigned Byte", GL11.GL_UNSIGNED_BYTE),
|
||||
BYTE(1, "Byte", GL11.GL_BYTE),
|
||||
USHORT(2, "Unsigned Short", GL11.GL_UNSIGNED_SHORT),
|
||||
SHORT(2, "Short", GL11.GL_SHORT),
|
||||
UINT(4, "Unsigned Int", GL11.GL_UNSIGNED_INT),
|
||||
INT(4, "Int", GL11.GL_INT);
|
||||
|
||||
private final int size;
|
||||
private final String name;
|
||||
private final int glType;
|
||||
|
||||
Type(int size, String name, int glType) {
|
||||
this.size = size;
|
||||
this.name = name;
|
||||
this.glType = glType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class BufferWriter {
|
||||
// Adapted from Neodymium
|
||||
|
||||
private final ByteBuffer buf;
|
||||
|
||||
private final FloatBuffer floatBuffer;
|
||||
private final ShortBuffer shortBuffer;
|
||||
private final IntBuffer intBuffer;
|
||||
|
||||
public BufferWriter(ByteBuffer buf) {
|
||||
this.buf = buf;
|
||||
this.floatBuffer = buf.asFloatBuffer();
|
||||
this.shortBuffer = buf.asShortBuffer();
|
||||
this.intBuffer = buf.asIntBuffer();
|
||||
}
|
||||
|
||||
private void incrementPosition(int add) {
|
||||
buf.position(buf.position() + add);
|
||||
floatBuffer.position(buf.position() / 4);
|
||||
shortBuffer.position(buf.position() / 2);
|
||||
intBuffer.position(buf.position() / 4);
|
||||
}
|
||||
|
||||
public void writeFloat(float x) {
|
||||
try {
|
||||
floatBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
} catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeInt(int x) {
|
||||
intBuffer.put(x);
|
||||
|
||||
incrementPosition(4);
|
||||
}
|
||||
|
||||
public void writeByte(byte x) {
|
||||
buf.put(x); // this increments the buffer position by 1
|
||||
|
||||
incrementPosition(0);
|
||||
}
|
||||
|
||||
public int position() {
|
||||
return buf.position();
|
||||
}
|
||||
|
||||
public void writeShort(short s) {
|
||||
shortBuffer.put(s);
|
||||
|
||||
incrementPosition(2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface IWriteQuads {
|
||||
void writeQuad(QuadView quad, ByteBuffer buf);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ItemVBOQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public ItemVBOQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
|
||||
// Normals + Padding
|
||||
buf.putInt(quad.getNormal(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionColorTextureQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionColorTextureQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Color
|
||||
buf.putInt(quad.getColor(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionTextureColorQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionTextureColorQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
|
||||
// Color
|
||||
buf.putInt(quad.getColor(idx));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.nd.writers;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.QuadView;
|
||||
import com.gtnewhorizons.angelica.compat.nd.IWriteQuads;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PositionTextureQuadWriter implements IWriteQuads {
|
||||
private boolean direct;
|
||||
|
||||
public PositionTextureQuadWriter() {
|
||||
init(false);
|
||||
}
|
||||
|
||||
public void init(boolean direct) {
|
||||
// This would need to be re-initialized if the direct vs indirect changes
|
||||
this.direct = direct;
|
||||
}
|
||||
@Override
|
||||
public void writeQuad(QuadView quad, ByteBuffer buf) {
|
||||
if(direct) {
|
||||
writeQuadDirect(quad, buf);
|
||||
} else {
|
||||
writeQuadIndirect(quad, buf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
|
||||
throw new UnsupportedOperationException("Direct mode not supported yet");
|
||||
}
|
||||
|
||||
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
|
||||
for(int idx = 0; idx < 4; ++idx) {
|
||||
// Position
|
||||
buf.putFloat(quad.getX(idx));
|
||||
buf.putFloat(quad.getY(idx));
|
||||
buf.putFloat(quad.getZ(idx));
|
||||
|
||||
// Texture
|
||||
buf.putFloat(quad.getTexU(idx));
|
||||
buf.putFloat(quad.getTexV(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormat;
|
||||
import lombok.Getter;
|
||||
|
||||
@Deprecated
|
||||
public class DrawState {
|
||||
@Getter
|
||||
private final VertexFormat format;
|
||||
@Getter
|
||||
private final int vertexCount;
|
||||
@Getter
|
||||
private final int mode;
|
||||
|
||||
public DrawState(VertexFormat format, int vertexCount, int mode) {
|
||||
this.format = format;
|
||||
this.vertexCount = vertexCount;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,10 +34,17 @@ public class MatrixStack {
|
||||
public void pop() {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
public boolean clear() {
|
||||
public boolean isEmpty() {
|
||||
return this.matrixStack.size() == 1;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
while (this.matrixStack.size() > 1) {
|
||||
this.matrixStack.removeLast();
|
||||
}
|
||||
loadIdentity();
|
||||
}
|
||||
|
||||
public void translate(double d, double e, double f) {
|
||||
final Entry lv = (Entry)this.matrixStack.getLast();
|
||||
lv.model.translate((float)d, (float)e, (float)f);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
@Deprecated
|
||||
public class OverlayTexture {
|
||||
|
||||
// This is definitely wrong
|
||||
public static final int NO_OVERLAY = 0;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.VertexFormat;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
||||
import lombok.Getter;
|
||||
@@ -58,7 +60,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
|
||||
public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) {
|
||||
return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).texturing(OUTLINE_TEXTURING).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE));
|
||||
return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE));
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
@@ -70,7 +72,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private final MultiPhaseParameters phases;
|
||||
private final int hash;
|
||||
private final Optional<RenderLayer> affectedOutline;
|
||||
private final boolean outline;
|
||||
|
||||
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
|
||||
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
|
||||
@@ -82,7 +83,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
|
||||
return getOutline(arg2, phases.cull);
|
||||
}) : Optional.empty();
|
||||
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
|
||||
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
|
||||
}
|
||||
|
||||
@@ -95,20 +95,23 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this.affectedOutline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
return this == object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RenderType[" + this.phases + ']';
|
||||
}
|
||||
|
||||
static {
|
||||
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
|
||||
@@ -140,17 +143,15 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private final RenderPhase.DepthTest depthTest;
|
||||
private final RenderPhase.Cull cull;
|
||||
private final RenderPhase.Lightmap lightmap;
|
||||
private final RenderPhase.Overlay overlay;
|
||||
private final RenderPhase.Fog fog;
|
||||
private final RenderPhase.Layering layering;
|
||||
private final RenderPhase.Target target;
|
||||
private final RenderPhase.Texturing texturing;
|
||||
private final RenderPhase.WriteMaskState writeMaskState;
|
||||
private final RenderPhase.LineWidth lineWidth;
|
||||
private final OutlineMode outlineMode;
|
||||
private final ImmutableList<RenderPhase> phases;
|
||||
|
||||
private MultiPhaseParameters(RenderPhase.Texture texture, RenderPhase.Transparency transparency, RenderPhase.DiffuseLighting diffuseLighting, RenderPhase.ShadeModel shadeModel, RenderPhase.Alpha alpha, RenderPhase.DepthTest depthTest, RenderPhase.Cull cull, RenderPhase.Lightmap lightmap, RenderPhase.Overlay overlay, RenderPhase.Fog fog, RenderPhase.Layering layering, RenderPhase.Target target, RenderPhase.Texturing texturing, RenderPhase.WriteMaskState writeMaskState, RenderPhase.LineWidth lineWidth, OutlineMode outlineMode) {
|
||||
private MultiPhaseParameters(RenderPhase.Texture texture, RenderPhase.Transparency transparency, RenderPhase.DiffuseLighting diffuseLighting, RenderPhase.ShadeModel shadeModel, RenderPhase.Alpha alpha, RenderPhase.DepthTest depthTest, RenderPhase.Cull cull, RenderPhase.Lightmap lightmap, RenderPhase.Fog fog, RenderPhase.Layering layering, RenderPhase.Target target, RenderPhase.Texturing texturing, RenderPhase.WriteMaskState writeMaskState, OutlineMode outlineMode) {
|
||||
this.texture = texture;
|
||||
this.transparency = transparency;
|
||||
this.diffuseLighting = diffuseLighting;
|
||||
@@ -159,17 +160,16 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.depthTest = depthTest;
|
||||
this.cull = cull;
|
||||
this.lightmap = lightmap;
|
||||
this.overlay = overlay;
|
||||
this.fog = fog;
|
||||
this.layering = layering;
|
||||
this.target = target;
|
||||
this.texturing = texturing;
|
||||
this.writeMaskState = writeMaskState;
|
||||
this.lineWidth = lineWidth;
|
||||
this.outlineMode = outlineMode;
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.overlay, this.fog, this.layering, this.target, new RenderPhase[]{this.texturing, this.writeMaskState, this.lineWidth});
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -181,10 +181,12 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(new Object[]{this.phases, this.outlineMode});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
|
||||
}
|
||||
@@ -202,13 +204,11 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private RenderPhase.DepthTest depthTest;
|
||||
private RenderPhase.Cull cull;
|
||||
private RenderPhase.Lightmap lightmap;
|
||||
private RenderPhase.Overlay overlay;
|
||||
private RenderPhase.Fog fog;
|
||||
private RenderPhase.Layering layering;
|
||||
private RenderPhase.Target target;
|
||||
private RenderPhase.Texturing texturing;
|
||||
private RenderPhase.WriteMaskState writeMaskState;
|
||||
private RenderPhase.LineWidth lineWidth;
|
||||
|
||||
private Builder() {
|
||||
this.texture = RenderPhase.NO_TEXTURE ;
|
||||
@@ -219,13 +219,11 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.depthTest = RenderPhase.LEQUAL_DEPTH_TEST;
|
||||
this.cull = RenderPhase.ENABLE_CULLING;
|
||||
this.lightmap = RenderPhase.DISABLE_LIGHTMAP;
|
||||
this.overlay = RenderPhase.DISABLE_OVERLAY_COLOR;
|
||||
this.fog = RenderPhase.FOG;
|
||||
this.layering = RenderPhase.NO_LAYERING;
|
||||
this.target = RenderPhase.MAIN_TARGET;
|
||||
this.texturing = RenderPhase.DEFAULT_TEXTURING;
|
||||
this.writeMaskState = RenderPhase.ALL_MASK;
|
||||
this.lineWidth = RenderPhase.FULL_LINE_WIDTH;
|
||||
}
|
||||
|
||||
public Builder texture(RenderPhase.Texture texture) {
|
||||
@@ -263,11 +261,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder overlay(RenderPhase.Overlay overlay) {
|
||||
this.overlay = overlay;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fog(RenderPhase.Fog fog) {
|
||||
this.fog = fog;
|
||||
return this;
|
||||
@@ -288,7 +281,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
|
||||
public MultiPhaseParameters build(OutlineMode outlineMode) {
|
||||
return new MultiPhaseParameters(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.overlay, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, this.lineWidth, outlineMode);
|
||||
return new MultiPhaseParameters(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, outlineMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,6 +297,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.compat.toremove;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import me.jellysquid.mods.sodium.client.SodiumClientMod;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL11;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
|
||||
public abstract class RenderPhase {
|
||||
@@ -64,13 +63,8 @@ public abstract class RenderPhase {
|
||||
protected static final Texture BLOCK_ATLAS_TEXTURE;
|
||||
protected static final Texture NO_TEXTURE;
|
||||
protected static final Texturing DEFAULT_TEXTURING;
|
||||
protected static final Texturing OUTLINE_TEXTURING;
|
||||
protected static final Texturing GLINT_TEXTURING;
|
||||
protected static final Texturing ENTITY_GLINT_TEXTURING;
|
||||
protected static final Lightmap ENABLE_LIGHTMAP;
|
||||
protected static final Lightmap DISABLE_LIGHTMAP;
|
||||
protected static final Overlay ENABLE_OVERLAY_COLOR;
|
||||
protected static final Overlay DISABLE_OVERLAY_COLOR;
|
||||
protected static final DiffuseLighting ENABLE_DIFFUSE_LIGHTING;
|
||||
protected static final DiffuseLighting DISABLE_DIFFUSE_LIGHTING;
|
||||
protected static final Cull ENABLE_CULLING;
|
||||
@@ -82,19 +76,12 @@ public abstract class RenderPhase {
|
||||
protected static final WriteMaskState COLOR_MASK;
|
||||
protected static final WriteMaskState DEPTH_MASK;
|
||||
protected static final Layering NO_LAYERING;
|
||||
protected static final Layering POLYGON_OFFSET_LAYERING;
|
||||
protected static final Layering VIEW_OFFSET_Z_LAYERING;
|
||||
protected static final Fog NO_FOG;
|
||||
protected static final Fog FOG;
|
||||
protected static final Fog BLACK_FOG;
|
||||
protected static final Target MAIN_TARGET;
|
||||
protected static final Target OUTLINE_TARGET;
|
||||
protected static final Target TRANSLUCENT_TARGET;
|
||||
protected static final Target PARTICLES_TARGET;
|
||||
protected static final Target WEATHER_TARGET;
|
||||
protected static final Target CLOUDS_TARGET;
|
||||
protected static final Target ITEM_TARGET;
|
||||
protected static final LineWidth FULL_LINE_WIDTH;
|
||||
|
||||
public RenderPhase(String name, Runnable beginAction, Runnable endAction) {
|
||||
this.name = name;
|
||||
@@ -110,6 +97,7 @@ public abstract class RenderPhase {
|
||||
this.endAction.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
@@ -121,28 +109,16 @@ public abstract class RenderPhase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
private static void setupGlintTexturing(float scale) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// long l = Util.getMeasuringTimeMs() * 8L;
|
||||
// float g = (float)(l % 110000L) / 110000.0F;
|
||||
// float h = (float)(l % 30000L) / 30000.0F;
|
||||
// RenderSystem.translatef(-g, h, 0.0F);
|
||||
// RenderSystem.rotatef(10.0F, 0.0F, 0.0F, 1.0F);
|
||||
// RenderSystem.scalef(scale, scale, scale);
|
||||
// RenderSystem.matrixMode(5888);
|
||||
}
|
||||
|
||||
private static ResourceLocation ATLAS = TextureMap.locationBlocksTexture;
|
||||
static {
|
||||
// TODO: Sodium - SpriteAtlasTexture
|
||||
@@ -151,34 +127,9 @@ public abstract class RenderPhase {
|
||||
NO_TEXTURE = new Texture();
|
||||
DEFAULT_TEXTURING = new Texturing("default_texturing", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
OUTLINE_TEXTURING = new Texturing("outline_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.setupOutline();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.teardownOutline();
|
||||
});
|
||||
GLINT_TEXTURING = new Texturing("glint_texturing", () -> {
|
||||
setupGlintTexturing(8.0F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
ENTITY_GLINT_TEXTURING = new Texturing("entity_glint_texturing", () -> {
|
||||
setupGlintTexturing(0.16F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
ENABLE_LIGHTMAP = new Lightmap(true);
|
||||
DISABLE_LIGHTMAP = new Lightmap(false);
|
||||
ENABLE_OVERLAY_COLOR = new Overlay(true);
|
||||
DISABLE_OVERLAY_COLOR = new Overlay(false);
|
||||
ENABLE_DIFFUSE_LIGHTING = new DiffuseLighting(true);
|
||||
DISABLE_DIFFUSE_LIGHTING = new DiffuseLighting(false);
|
||||
ENABLE_CULLING = new Cull(true);
|
||||
@@ -189,33 +140,14 @@ public abstract class RenderPhase {
|
||||
ALL_MASK = new WriteMaskState(true, true);
|
||||
COLOR_MASK = new WriteMaskState(true, false);
|
||||
DEPTH_MASK = new WriteMaskState(false, true);
|
||||
NO_LAYERING = new Layering("no_layering", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
POLYGON_OFFSET_LAYERING = new Layering("polygon_offset_layering", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.polygonOffset(-1.0F, -10.0F);
|
||||
// RenderSystem.enablePolygonOffset();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.polygonOffset(0.0F, 0.0F);
|
||||
// RenderSystem.disablePolygonOffset();
|
||||
});
|
||||
VIEW_OFFSET_Z_LAYERING = new Layering("view_offset_z_layering", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.scalef(0.99975586F, 0.99975586F, 0.99975586F);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem::popMatrix();
|
||||
});
|
||||
NO_LAYERING = new Layering("no_layering", () -> {}, () -> {});
|
||||
NO_FOG = new Fog("no_fog", () -> {
|
||||
}, () -> {
|
||||
});
|
||||
FOG = new Fog("fog", () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.enableFog();
|
||||
}, GLStateManager::disableFog);
|
||||
BLACK_FOG = new Fog("black_fog", () -> {
|
||||
@@ -224,7 +156,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
// Unclear what this should do
|
||||
// BackgroundRenderer.setFogBlack(), also levelFogColor()
|
||||
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
|
||||
GLStateManager.disableFog();
|
||||
});
|
||||
MAIN_TARGET = new Target("main_target", () -> {
|
||||
@@ -232,7 +164,7 @@ public abstract class RenderPhase {
|
||||
});
|
||||
OUTLINE_TARGET = new Target("outline_target", () -> {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
|
||||
//MinecraftClient.getInstance().worldRenderer.getEntityOutlinesFramebuffer().beginWrite(false);
|
||||
}, () -> {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
@@ -240,7 +172,7 @@ public abstract class RenderPhase {
|
||||
TRANSLUCENT_TARGET = new Target("translucent_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
// TODO: Sodium
|
||||
SodiumClientMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
AngelicaMod.LOGGER.debug("NOT enabling the translucent framebuffer");
|
||||
// MinecraftClient.getInstance().worldRenderer.getTranslucentFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
@@ -250,103 +182,6 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
});
|
||||
PARTICLES_TARGET = new Target("particles_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getParticlesFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
WEATHER_TARGET = new Target("weather_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getWeatherFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
CLOUDS_TARGET = new Target("clouds_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getCloudsFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
ITEM_TARGET = new Target("item_entity_target", () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().worldRenderer.getEntityFramebuffer().beginWrite(false);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (Minecraft.isFancyGraphicsEnabled()) {
|
||||
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
|
||||
});
|
||||
FULL_LINE_WIDTH = new LineWidth(OptionalDouble.of(1.0));
|
||||
}
|
||||
|
||||
|
||||
public static class LineWidth extends RenderPhase {
|
||||
private final OptionalDouble width;
|
||||
|
||||
public LineWidth(OptionalDouble optionalDouble) {
|
||||
super("line_width", () -> {
|
||||
if (!Objects.equals(optionalDouble, OptionalDouble.of(1.0))) {
|
||||
if (optionalDouble.isPresent()) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth((float)optionalDouble.getAsDouble());
|
||||
} else {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth(Math.max(2.5F, (float)MinecraftClient.getInstance().getWindow().getFramebufferWidth() / 1920.0F * 2.5F));
|
||||
}
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (!Objects.equals(optionalDouble, OptionalDouble.of(1.0))) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.lineWidth(1.0F);
|
||||
}
|
||||
|
||||
});
|
||||
this.width = optionalDouble;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
return !super.equals(object) ? false : Objects.equals(this.width, ((LineWidth)object).width);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), this.width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name + '[' + (this.width.isPresent() ? this.width.getAsDouble() : "window_scale") + ']';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -505,31 +340,12 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
|
||||
public static class Overlay extends Toggleable {
|
||||
public Overlay(boolean overlayColor) {
|
||||
super("overlay", () -> {
|
||||
if (overlayColor) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getOverlayTexture().setupOverlayColor();
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
if (overlayColor) {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getOverlayTexture().teardownOverlayColor();
|
||||
}
|
||||
|
||||
}, overlayColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Lightmap extends Toggleable {
|
||||
public Lightmap(boolean lightmap) {
|
||||
super("lightmap", () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - enable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().enable();
|
||||
|
||||
@@ -538,7 +354,7 @@ public abstract class RenderPhase {
|
||||
}, () -> {
|
||||
if (lightmap) {
|
||||
// TODO: Sodium - LightmapTextureManager
|
||||
SodiumClientMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
AngelicaMod.LOGGER.debug("Lightmap - disable (not implemented)");
|
||||
// throw new RuntimeException("Not Implemented Yet");
|
||||
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().disable();
|
||||
}
|
||||
@@ -580,93 +396,6 @@ public abstract class RenderPhase {
|
||||
}
|
||||
|
||||
|
||||
public static final class PortalTexturing extends Texturing {
|
||||
private final int layer;
|
||||
|
||||
public PortalTexturing(int layer) {
|
||||
super("portal_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// RenderSystem.translatef(0.5F, 0.5F, 0.0F);
|
||||
// RenderSystem.scalef(0.5F, 0.5F, 1.0F);
|
||||
// RenderSystem.translatef(17.0F / (float)layer, (2.0F + (float)layer / 1.5F) * ((float)(Util.getMeasuringTimeMs() % 800000L) / 800000.0F), 0.0F);
|
||||
// RenderSystem.rotatef(((float)(layer * layer) * 4321.0F + (float)layer * 9.0F) * 2.0F, 0.0F, 0.0F, 1.0F);
|
||||
// RenderSystem.scalef(4.5F - (float)layer / 4.0F, 4.5F - (float)layer / 4.0F, 1.0F);
|
||||
// RenderSystem.mulTextureByProjModelView();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
// RenderSystem.setupEndPortalTexGen();
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
// RenderSystem.clearTexGen();
|
||||
});
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
PortalTexturing lv = (PortalTexturing)object;
|
||||
return this.layer == lv.layer;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(this.layer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final class OffsetTexturing extends Texturing {
|
||||
private final float x;
|
||||
private final float y;
|
||||
|
||||
public OffsetTexturing(float x, float y) {
|
||||
super("offset_texturing", () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.pushMatrix();
|
||||
// RenderSystem.loadIdentity();
|
||||
// RenderSystem.translatef(x, y, 0.0F);
|
||||
// RenderSystem.matrixMode(5888);
|
||||
}, () -> {
|
||||
throw new RuntimeException("Not Implemented Yet");
|
||||
// RenderSystem.matrixMode(5890);
|
||||
// RenderSystem.popMatrix();
|
||||
// RenderSystem.matrixMode(5888);
|
||||
});
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
} else if (object != null && this.getClass() == object.getClass()) {
|
||||
OffsetTexturing lv = (OffsetTexturing)object;
|
||||
return Float.compare(lv.x, this.x) == 0 && Float.compare(lv.y, this.y) == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.x, this.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Texturing extends RenderPhase {
|
||||
public Texturing(String string, Runnable runnable, Runnable runnable2) {
|
||||
super(string, runnable, runnable2);
|
||||
|
||||
@@ -4,10 +4,21 @@ import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-modules")
|
||||
public class AngelicaConfig {
|
||||
@Config.Comment("Enable Sodium rendering")
|
||||
@Config.Comment("Enable Celeritas terrain rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodium;
|
||||
public static boolean enableCeleritas;
|
||||
|
||||
@Config.Comment("Enable multi-threaded chunk building for improved performance [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresWorldRestart
|
||||
public static boolean enableThreadedChunkBuilding;
|
||||
|
||||
@Config.Comment("Number of chunk builder threads. 0 = auto-detect, -1 = use single-threaded fallback")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = -1, max = 16)
|
||||
@Config.RequiresWorldRestart
|
||||
public static int chunkBuilderThreadCount;
|
||||
|
||||
@Config.Comment("Enable NotFine Options")
|
||||
@Config.DefaultBoolean(false)
|
||||
@@ -17,12 +28,7 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableReesesSodiumOptions;
|
||||
|
||||
@Config.Comment("Enable Sodium fluid rendering")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableSodiumFluidRendering;
|
||||
|
||||
@Config.Comment("Inject QuadProvider rendering into some vanilla blocks")
|
||||
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean injectQPRendering;
|
||||
@@ -32,22 +38,30 @@ public class AngelicaConfig {
|
||||
@Config.Ignore()
|
||||
public static boolean enableTestBlocks;
|
||||
|
||||
@Config.Comment("Enable Iris Shaders [Requires Sodium]")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.Comment("Enable Iris Shaders [Requires Celeritas]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
@Config.Ignore()
|
||||
public static boolean enableIris;
|
||||
|
||||
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
@Config.Ignore()
|
||||
public static boolean enableMCPatcherForgeFeatures;
|
||||
|
||||
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBO;
|
||||
public static boolean enableMCPatcherForgeFeatures;
|
||||
|
||||
@Config.Comment("Replace main menu panorama with modern equivalent.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enablePanoramaBlurShader;
|
||||
|
||||
@Config.Comment("Replace cloud renderer with a VBO version.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBOClouds;
|
||||
|
||||
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVAO;
|
||||
|
||||
@Config.Comment("Enable NotFine features")
|
||||
@Config.DefaultBoolean(true)
|
||||
@@ -70,18 +84,31 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean showSplashMemoryBar;
|
||||
|
||||
@Config.Comment("Renders the HUD elements once per tick and reuses the pixels to improve performance. [Experimental]")
|
||||
@Config.Comment("Renders the HUD elements once per 20 frames (by default) and reuses the pixels to improve performance. [Experimental]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableHudCaching;
|
||||
@Config.Comment("Inject a conditional early return into all RenderGameOverlayEvent receivers; Requires enableHudCaching")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableHudCachingEventTransformer;
|
||||
|
||||
@Config.Comment("Batch drawScreen fonts [Experimental]")
|
||||
@Config.Comment("Enable HUD Caching at runtime. Requires enableHudCaching to be on at startup. [Experimental]")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean hudCachingActive;
|
||||
|
||||
@Config.Comment("The amount of frames to wait before updating the HUD elements. [Experimental]")
|
||||
@Config.DefaultInt(20)
|
||||
@Config.RangeInt(min = 1, max = 60)
|
||||
public static int hudCachingFPS = 20;
|
||||
|
||||
@Config.Comment("Batch drawScreen fonts")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableFontRenderer;
|
||||
|
||||
@Config.Comment("Enable Dynamic Lights")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDynamicLights;
|
||||
|
||||
@@ -90,7 +117,7 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeWorldUpdateLight;
|
||||
|
||||
@Config.Comment("Speedup Animations. [From Hodgepodge]")
|
||||
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean speedupAnimations;
|
||||
@@ -100,23 +127,132 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeTextureLoading;
|
||||
|
||||
@Config.Comment("Fix thread-safety in lotrs rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixLotrSodiumCompat;
|
||||
|
||||
@Config.Comment("Fix thread-safety in Extra Utilities rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixExtraUtilsSodiumCompat;
|
||||
|
||||
@Config.Comment("Fix RenderBlockFluid reading the block type from the world access multiple times")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixFluidRendererCheckingBlockAgain;
|
||||
|
||||
@Config.Comment("Dynamically modifies the render distance of dropped items entities to preserve performance."
|
||||
+ " It starts reducing the render distance when exceeding the threshold set below.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean dynamicItemRenderDistance;
|
||||
|
||||
@Config.Comment("Max amount of dropped item rendered")
|
||||
@Config.DefaultInt(256)
|
||||
@Config.RangeInt(min = 32, max = 2048)
|
||||
public static int droppedItemLimit;
|
||||
|
||||
@Config.Comment("Use total world time instead of normal world time. Allows most shader animations to play when "
|
||||
+ "doDaylightCycle is off, but causes shader animations to desync from time of day.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useTotalWorldTime;
|
||||
|
||||
@Config.Comment("Enable Debug Logging")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDebugLogging;
|
||||
|
||||
@Config.Comment("Enables PBR atlas dumping")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.Name("Enable PBR Debug")
|
||||
public static boolean enablePBRDebug;
|
||||
|
||||
@Config.Comment("Enable Zoom")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableZoom;
|
||||
|
||||
@Config.Comment("Optimizes in-world item rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeInWorldItemRendering;
|
||||
|
||||
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
|
||||
@Config.DefaultInt(512)
|
||||
@Config.RangeInt(min = 256, max = 1024)
|
||||
public static int itemRendererCacheSize;
|
||||
|
||||
@Config.Comment("Render distance for the spinning mob inside mod spawners")
|
||||
@Config.DefaultDouble(16D)
|
||||
@Config.RangeDouble(min = 16D, max = 64D)
|
||||
public static double mobSpawnerRenderDistance;
|
||||
|
||||
@Config.Comment("Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly " +
|
||||
"more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean sleepBeforeSwap;
|
||||
|
||||
@Config.Comment("Allows unicode languages to use an odd gui scale")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean removeUnicodeEvenScaling;
|
||||
|
||||
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
|
||||
"While using \"Compact Vertex Format\" makes the situation even worse.",
|
||||
"This option fixes it.",
|
||||
"Requires texture reloading (F3 + T) after changing this option to take effect"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean fixBlockCrack;
|
||||
|
||||
@Config.Comment({
|
||||
"The \"epsilon\" value for the fixBlockCrack option. ",
|
||||
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
|
||||
"May cause intense flickering (z-fighting) between blocks if the value is too high"
|
||||
})
|
||||
@Config.RangeDouble(min = 0, max = 0.005)
|
||||
@Config.DefaultDouble(0.001)
|
||||
public static double blockCrackFixEpsilon;
|
||||
|
||||
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
|
||||
@Config.DefaultStringList({
|
||||
"net.minecraft.block.BlockCauldron",
|
||||
"net.minecraft.block.BlockStairs"
|
||||
})
|
||||
public static String[] blockCrackFixBlacklist;
|
||||
|
||||
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
|
||||
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
|
||||
})
|
||||
@Config.DefaultStringList({
|
||||
"gregtech.common.blocks.BlockOres",
|
||||
"gregtech.common.blocks.GTBlockOre",
|
||||
"shukaro.artifice.block.world.BlockOre",
|
||||
"bartworks.system.material.BWMetaGeneratedOres",
|
||||
"gtPlusPlus.core.block.base.BlockBaseOre",
|
||||
"org.pfaa.geologica.block.BrokenGeoBlock",
|
||||
"org.pfaa.geologica.block.BrickGeoBlock",
|
||||
})
|
||||
public static String[] blockCrackFixRenderPassWhitelist__;
|
||||
|
||||
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableHardcodedCustomUniforms;
|
||||
|
||||
@Config.Comment("Define IS_IRIS in shader macros.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean defineIsIris;
|
||||
|
||||
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useVanillaChunkTracking;
|
||||
|
||||
@Config.Comment("Disables additional F3 information added by Angelica.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean disableF3Additions;
|
||||
|
||||
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean replaceFFPUploads;
|
||||
|
||||
@Config.Comment("Pinned OpenGL version an integer (e.g. 46, 41, 33). 0 = auto-detect. [33, 46]. (Disable with disableGLVersionPinning=true)")
|
||||
@Config.DefaultInt(0)
|
||||
@Config.RangeInt(min = 0, max = 46)
|
||||
@Config.RequiresMcRestart
|
||||
public static int pinnedGLVersion;
|
||||
|
||||
@Config.Comment("Disable automatic GL version pinning. When true, always probes from highest on every launch.")
|
||||
@Config.DefaultBoolean(false)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean disableGLVersionPinning;
|
||||
|
||||
}
|
||||
@@ -6,6 +6,13 @@ import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
public class AngelicaGuiConfig extends SimpleGuiConfig {
|
||||
public AngelicaGuiConfig(GuiScreen parent) throws ConfigException {
|
||||
super(parent, AngelicaConfig.class, "angelica", "Angelica");
|
||||
super(parent,
|
||||
"angelica",
|
||||
"Angelica",
|
||||
true,
|
||||
AngelicaConfig.class,
|
||||
CompatConfig.class,
|
||||
FontConfig.class
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-compat", category = "compat")
|
||||
public class CompatConfig {
|
||||
|
||||
@Config.Comment("Apply fixes to the LOTR mod")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixLotr;
|
||||
|
||||
@Config.Comment("Apply fixes to Extra Utilities")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixExtraUtils;
|
||||
|
||||
@Config.Comment("Apply fixes to Stacks on Stacks")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixStacksOnStacks;
|
||||
|
||||
@Config.Comment("Apply fixes to Minefactory Reloaded")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixMinefactoryReloaded;
|
||||
|
||||
@Config.Comment("Apply fixes to Thaumcraft")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixThaumcraft;
|
||||
|
||||
@Config.Comment("Apply fixes to ThaumicHorizons")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixThaumicHorizons;
|
||||
|
||||
@Config.Comment("Apply fixes to Immersive Engineering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixImmersiveEngineering;
|
||||
|
||||
@Config.Comment("Apply fixes to Security Craft")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixSecurityCraft;
|
||||
|
||||
@Config.Comment("Apply tweaks to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean tweakNTMSpace;
|
||||
|
||||
@Config.Comment("Apply fixes to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixNTMSpace;
|
||||
|
||||
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
|
||||
@Config.DefaultEnum("ShadersOnly")
|
||||
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
|
||||
public enum altitudePlanetRendererState {
|
||||
Always,
|
||||
ShadersOnly,
|
||||
Never
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
|
||||
@Config(modid = "angelica", category = "customfont")
|
||||
public class FontConfig {
|
||||
@Config.Comment("Whether or not to use custom fonts when drawing text.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableCustomFont;
|
||||
|
||||
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNamePrimary;
|
||||
|
||||
@Config.Comment("Name of the fallback custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNameFallback;
|
||||
|
||||
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
|
||||
@Config.DefaultInt(30)
|
||||
@Config.RangeInt(min = 6, max = 72)
|
||||
public static int customFontQuality;
|
||||
|
||||
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0F, max = 2F)
|
||||
public static float fontShadowOffset;
|
||||
|
||||
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(1)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int shadowCopies;
|
||||
|
||||
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 1, max = 8)
|
||||
public static int boldCopies;
|
||||
|
||||
@Config.Comment("Influences the aspect ratio of each glyph.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -1F, max = 1F)
|
||||
public static float glyphAspect;
|
||||
|
||||
@Config.Comment("Scale of each glyph, whitespace excluded.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float glyphScale;
|
||||
|
||||
@Config.Comment("Whitespace scale.")
|
||||
@Config.DefaultFloat(1F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float whitespaceScale;
|
||||
|
||||
@Config.Comment("Adds extra spacing between glyphs.")
|
||||
@Config.DefaultFloat(0F)
|
||||
@Config.RangeFloat(min = -2F, max = 2F)
|
||||
public static float glyphSpacing;
|
||||
|
||||
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 0, max = 2)
|
||||
public static int fontAAMode;
|
||||
|
||||
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
|
||||
@Config.DefaultInt(7)
|
||||
@Config.RangeInt(min = 1, max = 24)
|
||||
public static int fontAAStrength;
|
||||
|
||||
@Config.Comment("Custom font scale multiplier, for bugfixes.")
|
||||
@Config.DefaultFloat(1.5F)
|
||||
@Config.RangeFloat(min = 0.1F, max = 3F)
|
||||
public static float customFontScale;
|
||||
|
||||
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
|
||||
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
|
||||
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
|
||||
"See for additional details: https://github.com/GTNewHorizons/Angelica/issues/1239#issuecomment-3729877936"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableGlyphReplacements;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Adapated from: Beddium for usage in Angelica
|
||||
*
|
||||
* Copyright (C) 2025 Ven, FalsePattern
|
||||
* All Rights Reserved
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, only version 3 of the License.
|
||||
*
|
||||
* 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 Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ChunkProviderClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.world.chunk.EmptyChunk;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
|
||||
*/
|
||||
public class ChunkDebugMinimap {
|
||||
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
|
||||
@Getter
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private static final int RANGE = 32; // Chunks in each direction from player
|
||||
|
||||
public static synchronized void toggle() {
|
||||
if (enabled) disable();
|
||||
else enable();
|
||||
}
|
||||
|
||||
public static synchronized void enable() {
|
||||
if (enabled) return;
|
||||
enabled = true;
|
||||
MinecraftForge.EVENT_BUS.register(INSTANCE);
|
||||
}
|
||||
|
||||
public static synchronized void disable() {
|
||||
if (!enabled) return;
|
||||
enabled = false;
|
||||
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderOverlay(RenderGameOverlayEvent.Text event) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld == null || mc.thePlayer == null) return;
|
||||
|
||||
final CeleritasWorldRenderer renderer;
|
||||
try {
|
||||
renderer = CeleritasWorldRenderer.getInstance();
|
||||
} catch (IllegalStateException e) {
|
||||
return; // Renderer not initialized
|
||||
}
|
||||
|
||||
final AngelicaRenderSectionManager manager = renderer.getRenderSectionManager();
|
||||
if (manager == null) return;
|
||||
|
||||
final float partialTicks = event.partialTicks;
|
||||
final double pX = mc.thePlayer.prevPosX + (mc.thePlayer.posX - mc.thePlayer.prevPosX) * partialTicks;
|
||||
final double pZ = mc.thePlayer.prevPosZ + (mc.thePlayer.posZ - mc.thePlayer.prevPosZ) * partialTicks;
|
||||
|
||||
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
|
||||
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
|
||||
|
||||
// Save GL state
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
// Setup rendering
|
||||
GLStateManager.glScaled(2, 2, 1);
|
||||
GLStateManager.glTranslated(32, 32, 0);
|
||||
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
tess.draw();
|
||||
|
||||
// Restore GL state
|
||||
GLStateManager.enableLighting();
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
boolean initialized = false;
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
// Check all 16 sections in the chunk column
|
||||
for (int y = 0; y < 16; y++) {
|
||||
if (manager.isSectionBuilt(chunkX, y, chunkZ) && !manager.isSectionVisuallyEmpty(chunkX, y, chunkZ)) {
|
||||
initialized = true;
|
||||
g = 1; // Green = built
|
||||
if (manager.isSectionVisible(chunkX, y, chunkZ)) {
|
||||
b = 1; // Cyan = visible
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
// Check if chunk is loaded
|
||||
final var chunk = chunkProvider.provideChunk(chunkX, chunkZ);
|
||||
if (chunk instanceof EmptyChunk) {
|
||||
r = 1; // Red = unloaded
|
||||
} else {
|
||||
b = 1; // Blue = loaded but not built
|
||||
}
|
||||
}
|
||||
|
||||
tess.setColorRGBA_F(r, g, b, 0.8f);
|
||||
|
||||
final int xStart = chunkX - playerChunkX;
|
||||
final int zStart = chunkZ - playerChunkZ;
|
||||
final int xEnd = xStart + 1;
|
||||
final int zEnd = zStart + 1;
|
||||
|
||||
tess.addVertex(xStart, zStart, 0);
|
||||
tess.addVertex(xStart, zEnd, 0);
|
||||
tess.addVertex(xEnd, zEnd, 0);
|
||||
tess.addVertex(xEnd, zStart, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class F3Direction {
|
||||
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
|
||||
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
|
||||
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
final int width = scaledresolution.getScaledWidth();
|
||||
final int height = scaledresolution.getScaledHeight();
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
GLStateManager.glTranslatef((float) (width / 2), (float) (height / 2), -90);
|
||||
|
||||
final Entity entity = mc.renderViewEntity;
|
||||
GLStateManager.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
|
||||
GLStateManager.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
|
||||
|
||||
GLStateManager.glScalef(-1.0F, -1.0F, -1.0F);
|
||||
|
||||
renderWorldDirections();
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
public static void renderWorldDirections() {
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDepthMask(false);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
tessellator.startDrawing(GL11.GL_LINES);
|
||||
|
||||
//X
|
||||
tessellator.setColorRGBA_F(255, 0, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(10, 0.0D, 0.0D);
|
||||
|
||||
//Z
|
||||
tessellator.setColorRGBA_F(0, 0, 255, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 0.0D, 10);
|
||||
|
||||
//Y
|
||||
tessellator.setColorRGBA_F(0, 255, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 10, 0.0D);
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
|
||||
|
||||
public abstract class F3Graph {
|
||||
private static final int NUM_SAMPLES = 240;
|
||||
// Two floats (x,y)
|
||||
private static final int VERT_FLOATS = 2;
|
||||
private static final int VERT_COUNT = 4;
|
||||
private static final int BORDER = 1;
|
||||
private static final int HEIGHT = 60;
|
||||
private static final int WIDTH = NUM_SAMPLES;
|
||||
private static final int FONT_COLOR = 0xFFE0E0E0;
|
||||
// Due to GLSL 120 limitations, it's just easier to use floats
|
||||
private final FloatBuffer sampleBuf = BufferUtils.createFloatBuffer(NUM_SAMPLES);
|
||||
private final long[] samples = new long[NUM_SAMPLES]; // long version for calculations
|
||||
// Circular buffer holding the last 240 samples, in nanoseconds
|
||||
private int samplesHead = 0; // one ahead of the position of the last sample
|
||||
private boolean initialized = false;
|
||||
private ShaderProgram shader;
|
||||
private int aPos;
|
||||
private int uFBWidth;
|
||||
private int uFBHeight;
|
||||
private int uScaleFactor;
|
||||
private int uHeadIdx;
|
||||
private int uSamples;
|
||||
private int vertBuf;
|
||||
private int vao;
|
||||
private final ResourceLocation texture;
|
||||
private final float pxPerNs;
|
||||
private final boolean left;
|
||||
private long sum;
|
||||
|
||||
protected F3Graph(ResourceLocation texture, float pxPerNs, boolean left) {
|
||||
this.texture = texture;
|
||||
this.pxPerNs = pxPerNs;
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
public void putSample(long time) {
|
||||
// Manage running trackers
|
||||
sum -= samples[samplesHead];
|
||||
sum += time;
|
||||
samples[samplesHead] = time;
|
||||
|
||||
sampleBuf.put(samplesHead, (float) time);
|
||||
samplesHead = (samplesHead + 1) % NUM_SAMPLES;
|
||||
}
|
||||
|
||||
private int getVertX(ScaledResolution sres, int idx) {
|
||||
if (left) {
|
||||
return switch (idx) {
|
||||
case 0, 3 -> WIDTH + BORDER * 2;
|
||||
case 1, 2 -> 0;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
final int displayWidth = sres.getScaledWidth();
|
||||
return switch (idx) {
|
||||
case 0, 3 -> displayWidth;
|
||||
case 1, 2 -> displayWidth - WIDTH - BORDER * 2;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
private int getVertY(ScaledResolution sres, int idx) {
|
||||
final int displayHeight = sres.getScaledHeight();
|
||||
return switch (idx) {
|
||||
case 0, 1 -> displayHeight - HEIGHT - BORDER * 2;
|
||||
case 2, 3 -> displayHeight;
|
||||
default -> throw new RuntimeException("Tried to get out-of-bounds vertex for graph!");
|
||||
};
|
||||
}
|
||||
|
||||
private void init() {
|
||||
shader = new ShaderProgram(
|
||||
"angelica",
|
||||
"shaders/debug_graph.vert.glsl",
|
||||
"shaders/debug_graph.frag.glsl");
|
||||
shader.use();
|
||||
|
||||
// Attribute location is declared in shader via layout(location = 0)
|
||||
aPos = 0;
|
||||
|
||||
// Register uniforms
|
||||
uFBWidth = shader.getUniformLocation("fbWidth");
|
||||
uFBHeight = shader.getUniformLocation("fbHeight");
|
||||
uScaleFactor = shader.getUniformLocation("scaleFactor");
|
||||
uSamples = shader.getUniformLocation("samples");
|
||||
uHeadIdx = shader.getUniformLocation("headIdx");
|
||||
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
|
||||
final int uLeft = shader.getUniformLocation("left");
|
||||
|
||||
vao = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
vertBuf = glGenBuffers();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
|
||||
try (final MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
|
||||
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
|
||||
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
|
||||
vertices.put(new float[]{
|
||||
BORDER, BORDER,
|
||||
WIDTH + BORDER, BORDER,
|
||||
BORDER, Float.MAX_VALUE,
|
||||
WIDTH + BORDER, Float.MAX_VALUE
|
||||
});
|
||||
vertices.rewind();
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Configure vertex attribute in the VAO
|
||||
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
glEnableVertexAttribArray(aPos);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Load initial value for uniforms
|
||||
glUniform1f(uFBWidth, mc.displayWidth);
|
||||
glUniform1f(uFBHeight, mc.displayHeight);
|
||||
glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
glUniform1f(uPxPerNs, pxPerNs);
|
||||
glUniform1i(uLeft, left ? 1 : 0); // this is how you load bool uniforms
|
||||
|
||||
ShaderProgram.clear();
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (!initialized) {
|
||||
init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We try to copy modern vanilla's tracker.
|
||||
* It is 240 (FRAME_TIMES) wide by 60 tall, including the 1px borders.
|
||||
* We instead make it 242x62 including borders to prevent the borders from overlapping the samples.
|
||||
* The background is ARGB 90505050, the borders FFFFFFFF, and the text FFE0E0E0.
|
||||
* First the background is rendered, then the samples, then the foreground texture and the text.
|
||||
* The background is drawn via a 242x62 translucent rect.
|
||||
* Next, the samples are rendered by a shader on a transparent 240x60 rect.
|
||||
* Finally, the foreground texture and the text is rendered on top.
|
||||
* The shader pipeline is only needed for the sample draw.
|
||||
*/
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Setup GL state
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Draw background
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
tess.startDrawingQuads();
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
|
||||
}
|
||||
tess.draw();
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
|
||||
// Draw samples
|
||||
shader.use();
|
||||
|
||||
// Load uniforms
|
||||
glUniform1f(uFBWidth, mc.displayWidth);
|
||||
glUniform1f(uFBHeight, mc.displayHeight);
|
||||
glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
glUniform1i(uHeadIdx, samplesHead);
|
||||
glUniform1(uSamples, sampleBuf);
|
||||
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
// Left side graphs are inverted, so temporarily disable culling
|
||||
if (!left) GLStateManager.disableCull();
|
||||
|
||||
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
|
||||
if (!left) GLStateManager.enableCull();
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
ShaderProgram.clear();
|
||||
|
||||
// Draw foreground
|
||||
|
||||
mc.getTextureManager().bindTexture(texture);
|
||||
tess.startDrawingQuads();
|
||||
tess.addVertexWithUV(getVertX(sr, 0), getVertY(sr, 0), 0.0D, 1, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 1), getVertY(sr, 1), 0.0D, 0, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 2), getVertY(sr, 2), 0.0D, 0, 1);
|
||||
tess.addVertexWithUV(getVertX(sr, 3), getVertY(sr, 3), 0.0D, 1, 1);
|
||||
tess.draw();
|
||||
|
||||
// Reset GL state
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
// Draw text
|
||||
// Ensure running counters are reset on first sample
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
for (int i = 0; i < NUM_SAMPLES; ++i) {
|
||||
min = Math.min(min, samples[i]);
|
||||
max = Math.max(max, samples[i]);
|
||||
}
|
||||
String minStr = String.format("%.1f ms min", min / 1_000_000D);
|
||||
String avgStr = String.format("%.1f ms avg", sum / 1_000_000 / (double) NUM_SAMPLES);
|
||||
String maxStr = String.format("%.1f ms max", max / 1_000_000D);
|
||||
final FontRenderer fr = mc.fontRenderer;
|
||||
final int top = sr.getScaledHeight() - HEIGHT - BORDER * 2 - fr.FONT_HEIGHT;
|
||||
final int scaledWidth = sr.getScaledWidth();
|
||||
|
||||
if (left) {
|
||||
fr.drawString(minStr, BORDER * 2, top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, BORDER + WIDTH / 2 - fr.getStringWidth(avgStr) / 2, top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, BORDER * 2 + WIDTH - fr.getStringWidth(maxStr), top, FONT_COLOR, true);
|
||||
} else {
|
||||
fr.drawString(minStr, scaledWidth - (BORDER * 2 + WIDTH), top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, scaledWidth - (BORDER + WIDTH / 2 + fr.getStringWidth(avgStr) / 2), top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, scaledWidth - (BORDER * 2 + fr.getStringWidth(maxStr)), top, FONT_COLOR, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class FrametimeGraph extends F3Graph {
|
||||
public FrametimeGraph() {
|
||||
// At 1x scale, 30 FPS should be 60 px. 30 FPS = 33_333_333ns per frame, 60px/33_333_333ns = 0.0000018f px/ns
|
||||
super(
|
||||
new ResourceLocation("angelica:textures/frametimes_fg.png"),
|
||||
0.0000018f,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class TPSGraph extends F3Graph {
|
||||
public TPSGraph() {
|
||||
// At 1x scale, 20 TPS should be 60 px. 20 FPS = 50_000_000ns per frame, 60px/50_000_000ns = 0.0000012 px/ns
|
||||
super(
|
||||
new ResourceLocation("angelica:textures/tps_fg.png"),
|
||||
0.0000012f,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Calculates the appropriate tick mode for a dynamic light source based on distance from camera and position relative to camera direction.
|
||||
*/
|
||||
public final class AdaptiveTickCalculator {
|
||||
|
||||
// Distance thresholds in blocks (configurable via settings)
|
||||
private static int slowDistance = 32; // ~2 chunks
|
||||
private static int slowerDistance = 64; // ~4 chunks
|
||||
private static int backgroundDistance = 128; // ~8 chunks
|
||||
|
||||
// Squared thresholds for efficient distance comparison
|
||||
private static int slowDistanceSq = slowDistance * slowDistance;
|
||||
private static int slowerDistanceSq = slowerDistance * slowerDistance;
|
||||
private static int backgroundDistanceSq = backgroundDistance * backgroundDistance;
|
||||
|
||||
private AdaptiveTickCalculator() {}
|
||||
|
||||
/**
|
||||
* Calculate the appropriate tick mode for a light source.
|
||||
*/
|
||||
public static AdaptiveTickMode calculate(@NotNull IDynamicLightSource source, double cameraX, double cameraY, double cameraZ, double lookDirX, double lookDirZ) {
|
||||
double dx = source.angelica$getDynamicLightX() - cameraX;
|
||||
double dy = source.angelica$getDynamicLightY() - cameraY;
|
||||
double dz = source.angelica$getDynamicLightZ() - cameraZ;
|
||||
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
// Check if behind camera (dot product of direction to source with look direction)
|
||||
// Only use X and Z for horizontal check (more stable as player looks up/down)
|
||||
double dot = dx * lookDirX + dz * lookDirZ;
|
||||
boolean behindCamera = dot < 0;
|
||||
|
||||
// Behind camera always gets BACKGROUND mode
|
||||
if (behindCamera) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
|
||||
// Distance-based mode selection
|
||||
if (distSq > backgroundDistanceSq) {
|
||||
return AdaptiveTickMode.BACKGROUND;
|
||||
}
|
||||
if (distSq > slowerDistanceSq) {
|
||||
return AdaptiveTickMode.SLOWER;
|
||||
}
|
||||
if (distSq > slowDistanceSq) {
|
||||
return AdaptiveTickMode.SLOW;
|
||||
}
|
||||
|
||||
return AdaptiveTickMode.REAL_TIME;
|
||||
}
|
||||
|
||||
public static void setSlowDistance(int distance) {
|
||||
slowDistance = distance;
|
||||
slowDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setSlowerDistance(int distance) {
|
||||
slowerDistance = distance;
|
||||
slowerDistanceSq = distance * distance;
|
||||
}
|
||||
|
||||
public static void setBackgroundDistance(int distance) {
|
||||
backgroundDistance = distance;
|
||||
backgroundDistanceSq = distance * distance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
/**
|
||||
* Defines tick modes for adaptive dynamic light updates. Distant or off-screen light sources update less frequently to save CPU.
|
||||
*/
|
||||
public enum AdaptiveTickMode {
|
||||
/** Updates every tick - for nearby, visible light sources */
|
||||
REAL_TIME(1),
|
||||
/** Updates every 5 ticks - for light sources at moderate distance */
|
||||
SLOW(5),
|
||||
/** Updates every 10 ticks - for light sources at far distance */
|
||||
SLOWER(10),
|
||||
/** Updates every 20 ticks - for light sources behind the camera or very far */
|
||||
BACKGROUND(20);
|
||||
|
||||
private final int delay;
|
||||
|
||||
AdaptiveTickMode(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public int getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public boolean shouldTickThisFrame(int worldTick, int sourceHash) {
|
||||
if (delay <= 1) {
|
||||
return true;
|
||||
}
|
||||
return (worldTick % delay) == Math.abs(sourceHash % delay);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Manages deferred chunk rebuilds for dynamic lights with optional frustum culling. Instead of scheduling chunk rebuilds immediately
|
||||
* when light sources move, this tracks pending rebuilds and only schedules visible chunks.
|
||||
*/
|
||||
public class ChunkRebuildManager {
|
||||
|
||||
/** Maps packed chunk position to ticks waiting */
|
||||
private final Long2IntOpenHashMap pendingRebuilds = new Long2IntOpenHashMap();
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
/** Maximum ticks a pending rebuild can wait before being forced through */
|
||||
private static int maxTicksWaiting = 100;
|
||||
|
||||
/** Reusable lists for processVisible - only used from render thread */
|
||||
private final LongArrayList candidates = new LongArrayList();
|
||||
private final LongArrayList toRebuild = new LongArrayList();
|
||||
private final LongArrayList toIncrement = new LongArrayList();
|
||||
|
||||
public static void setMaxTicksWaiting(int ticks) {
|
||||
maxTicksWaiting = Math.max(20, Math.min(ticks, 600)); // Clamp 1-30 seconds (20-600 ticks)
|
||||
}
|
||||
|
||||
public void requestRebuild(int x, int y, int z) {
|
||||
final long packed = CoordinatePacker.pack(x, y, z);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Reset wait counter if already pending, otherwise add new entry
|
||||
pendingRebuilds.put(packed, 0);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void processVisible(Viewport viewport, @NotNull IDynamicLightWorldRenderer renderer) {
|
||||
candidates.clear();
|
||||
toRebuild.clear();
|
||||
toIncrement.clear();
|
||||
|
||||
// Phase 1: Collect all pending chunks with read lock (fast)
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
if (pendingRebuilds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// Collect candidates that might need processing
|
||||
final var iterator = pendingRebuilds.long2IntEntrySet().fastIterator();
|
||||
while (iterator.hasNext()) {
|
||||
final var entry = iterator.next();
|
||||
final long packed = entry.getLongKey();
|
||||
final int ticksWaiting = entry.getIntValue();
|
||||
|
||||
// Force process if no viewport or waited too long
|
||||
if (viewport == null || ticksWaiting >= maxTicksWaiting) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
candidates.add(packed);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
// Phase 2: Check visibility
|
||||
for (int i = 0; i < candidates.size(); i++) {
|
||||
long packed = candidates.getLong(i);
|
||||
int x = CoordinatePacker.unpackX(packed);
|
||||
int y = CoordinatePacker.unpackY(packed);
|
||||
int z = CoordinatePacker.unpackZ(packed);
|
||||
|
||||
if (isChunkVisible(viewport, x, y, z)) {
|
||||
toRebuild.add(packed);
|
||||
} else {
|
||||
toIncrement.add(packed);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Update map with write lock (brief)
|
||||
if (!toRebuild.isEmpty() || !toIncrement.isEmpty()) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Remove chunks we're going to rebuild
|
||||
LongIterator rebuildIter = toRebuild.iterator();
|
||||
while (rebuildIter.hasNext()) {
|
||||
pendingRebuilds.remove(rebuildIter.nextLong());
|
||||
}
|
||||
LongIterator incIter = toIncrement.iterator();
|
||||
while (incIter.hasNext()) {
|
||||
long packed = incIter.nextLong();
|
||||
int current = pendingRebuilds.get(packed);
|
||||
if (pendingRebuilds.containsKey(packed)) {
|
||||
pendingRebuilds.put(packed, current + 1);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Schedule rebuilds outside of lock
|
||||
for (int i = 0; i < toRebuild.size(); i++) {
|
||||
final long packed = toRebuild.getLong(i);
|
||||
renderer.scheduleRebuildForChunk(
|
||||
CoordinatePacker.unpackX(packed),
|
||||
CoordinatePacker.unpackY(packed),
|
||||
CoordinatePacker.unpackZ(packed),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isChunkVisible(Viewport viewport, int chunkX, int chunkY, int chunkZ) {
|
||||
// Convert chunk section coords to block coords
|
||||
int blockX = chunkX << 4;
|
||||
int blockY = chunkY << 4;
|
||||
int blockZ = chunkZ << 4;
|
||||
|
||||
// Check if the 16x16x16 chunk section box is visible
|
||||
return viewport.isBoxVisible(
|
||||
blockX, blockY, blockZ,
|
||||
blockX + 16, blockY + 16, blockZ + 16
|
||||
);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
pendingRebuilds.clear();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPendingCount() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return pendingRebuilds.size();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.BlockPos;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.BlockPosImpl;
|
||||
import baubles.common.lib.PlayerHandler;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.PrimedEntityAccessor;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import mods.battlegear2.api.core.IBattlePlayer;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.item.EntityItem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DynamicLights {
|
||||
private static DynamicLights instance;
|
||||
private static IDynamicLightWorldRenderer activeRenderer;
|
||||
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
|
||||
public static boolean ShaderForce = false;
|
||||
public static boolean FrustumCullingEnabled = true;
|
||||
public static boolean AdaptiveTickingEnabled = true;
|
||||
|
||||
public static final boolean configEnabled = AngelicaConfig.enableDynamicLights;
|
||||
|
||||
private static final double MAX_RADIUS = 7.75;
|
||||
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
|
||||
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
|
||||
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
|
||||
private volatile int lightSourceCount = 0;
|
||||
private final ChunkRebuildManager chunkRebuildManager = new ChunkRebuildManager();
|
||||
private long lastUpdate = System.currentTimeMillis();
|
||||
private int lastUpdateCount = 0;
|
||||
|
||||
@@ -42,7 +65,23 @@ public class DynamicLights {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return Mode.isEnabled() && AngelicaConfig.enableDynamicLights;
|
||||
return configEnabled &&
|
||||
Mode.isEnabled() &&
|
||||
// if shader force is enabled then true
|
||||
// if not, then true when shaders are not in use
|
||||
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
|
||||
}
|
||||
|
||||
public boolean hasLightSources() {
|
||||
return lightSourceCount > 0;
|
||||
}
|
||||
|
||||
public static IDynamicLightWorldRenderer getActiveRenderer() {
|
||||
return activeRenderer;
|
||||
}
|
||||
|
||||
public static void setActiveRenderer(IDynamicLightWorldRenderer renderer) {
|
||||
activeRenderer = renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,17 +89,52 @@ public class DynamicLights {
|
||||
*
|
||||
* @param renderer the renderer
|
||||
*/
|
||||
public void updateAll(@NotNull SodiumWorldRenderer renderer) {
|
||||
public void updateAll(@NotNull IDynamicLightWorldRenderer renderer) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now >= this.lastUpdate + 50) {
|
||||
if (now >= this.lastUpdate + Mode.getDelay()) {
|
||||
this.lastUpdate = now;
|
||||
this.lastUpdateCount = 0;
|
||||
|
||||
// Get camera info for adaptive ticking
|
||||
double cameraX = 0, cameraY = 0, cameraZ = 0;
|
||||
double lookDirX = 0, lookDirZ = 1;
|
||||
int worldTick = 0;
|
||||
|
||||
if (AdaptiveTickingEnabled) {
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc.theWorld != null && mc.renderViewEntity != null) {
|
||||
cameraX = mc.renderViewEntity.posX;
|
||||
cameraY = mc.renderViewEntity.posY + mc.renderViewEntity.getEyeHeight();
|
||||
cameraZ = mc.renderViewEntity.posZ;
|
||||
worldTick = (int) (mc.theWorld.getTotalWorldTime() & 0x7FFFFFFF);
|
||||
|
||||
// Calculate look direction from yaw (normalized horizontal direction)
|
||||
float yaw = mc.renderViewEntity.rotationYaw;
|
||||
double yawRad = Math.toRadians(yaw);
|
||||
lookDirX = -Math.sin(yawRad);
|
||||
lookDirZ = Math.cos(yawRad);
|
||||
}
|
||||
}
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
// Entity type filter
|
||||
if (!EntityLightConfig.isEntityTypeEnabled(lightSource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adaptive ticking filter
|
||||
if (AdaptiveTickingEnabled) {
|
||||
AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
|
||||
lightSource, cameraX, cameraY, cameraZ, lookDirX, lookDirZ);
|
||||
if (!mode.shouldTickThisFrame(worldTick, lightSource.hashCode())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (lightSource.angelica$updateDynamicLight(renderer))
|
||||
this.lastUpdateCount++;
|
||||
}
|
||||
@@ -74,7 +148,9 @@ public class DynamicLights {
|
||||
|
||||
public void addLightSource(IDynamicLightSource lightSource) {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
this.dynamicLightSources.add(lightSource);
|
||||
if (this.dynamicLightSources.add(lightSource)) {
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
}
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
@@ -92,8 +168,9 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (it.equals(lightSource)) {
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null)
|
||||
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -115,10 +192,11 @@ public class DynamicLights {
|
||||
it = dynamicLightSources.next();
|
||||
if (filter.test(it)) {
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null) {
|
||||
this.lightSourceCount = this.dynamicLightSources.size();
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -138,12 +216,13 @@ public class DynamicLights {
|
||||
while (dynamicLightSources.hasNext()) {
|
||||
it = dynamicLightSources.next();
|
||||
dynamicLightSources.remove();
|
||||
if (SodiumWorldRenderer.getInstance() != null) {
|
||||
if (activeRenderer != null) {
|
||||
if (it.angelica$getLuminance() > 0)
|
||||
it.angelica$resetDynamicLight();
|
||||
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
|
||||
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
|
||||
}
|
||||
}
|
||||
this.lightSourceCount = 0;
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
@@ -188,7 +267,21 @@ public class DynamicLights {
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
public double getDynamicLightLevel(@NotNull BlockPosImpl pos) {
|
||||
/**
|
||||
* Gets dynamic light level at an exact world position (for smooth per-vertex lighting).
|
||||
*/
|
||||
public double getDynamicLightLevel(double x, double y, double z) {
|
||||
double result = 0;
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
for (var lightSource : this.dynamicLightSources) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
|
||||
}
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
public double getDynamicLightLevel(@NotNull BlockPos pos) {
|
||||
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
@@ -214,6 +307,29 @@ public class DynamicLights {
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates dynamic light level at an exact position (no block center offset).
|
||||
* Used for smooth per-vertex lighting.
|
||||
*/
|
||||
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
int luminance = lightSource.angelica$getLuminance();
|
||||
if (luminance > 0) {
|
||||
double dx = x - lightSource.angelica$getDynamicLightX();
|
||||
double dy = y - lightSource.angelica$getDynamicLightY();
|
||||
double dz = z - lightSource.angelica$getDynamicLightZ();
|
||||
|
||||
double distanceSquared = dx * dx + dy * dy + dz * dz;
|
||||
if (distanceSquared <= MAX_RADIUS_SQUARED) {
|
||||
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
|
||||
double lightLevel = multiplier * (double) luminance;
|
||||
if (lightLevel > currentLightLevel) {
|
||||
return lightLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentLightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic light level generated by the light source at the specified position.
|
||||
*
|
||||
@@ -222,7 +338,7 @@ public class DynamicLights {
|
||||
* @param currentLightLevel the current surrounding dynamic light level
|
||||
* @return the dynamic light level at the specified position
|
||||
*/
|
||||
public static double maxDynamicLightLevel(@NotNull BlockPosImpl pos, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
public static double maxDynamicLightLevel(@NotNull BlockPos pos, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
|
||||
return maxDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ(), lightSource, currentLightLevel);
|
||||
}
|
||||
|
||||
@@ -230,6 +346,51 @@ public class DynamicLights {
|
||||
return this.getLightmapWithDynamicLight(this.getDynamicLightLevel(x, y, z), lightmap);
|
||||
}
|
||||
|
||||
public List<IDynamicLightSource> getSourcesForChunk(int chunkMinX, int chunkMinY, int chunkMinZ) {
|
||||
// Chunk center
|
||||
final double centerX = chunkMinX + 8.0;
|
||||
final double centerY = chunkMinY + 8.0;
|
||||
final double centerZ = chunkMinZ + 8.0;
|
||||
|
||||
// Max distance from chunk center to any corner is sqrt(3) * 8 ≈ 13.86
|
||||
// Add MAX_RADIUS to get the search radius (13.86 + 7.75)^2 ≈ 467
|
||||
final double searchRadiusSq = 470.0; // Slightly padded for safety
|
||||
|
||||
List<IDynamicLightSource> result = null;
|
||||
|
||||
this.lightSourcesLock.readLock().lock();
|
||||
try {
|
||||
for (IDynamicLightSource source : this.dynamicLightSources) {
|
||||
double dx = source.angelica$getDynamicLightX() - centerX;
|
||||
double dy = source.angelica$getDynamicLightY() - centerY;
|
||||
double dz = source.angelica$getDynamicLightZ() - centerZ;
|
||||
double distSq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if (distSq <= searchRadiusSq) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
result.add(source);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.lightSourcesLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return result != null ? result : Collections.emptyList();
|
||||
}
|
||||
|
||||
public double getDynamicLightLevelFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
|
||||
if (sources == null || sources.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
double result = 0;
|
||||
for (int i = 0, size = sources.size(); i < size; i++) {
|
||||
result = maxDynamicLightLevelExact(x, y, z, sources.get(i), result);
|
||||
}
|
||||
return MathHelper.clamp_double(result, 0, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lightmap with combined light levels.
|
||||
*
|
||||
@@ -260,7 +421,7 @@ public class DynamicLights {
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, @NotNull BlockPosImpl chunkPos) {
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
|
||||
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
|
||||
}
|
||||
|
||||
@@ -270,14 +431,37 @@ public class DynamicLights {
|
||||
* @param renderer the renderer
|
||||
* @param chunkPos the packed chunk position
|
||||
*/
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuild(renderer, BlockPos.unpackLongX(chunkPos), BlockPos.unpackLongY(chunkPos), BlockPos.unpackLongZ(chunkPos));
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, int x, int y, int z) {
|
||||
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
if (FrustumCullingEnabled && instance != null) {
|
||||
instance.chunkRebuildManager.requestRebuild(x, y, z);
|
||||
} else {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
|
||||
renderer.scheduleRebuildForChunk(x, y, z, false);
|
||||
}
|
||||
|
||||
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
|
||||
scheduleChunkRebuildForRemoval(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
|
||||
}
|
||||
|
||||
public void processChunkRebuilds(@Nullable Viewport viewport) {
|
||||
if (activeRenderer == null) {
|
||||
return;
|
||||
}
|
||||
chunkRebuildManager.processVisible(viewport, activeRenderer);
|
||||
}
|
||||
|
||||
public ChunkRebuildManager getChunkRebuildManager() {
|
||||
return chunkRebuildManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tracked chunk sets.
|
||||
*
|
||||
@@ -285,9 +469,9 @@ public class DynamicLights {
|
||||
* @param old the set of old chunk coordinates to remove this chunk from it
|
||||
* @param newPos the set of new chunk coordinates to add this chunk to it
|
||||
*/
|
||||
public static void updateTrackedChunks(@NotNull BlockPosImpl chunkPos, @Nullable LongOpenHashSet old, @Nullable LongOpenHashSet newPos) {
|
||||
public static void updateTrackedChunks(@NotNull IBlockPos chunkPos, @Nullable LongOpenHashSet old, @Nullable LongOpenHashSet newPos) {
|
||||
if (old != null || newPos != null) {
|
||||
long pos = chunkPos.asLong();
|
||||
final long pos = chunkPos.asLong();
|
||||
if (old != null)
|
||||
old.remove(pos);
|
||||
if (newPos != null)
|
||||
@@ -318,13 +502,15 @@ public class DynamicLights {
|
||||
* @param submergedInWater {@code true} if the stack is submerged in water, else {@code false}
|
||||
* @return the luminance of the item
|
||||
*/
|
||||
public static int getLuminanceFromItemStack(@NotNull ItemStack stack, boolean submergedInWater) {
|
||||
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
|
||||
Item item = stack.getItem();
|
||||
if (item instanceof ItemBlock itemBlock) {
|
||||
Block block = itemBlock.field_150939_a;
|
||||
if (block != null) {
|
||||
return block.getLightValue();
|
||||
}
|
||||
} else if (item instanceof IDynamicLightProducer lightProducer){
|
||||
return lightProducer.getLuminance();
|
||||
}
|
||||
|
||||
if (item == Items.lava_bucket) return Blocks.lava.getLightValue();
|
||||
@@ -334,22 +520,55 @@ public class DynamicLights {
|
||||
|
||||
public static int getLuminanceFromEntity(@NotNull Entity entity) {
|
||||
|
||||
if (entity.fire > 0) return 15;
|
||||
if (entity.isBurning()) return 15;
|
||||
|
||||
boolean inWater = entity.isInsideOfMaterial(Material.water);
|
||||
|
||||
// TODO only have certain items not glow in water?
|
||||
if (inWater) return 0;
|
||||
|
||||
if (entity instanceof EntityItem item) {
|
||||
return getLuminanceFromItemStack(item.getEntityItem(), item.isInWater());
|
||||
return getLuminanceFromItemStack(item.getEntityItem());
|
||||
}
|
||||
|
||||
int luminance = 0;
|
||||
if (entity instanceof EntityLivingBase living) {
|
||||
int luminance = 0;
|
||||
|
||||
boolean inWater = living.isInWater();
|
||||
|
||||
// check equipment + hand for light
|
||||
// check equipment + hand for light (should work for all entities)
|
||||
ItemStack itemStack;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if ((itemStack = living.getEquipmentInSlot(i)) != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack, inWater));
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack));
|
||||
}
|
||||
}
|
||||
|
||||
if (living instanceof EntityPlayer player) {
|
||||
if (ModStatus.isBattlegearLoaded &&
|
||||
player instanceof IBattlePlayer battlePlayer &&
|
||||
battlePlayer.battlegear2$isBattlemode()
|
||||
) {
|
||||
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isBackhandLoaded){
|
||||
ItemStack offhand = BackhandReflectionCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
}
|
||||
|
||||
if (ModStatus.isBaublesLoaded){
|
||||
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
|
||||
if (playerBaubles != null){
|
||||
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
|
||||
var stack = playerBaubles.getStackInSlot(i);
|
||||
if (stack != null){
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +576,10 @@ public class DynamicLights {
|
||||
}
|
||||
|
||||
// TODO: Creepers? TNT? Dusts?
|
||||
if (entity instanceof PrimedEntityAccessor primedEntity) {
|
||||
luminance = Math.max(luminance, primedEntity.angelica$getLuminance());
|
||||
}
|
||||
|
||||
return 0;
|
||||
return luminance;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ public enum DynamicLightsMode
|
||||
OFF("Off", 0),
|
||||
FASTEST("Fastest", 500),
|
||||
FAST("Fast", 250),
|
||||
FANCY("Fancy", 0);
|
||||
FANCY("Fancy", 50),
|
||||
REALTIME("Realtime", 0);
|
||||
|
||||
private final String name;
|
||||
private final int delay;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -27,11 +26,13 @@ public interface IDynamicLightSource {
|
||||
double angelica$getDynamicLightZ();
|
||||
|
||||
/**
|
||||
* Returns the dynamic light source world.
|
||||
* Returns the dynamic light source world renderer.
|
||||
*
|
||||
* @return the world instance
|
||||
* @return the world renderer instance
|
||||
*/
|
||||
SodiumWorldRenderer angelica$getDynamicLightWorld();
|
||||
default IDynamicLightWorldRenderer angelica$getDynamicLightWorld() {
|
||||
return DynamicLights.getActiveRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the dynamic light is enabled or not.
|
||||
@@ -72,15 +73,9 @@ public interface IDynamicLightSource {
|
||||
* Executed at each tick.
|
||||
*/
|
||||
void angelica$dynamicLightTick();
|
||||
default void angelica$updateLights() {}
|
||||
|
||||
/**
|
||||
* Returns whether this dynamic light source should update.
|
||||
*
|
||||
* @return {@code true} if this dynamic light source should update, else {@code false}
|
||||
*/
|
||||
boolean angelica$shouldUpdateDynamicLight();
|
||||
boolean angelica$updateDynamicLight(@NotNull IDynamicLightWorldRenderer renderer);
|
||||
|
||||
boolean angelica$updateDynamicLight(@NotNull SodiumWorldRenderer renderer);
|
||||
|
||||
void angelica$scheduleTrackedChunksRebuild(@NotNull SodiumWorldRenderer renderer);
|
||||
void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
/**
|
||||
* Common interface for world renderers that support dynamic lights chunk rebuilds.
|
||||
* Implemented by CeleritasWorldRenderer.
|
||||
*/
|
||||
public interface IDynamicLightWorldRenderer {
|
||||
/**
|
||||
* Schedules a chunk rebuild for the render section at the given chunk coordinates.
|
||||
*/
|
||||
void scheduleRebuildForChunk(int x, int y, int z, boolean important);
|
||||
|
||||
/**
|
||||
* Returns whether this renderer is currently active (has a world loaded).
|
||||
*/
|
||||
boolean isActive();
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.concurrent.cas.CasMap;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
|
||||
import cpw.mods.fml.common.registry.EntityRegistry;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class EntityLightConfig {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/DynamicLights");
|
||||
|
||||
private static final Set<String> disabledClassNames = ConcurrentHashMap.newKeySet();
|
||||
private static final CasMap<Class<?>, Boolean> enabledCache = new CasMap<>();
|
||||
|
||||
private static File configFile;
|
||||
private static boolean initialized = false;
|
||||
|
||||
private EntityLightConfig() {}
|
||||
|
||||
public static void init(@NotNull File configDir) {
|
||||
if (initialized) return;
|
||||
|
||||
configFile = new File(configDir, "angelica-dynamiclights-entities.txt");
|
||||
load();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public static boolean isEntityTypeEnabled(@NotNull IDynamicLightSource source) {
|
||||
if (!initialized) return true;
|
||||
return isEntityTypeEnabled(source.getClass());
|
||||
}
|
||||
|
||||
public static boolean isEntityTypeEnabled(@NotNull Class<?> entityClass) {
|
||||
if (!initialized) return true;
|
||||
|
||||
Boolean cached = enabledCache.get(entityClass);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final boolean enabled = !disabledClassNames.contains(entityClass.getName());
|
||||
Boolean existing = enabledCache.putIfAbsent(entityClass, enabled);
|
||||
return existing != null ? existing : enabled;
|
||||
}
|
||||
|
||||
public static void setEntityTypeEnabled(@NotNull Class<?> entityClass, boolean enabled) {
|
||||
enabledCache.put(entityClass, enabled);
|
||||
|
||||
final String className = entityClass.getName();
|
||||
if (enabled) {
|
||||
disabledClassNames.remove(className);
|
||||
} else {
|
||||
disabledClassNames.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<EntityTypeEntry> getAllEntityTypes() {
|
||||
List<EntityTypeEntry> entries = new ArrayList<>();
|
||||
Set<String> seenClasses = new HashSet<>();
|
||||
|
||||
Map<String, Class<? extends Entity>> stringToClass = EntityList.stringToClassMapping;
|
||||
|
||||
for (Map.Entry<String, Class<? extends Entity>> entry : stringToClass.entrySet()) {
|
||||
String entityId = entry.getKey();
|
||||
Class<?> entityClass = entry.getValue();
|
||||
String className = entityClass.getName();
|
||||
|
||||
if (seenClasses.contains(className)) continue;
|
||||
seenClasses.add(className);
|
||||
|
||||
String modId = getModIdForEntity(entityClass, entityId);
|
||||
String displayName = getDisplayName(entityId, entityClass);
|
||||
boolean enabled = !disabledClassNames.contains(className);
|
||||
|
||||
entries.add(new EntityTypeEntry(displayName, className, modId, enabled));
|
||||
}
|
||||
|
||||
// Sort by mod ID, then by display name
|
||||
entries.sort((a, b) -> {
|
||||
int modCompare = a.getModId().compareToIgnoreCase(b.getModId());
|
||||
if (modCompare != 0) return modCompare;
|
||||
return a.getDisplayName().compareToIgnoreCase(b.getDisplayName());
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String getModIdForEntity(Class<?> entityClass, String entityId) {
|
||||
// Check EntityRegistry for mod entity entries
|
||||
if (Entity.class.isAssignableFrom(entityClass)) {
|
||||
EntityRegistry.EntityRegistration registration = EntityRegistry.instance()
|
||||
.lookupModSpawn((Class<? extends Entity>) entityClass, false);
|
||||
if (registration != null && registration.getContainer() != null) {
|
||||
return registration.getContainer().getModId();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a vanilla entity (no dot in entity ID)
|
||||
if (!entityId.contains(".")) {
|
||||
return "minecraft";
|
||||
}
|
||||
|
||||
// Extract mod ID from entity ID (format: modid.EntityName)
|
||||
int dotIndex = entityId.indexOf('.');
|
||||
if (dotIndex > 0) {
|
||||
return entityId.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static String getDisplayName(String entityId, Class<?> entityClass) {
|
||||
// Use the simple class name, removing package
|
||||
String simpleName = entityClass.getSimpleName();
|
||||
if (simpleName.isEmpty()) {
|
||||
// Anonymous class, use entity ID
|
||||
return entityId;
|
||||
}
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
if (configFile == null || !configFile.exists()) return;
|
||||
|
||||
disabledClassNames.clear();
|
||||
enabledCache.clear();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
|
||||
// Lines are entity class names that are DISABLED
|
||||
disabledClassNames.add(line);
|
||||
}
|
||||
LOGGER.info("Loaded {} disabled entity types from config", disabledClassNames.size());
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to load entity light config: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
if (configFile == null) return;
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(configFile))) {
|
||||
writer.write("# Angelica Dynamic Lights - Disabled Entity Types");
|
||||
writer.newLine();
|
||||
writer.write("# Each line is a fully qualified class name of an entity type that is DISABLED.");
|
||||
writer.newLine();
|
||||
writer.write("# Remove a line to re-enable dynamic lighting for that entity type.");
|
||||
writer.newLine();
|
||||
writer.newLine();
|
||||
|
||||
List<String> sorted = new ArrayList<>(disabledClassNames);
|
||||
Collections.sort(sorted);
|
||||
|
||||
for (String className : sorted) {
|
||||
writer.write(className);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
LOGGER.info("Saved {} disabled entity types to config", disabledClassNames.size());
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to save entity light config: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
|
||||
|
||||
public class EntityLightConfigStorage implements OptionStorage<EntityLightConfigStorage> {
|
||||
|
||||
public static final EntityLightConfigStorage INSTANCE = new EntityLightConfigStorage();
|
||||
|
||||
private EntityLightConfigStorage() {}
|
||||
|
||||
@Override
|
||||
public EntityLightConfigStorage getData() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
EntityLightConfig.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights.config;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityTypeEntry {
|
||||
private final String displayName;
|
||||
private final String className;
|
||||
private final String modId;
|
||||
private boolean enabled;
|
||||
|
||||
public EntityTypeEntry(@NotNull String displayName, @NotNull String className, @NotNull String modId, boolean enabled) {
|
||||
this.displayName = displayName;
|
||||
this.className = className;
|
||||
this.modId = modId;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName + " (" + modId + ") [" + (enabled ? "ON" : "OFF") + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.fog.FogService;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
|
||||
|
||||
public class AngelicaFogService implements FogService {
|
||||
private final float[] fogColorArray = new float[4];
|
||||
|
||||
@Override
|
||||
public float getFogEnd() {
|
||||
return GLStateManager.getFogState().getEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogStart() {
|
||||
return GLStateManager.getFogState().getStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogDensity() {
|
||||
return GLStateManager.getFogState().getDensity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShapeIndex() {
|
||||
return 0; // 1.7.10 only has one fog shape
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFogCutoff() {
|
||||
return getFogEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getFogColor() {
|
||||
final var fogState = GLStateManager.getFogState();
|
||||
final var color = fogState.getFogColor();
|
||||
fogColorArray[0] = (float) color.x;
|
||||
fogColorArray[1] = (float) color.y;
|
||||
fogColorArray[2] = (float) color.z;
|
||||
fogColorArray[3] = fogState.getFogAlpha();
|
||||
return fogColorArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkFogMode getFogMode() {
|
||||
if (!GLStateManager.getFogMode().isEnabled()) {
|
||||
return ChunkFogMode.NONE;
|
||||
}
|
||||
return ChunkFogMode.fromGLMode(GLStateManager.getFogState().getFogMode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.mitchej123.glsm.GLStateManagerService;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class AngelicaGLStateManagerService implements GLStateManagerService {
|
||||
|
||||
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
|
||||
|
||||
private IntBuffer getIntArrayBuffer(int size) {
|
||||
if (intArrayBuffer.capacity() < size) {
|
||||
intArrayBuffer = BufferUtils.createIntBuffer(size);
|
||||
}
|
||||
intArrayBuffer.clear();
|
||||
return intArrayBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== QUERY OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetInteger(int pname) {
|
||||
return GLStateManager.glGetInteger(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetString(int pname) {
|
||||
return GL11.glGetString(pname);
|
||||
}
|
||||
|
||||
// ===================== FRAMEBUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenFramebuffers() {
|
||||
return GL30.glGenFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(int framebuffer) {
|
||||
GL30.glDeleteFramebuffers(framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindFramebuffer(int target, int framebuffer) {
|
||||
GL30.glBindFramebuffer(target, framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCheckFramebufferStatus(int target) {
|
||||
return GL30.glCheckFramebufferStatus(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
|
||||
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
// ===================== SHADER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glCreateShader(int type) {
|
||||
return GL20.glCreateShader(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompileShader(int shader) {
|
||||
GL20.glCompileShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetShaderi(int shader, int pname) {
|
||||
return GL20.glGetShaderi(shader, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetShaderInfoLog(int shader, int maxLength) {
|
||||
return GL20.glGetShaderInfoLog(shader, maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteShader(int shader) {
|
||||
GL20.glDeleteShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateProgram() {
|
||||
return GL20.glCreateProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAttachShader(int program, int shader) {
|
||||
GL20.glAttachShader(program, shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLinkProgram(int program) {
|
||||
GL20.glLinkProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetProgrami(int program, int pname) {
|
||||
return GL20.glGetProgrami(program, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUseProgram(int program) {
|
||||
GLStateManager.glUseProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteProgram(int program) {
|
||||
GL20.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetUniformLocation(int program, CharSequence name) {
|
||||
return GL20.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GL20.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetAttribLocation(int program, CharSequence name) {
|
||||
return GL20.glGetAttribLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindAttribLocation(int program, int index, CharSequence name) {
|
||||
GL20.glBindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
// ===================== VAO OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenVertexArrays() {
|
||||
return UniversalVAO.genVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindVertexArray(int array) {
|
||||
GLStateManager.glBindVertexArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
GLStateManager.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenBuffers() {
|
||||
return GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(int buffer) {
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenTextures() {
|
||||
return GL11.glGenTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
GL11.glGenTextures(buffer);
|
||||
buffer.get(textures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int texture) {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
buffer.put(textures);
|
||||
buffer.flip();
|
||||
GLStateManager.glDeleteTextures(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameter(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
GL11.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPixelStorei(int pname, int param) {
|
||||
GLStateManager.glPixelStorei(pname, param);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthFunc(int func) {
|
||||
GLStateManager.glDepthFunc(func);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
GLStateManager.glColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClear(int mask) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== MOJANG ADDITIONS =====================
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTexture() {
|
||||
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTextureAccessor() {
|
||||
return GLStateManager.getActiveTextureUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBoundTexture(int internalUnit) {
|
||||
return GLStateManager.getBoundTextureForServerState(internalUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveBoundTexture() {
|
||||
return GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportWidth() {
|
||||
return GLStateManager.getViewportState().width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportHeight() {
|
||||
return GLStateManager.getViewportState().height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDepthStateMask() {
|
||||
return GLStateManager.getDepthState().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlendEnabled() {
|
||||
return GLStateManager.getBlendMode().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoundTexture(int unit, int texture) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
}
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextureBinding(int unit) {
|
||||
return GLStateManager.getBoundTextureForServerState(unit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.mitchej123.glsm.RenderSystemService;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class AngelicaRenderSystemService implements RenderSystemService {
|
||||
|
||||
// Shader state (Mojang-specific, tracked here)
|
||||
private final int[] shaderTextures = new int[16];
|
||||
private final float[] shaderColor = {1f, 1f, 1f, 1f};
|
||||
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
|
||||
private float shaderFogStart = 0f;
|
||||
private float shaderFogEnd = 0f;
|
||||
private int fogShape = 0;
|
||||
private float shaderLineWidth = 1f;
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultBlendFunc() {
|
||||
GLStateManager.defaultBlendFunc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnknownBlendState() {
|
||||
// No-op: Angelica tracks blend state accurately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthFunc(int depthFunc) {
|
||||
GLStateManager.glDepthFunc(depthFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GL20.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
|
||||
GL20.glUniformMatrix3(location, transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
|
||||
GL20.glUniformMatrix4(location, transpose, value);
|
||||
}
|
||||
|
||||
// ===================== THREAD ASSERTIONS =====================
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThread() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThreadOrInit() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
// ===================== SHADER STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderTexture(int shaderTexture, int textureId) {
|
||||
if (shaderTexture >= 0 && shaderTexture < 16) {
|
||||
shaderTextures[shaderTexture] = textureId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShaderTexture(int shaderTexture) {
|
||||
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderColor(float red, float green, float blue, float alpha) {
|
||||
shaderColor[0] = red;
|
||||
shaderColor[1] = green;
|
||||
shaderColor[2] = blue;
|
||||
shaderColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderColor() {
|
||||
return shaderColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderLineWidth(float lineWidth) {
|
||||
shaderLineWidth = lineWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderLineWidth() {
|
||||
return shaderLineWidth;
|
||||
}
|
||||
|
||||
// ===================== FOG STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderFogColor(float red, float green, float blue, float alpha) {
|
||||
shaderFogColor[0] = red;
|
||||
shaderFogColor[1] = green;
|
||||
shaderFogColor[2] = blue;
|
||||
shaderFogColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderFogColor() {
|
||||
return shaderFogColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogStart(float start) {
|
||||
shaderFogStart = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogStart() {
|
||||
return shaderFogStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogEnd(float end) {
|
||||
shaderFogEnd = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogEnd() {
|
||||
return shaderFogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFogShape(int shape) {
|
||||
fogShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShape() {
|
||||
return fogShape;
|
||||
}
|
||||
|
||||
// ===================== PROJECTION MATRIX =====================
|
||||
|
||||
@Override
|
||||
public Matrix4f getProjectionMatrix() {
|
||||
return new Matrix4f(projectionMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrth(Matrix4f m) {
|
||||
projectionMatrix.set(m);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
projectionMatrix.get(projectionMatrixBuffer);
|
||||
projectionMatrixBuffer.rewind();
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrigin(Matrix4f m) {
|
||||
setProjectionMatrixOrth(m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds final command buffers from raw command buffers and compiled VBO data.
|
||||
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
|
||||
*/
|
||||
public final class CommandBufferBuilder {
|
||||
|
||||
private CommandBufferBuilder() {}
|
||||
|
||||
/**
|
||||
* Build commands with transform collapsing optimization.
|
||||
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
|
||||
* Uses mergedRanges where consecutive same-transform draws are combined.
|
||||
*/
|
||||
public static void buildFromRawBuffer(
|
||||
CommandBuffer rawBuffer,
|
||||
List<AccumulatedDraw> accumulatedDraws,
|
||||
CommandBuffer finalBuffer) {
|
||||
|
||||
rawBuffer.resetRead();
|
||||
int cmdIndex = 0;
|
||||
int rangeIndex = 0;
|
||||
|
||||
AccumulatedDraw draw;
|
||||
while (rawBuffer.hasRemaining()) {
|
||||
// Read and process the command
|
||||
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
|
||||
|
||||
// Emit draw ranges at this command position
|
||||
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
|
||||
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
while (rangeIndex < accumulatedDraws.size()) {
|
||||
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
|
||||
rangeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a draw range to the output buffer, with transform if needed.
|
||||
*/
|
||||
private static void emitDrawRangeToBuffer(
|
||||
AccumulatedDraw draw,
|
||||
CommandBuffer out,
|
||||
int vboIdx) {
|
||||
|
||||
if (draw.restoreData != null) {
|
||||
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the draw range command
|
||||
out.writeDrawRange(vboIdx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Compat transformation for mod shaders running under core profile.
|
||||
*
|
||||
* <p>Handles:
|
||||
* <ul>
|
||||
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
|
||||
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
|
||||
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
|
||||
* <li>Texture function renames (texture2D → texture, etc.)</li>
|
||||
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
|
||||
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
|
||||
* <li>gl_FrontColor → local variable in vertex shaders</li>
|
||||
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
|
||||
* <li>Version upgrade to 330 core minimum</li>
|
||||
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class CompatShaderTransformer {
|
||||
|
||||
private static final Pattern VERSION_PATTERN =
|
||||
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
|
||||
|
||||
private static final Pattern DEFINE_PATTERN =
|
||||
Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
|
||||
|
||||
/** Compat builtins that trigger AST transformation. */
|
||||
private static final Set<String> COMPAT_BUILTINS = Set.of(
|
||||
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct",
|
||||
"gl_LightSource", "gl_FrontMaterial"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
|
||||
);
|
||||
|
||||
private static final Map<String, String> MATRIX_RENAMES = Map.of(
|
||||
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
|
||||
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
|
||||
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
|
||||
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
|
||||
"gl_NormalMatrix", "angelica_NormalMatrix"
|
||||
);
|
||||
|
||||
private static final Path DUMP_DIR;
|
||||
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
|
||||
static {
|
||||
boolean isDev = false;
|
||||
try {
|
||||
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
|
||||
isDev = Boolean.TRUE.equals(deobfEnv);
|
||||
} catch (Exception ignored) {}
|
||||
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
|
||||
? Paths.get("compat_shaders") : null;
|
||||
}
|
||||
|
||||
private static final int CACHE_SIZE = 32;
|
||||
private record CacheKey(String source, boolean isFragment) {}
|
||||
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
|
||||
new LinkedHashMap<>(32, 0.75f, true) {
|
||||
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
|
||||
return size() > CACHE_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
public static void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a mod shader source for core profile compatibility.
|
||||
*/
|
||||
public static String transform(String source, boolean isFragment) {
|
||||
final boolean needsTransform = needsTransformation(source);
|
||||
String result;
|
||||
if (!needsTransform) {
|
||||
result = fixupVersion(source);
|
||||
} else {
|
||||
final CacheKey key = new CacheKey(source, isFragment);
|
||||
final String cached = cache.get(key);
|
||||
if (cached != null) {
|
||||
dumpShader(source, cached, isFragment, needsTransform);
|
||||
return cached;
|
||||
}
|
||||
|
||||
try {
|
||||
result = transformInternal(source, isFragment);
|
||||
cache.put(key, result);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
|
||||
result = fixupVersion(source);
|
||||
}
|
||||
}
|
||||
|
||||
dumpShader(source, result, isFragment, needsTransform);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean needsTransformation(String source) {
|
||||
// Shaders already at 330+ core don't need compat transformation
|
||||
final Matcher vm = VERSION_PATTERN.matcher(source);
|
||||
if (vm.find()) {
|
||||
final int version = Integer.parseInt(vm.group(1));
|
||||
if (version >= 330 && "core".equals(vm.group(2))) return false;
|
||||
}
|
||||
|
||||
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
|
||||
}
|
||||
|
||||
private static String transformInternal(String source, boolean isFragment) {
|
||||
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
|
||||
int declaredVersion = 110;
|
||||
if (versionMatcher.find()) {
|
||||
declaredVersion = Integer.parseInt(versionMatcher.group(1));
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(declaredVersion, 330);
|
||||
|
||||
// Pre-parse reserved word renaming — prevents ANTLR parse failures
|
||||
source = GlslTransformUtils.replaceTexture(source);
|
||||
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
|
||||
|
||||
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
|
||||
final Transformer transformer = new Transformer(parsedShader.full());
|
||||
|
||||
injectMatrixUniforms(transformer);
|
||||
|
||||
transformFog(transformer, isFragment, source);
|
||||
|
||||
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
|
||||
if (source.contains("gl_FrontLightModelProduct")) {
|
||||
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_LightSource")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_LightSourceParameters {"
|
||||
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
|
||||
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
|
||||
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
|
||||
transformer.rename("gl_LightSource", "angelica_LightSource");
|
||||
}
|
||||
|
||||
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_FrontMaterial")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_MaterialParameters {"
|
||||
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
|
||||
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_FrontColor", "angelica_FrontColor");
|
||||
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
|
||||
|
||||
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
|
||||
transformVertexAttributes(transformer, source);
|
||||
} else {
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("in vec4 angelica_FrontColor;");
|
||||
transformer.rename("gl_Color", "angelica_FrontColor");
|
||||
}
|
||||
}
|
||||
|
||||
// gl_TexCoord[N] varying array → per-index in/out declarations
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
|
||||
for (Integer i : texCoordIndices) {
|
||||
final String qualifier = isFragment ? "in" : "out";
|
||||
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
|
||||
}
|
||||
|
||||
// Fragment output handling + alpha test discard
|
||||
if (isFragment) {
|
||||
transformFragmentOutputs(transformer);
|
||||
}
|
||||
|
||||
// texture-as-variable collision handling
|
||||
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
|
||||
transformer.rename("texture", "gtexture");
|
||||
}
|
||||
if (transformer.hasVariable("angelica_renamed_texture")) {
|
||||
transformer.rename("angelica_renamed_texture", "gtexture");
|
||||
}
|
||||
|
||||
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
|
||||
|
||||
transformer.renameAndWrapShadow("shadow2D", "texture");
|
||||
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
|
||||
|
||||
final String versionDirective = "#version " + targetVersion + " core\n";
|
||||
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
||||
|
||||
// Preserve #define directives
|
||||
final StringBuilder defines = new StringBuilder();
|
||||
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
|
||||
while (defineMatcher.find()) {
|
||||
defines.append(defineMatcher.group().trim()).append('\n');
|
||||
}
|
||||
|
||||
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
|
||||
// Restore pre-parse renames
|
||||
String output = GlslTransformUtils.restoreReservedWords(result.toString());
|
||||
|
||||
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
|
||||
output = fixupQualifiers(output, isFragment);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject matrix uniforms and rename compat builtins.
|
||||
*/
|
||||
private static void injectMatrixUniforms(Transformer transformer) {
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
|
||||
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
|
||||
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
|
||||
|
||||
transformer.rename(MATRIX_RENAMES);
|
||||
|
||||
// Expression replacements
|
||||
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
|
||||
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
|
||||
transformer.replaceExpression("gl_TextureMatrix", "mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform fragment outputs for core profile.
|
||||
*/
|
||||
private static void transformFragmentOutputs(Transformer transformer) {
|
||||
if (transformer.containsCall("gl_FragColor")) {
|
||||
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
|
||||
}
|
||||
|
||||
final Set<Integer> found = new HashSet<>();
|
||||
transformer.renameArray("gl_FragData", "angelica_FragData", found);
|
||||
|
||||
for (Integer i : found) {
|
||||
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
|
||||
}
|
||||
|
||||
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
|
||||
if (found.contains(0)) {
|
||||
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
|
||||
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
|
||||
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
|
||||
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
|
||||
if (!isFragment) {
|
||||
transformer.injectVariable("out float angelica_FogFragCoord;");
|
||||
transformer.prependMain("angelica_FogFragCoord = 0.0;");
|
||||
} else {
|
||||
if (source.contains("gl_FogFragCoord")) {
|
||||
transformer.injectVariable("in float angelica_FogFragCoord;");
|
||||
}
|
||||
}
|
||||
|
||||
transformer.rename("gl_Fog", "angelica_Fog");
|
||||
transformer.injectVariable("uniform float angelica_FogDensity;");
|
||||
transformer.injectVariable("uniform float angelica_FogStart;");
|
||||
transformer.injectVariable("uniform float angelica_FogEnd;");
|
||||
transformer.injectVariable("uniform vec4 angelica_FogColor;");
|
||||
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
|
||||
transformer.injectFunction(
|
||||
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
|
||||
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
|
||||
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
|
||||
*/
|
||||
private static void transformVertexAttributes(Transformer transformer, String source) {
|
||||
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
|
||||
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
|
||||
transformer.rename("gl_Vertex", "angelica_Vertex");
|
||||
}
|
||||
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
|
||||
if (source.contains("gl_Color")) {
|
||||
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
|
||||
transformer.rename("gl_Color", "angelica_Color");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord0")) {
|
||||
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
|
||||
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
|
||||
}
|
||||
if (source.contains("gl_MultiTexCoord1")) {
|
||||
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
|
||||
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
|
||||
}
|
||||
if (source.contains("gl_Normal")) {
|
||||
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
|
||||
transformer.rename("gl_Normal", "angelica_Normal");
|
||||
}
|
||||
if (source.contains("ftransform")) {
|
||||
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
|
||||
if (DUMP_DIR == null) return;
|
||||
final int id = dumpCounter.getAndIncrement();
|
||||
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
|
||||
try {
|
||||
Files.createDirectories(DUMP_DIR);
|
||||
// Capture caller info for identification
|
||||
final String caller = identifyCaller();
|
||||
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
|
||||
+ "\n// Transformed: " + wasTransformed
|
||||
+ "\n// Caller: " + caller + "\n\n";
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
|
||||
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String identifyCaller() {
|
||||
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
|
||||
final String cls = frame.getClassName();
|
||||
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
|
||||
&& !cls.startsWith("java.")
|
||||
&& !cls.equals("org.lwjgl.opengl.GL20")) {
|
||||
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
|
||||
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
|
||||
|
||||
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
|
||||
public static String fixupQualifiers(String source, boolean isFragment) {
|
||||
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
|
||||
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
|
||||
return source;
|
||||
}
|
||||
|
||||
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
|
||||
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
|
||||
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
|
||||
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
|
||||
|
||||
/**
|
||||
* Generate a passthrough vertex shader for a fragment-only program.
|
||||
*
|
||||
* @param fragmentSource the transformed fragment shader source
|
||||
* @return a complete vertex shader source string
|
||||
*/
|
||||
public static String generatePassthroughVertexShader(String fragmentSource) {
|
||||
final StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
// Always have position
|
||||
sb.append("layout(location = 0) in vec4 a_Position;\n");
|
||||
|
||||
// Matrix uniforms for position transform
|
||||
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
|
||||
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
|
||||
|
||||
final StringBuilder varyings = new StringBuilder();
|
||||
final StringBuilder assignments = new StringBuilder();
|
||||
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
|
||||
sb.append("layout(location = 1) in vec4 a_Color;\n");
|
||||
varyings.append("out vec4 angelica_FrontColor;\n");
|
||||
assignments.append(" angelica_FrontColor = a_Color;\n");
|
||||
}
|
||||
|
||||
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
|
||||
final Set<Integer> texCoordIndices = new HashSet<>();
|
||||
while (texCoordMatcher.find()) {
|
||||
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
|
||||
}
|
||||
for (int i : texCoordIndices) {
|
||||
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
|
||||
final int location = i == 0 ? 2 : 3;
|
||||
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
|
||||
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
|
||||
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
|
||||
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
|
||||
varyings.append("out float angelica_FogFragCoord;\n");
|
||||
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
|
||||
}
|
||||
|
||||
sb.append(varyings);
|
||||
sb.append("\nvoid main() {\n");
|
||||
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
|
||||
sb.append(assignments);
|
||||
sb.append("}\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
|
||||
private static String fixupVersion(String source) {
|
||||
final Matcher m = VERSION_PATTERN.matcher(source);
|
||||
if (!m.find()) {
|
||||
return "#version 330 core\n" + source;
|
||||
}
|
||||
|
||||
final int version = Integer.parseInt(m.group(1));
|
||||
final String profile = m.group(2);
|
||||
|
||||
if (version >= 330 && !"compatibility".equals(profile)) {
|
||||
if ("core".equals(profile)) return source;
|
||||
return m.replaceFirst("#version " + version + " core");
|
||||
}
|
||||
|
||||
final int targetVersion = Math.max(version, 330);
|
||||
return m.replaceFirst("#version " + targetVersion + " core");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
|
||||
|
||||
/**
|
||||
* Per-program compat uniform location cache and upload manager.
|
||||
*
|
||||
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
|
||||
*/
|
||||
public class CompatUniformManager {
|
||||
|
||||
// Uniform location indices within the cached array (package-private for tests)
|
||||
static final int LOC_MODELVIEW = 0;
|
||||
static final int LOC_MODELVIEW_INVERSE = 1;
|
||||
static final int LOC_PROJECTION = 2;
|
||||
static final int LOC_PROJECTION_INVERSE = 3;
|
||||
static final int LOC_NORMAL = 4;
|
||||
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
|
||||
static final int LOC_FOG_DENSITY = 6;
|
||||
static final int LOC_FOG_START = 7;
|
||||
static final int LOC_FOG_END = 8;
|
||||
static final int LOC_FOG_COLOR = 9;
|
||||
static final int LOC_IRIS_MODELVIEW = 10;
|
||||
static final int LOC_IRIS_PROJECTION = 11;
|
||||
static final int LOC_IRIS_NORMAL = 12;
|
||||
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
|
||||
static final int LOC_ALPHA_TEST_REF = 14;
|
||||
static final int LOC_SCENE_COLOR = 15;
|
||||
static final int LOC_CLIP_PLANES = 16;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 17;
|
||||
|
||||
// Light source fields: 12 per light × 2 lights = 24 locations
|
||||
static final int LIGHT_FIELDS = 12;
|
||||
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
|
||||
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
|
||||
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
|
||||
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
|
||||
static final int LOC_LIGHT_BASE = 18;
|
||||
|
||||
// Material fields: 5 locations
|
||||
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
|
||||
static final int MAT_FIELDS = 5;
|
||||
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 42
|
||||
|
||||
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
|
||||
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
|
||||
};
|
||||
private static final String[] MAT_FIELD_NAMES = {
|
||||
"emission", "ambient", "diffuse", "specular", "shininess",
|
||||
};
|
||||
|
||||
private static final String[] UNIFORM_NAMES;
|
||||
static {
|
||||
UNIFORM_NAMES = new String[LOC_COUNT];
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
|
||||
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
|
||||
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
|
||||
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
|
||||
for (int li = 0; li < 2; li++) {
|
||||
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] =
|
||||
"angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
for (int fi = 0; fi < MAT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
|
||||
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
|
||||
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
|
||||
private static final Matrix3f normalMatrix = new Matrix3f();
|
||||
private static final Matrix4f scratchMatrix = new Matrix4f();
|
||||
|
||||
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
|
||||
private static final FloatBuffer lightmapMatrixBuf;
|
||||
static {
|
||||
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
|
||||
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// Dirty tracking: skip uploads when state hasn't changed and program is the same
|
||||
private static int lastProgram = -1;
|
||||
private static int lastMvGen = -1;
|
||||
private static int lastProjGen = -1;
|
||||
private static int lastTexMatGen = -1;
|
||||
private static int lastFragmentGen = -1;
|
||||
private static int lastLightingGen = -1;
|
||||
private static int lastClipPlaneGen = -1;
|
||||
|
||||
private CompatUniformManager() {}
|
||||
|
||||
public static void onLinkProgram(int program) {
|
||||
int[] locs = new int[LOC_COUNT];
|
||||
boolean hasAny = false;
|
||||
|
||||
for (int i = 0; i < LOC_COUNT; i++) {
|
||||
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
|
||||
if (locs[i] != -1) hasAny = true;
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
programLocations.put(program, locs);
|
||||
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUseProgram(int program) {
|
||||
if (program == 0) return;
|
||||
|
||||
int[] locs = programLocations.get(program);
|
||||
if (locs == null) return;
|
||||
|
||||
final boolean programChanged = program != lastProgram;
|
||||
lastProgram = program;
|
||||
|
||||
// Matrix uniforms — skip if generation unchanged and same program
|
||||
final int mvGen = GLStateManager.mvGeneration;
|
||||
final int projGen = GLStateManager.projGeneration;
|
||||
final int texMatGen = GLStateManager.texMatrixGeneration;
|
||||
final boolean mvChanged = programChanged || mvGen != lastMvGen;
|
||||
final boolean projChanged = programChanged || projGen != lastProjGen;
|
||||
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
|
||||
if (mvChanged || projChanged || texMatChanged) {
|
||||
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
|
||||
lastMvGen = mvGen;
|
||||
lastProjGen = projGen;
|
||||
lastTexMatGen = texMatGen;
|
||||
}
|
||||
|
||||
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
|
||||
final int fragGen = GLStateManager.fragmentGeneration;
|
||||
if (programChanged || fragGen != lastFragmentGen) {
|
||||
lastFragmentGen = fragGen;
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
final int cpGen = GLStateManager.clipPlaneGeneration;
|
||||
if (programChanged || cpGen != lastClipPlaneGen) {
|
||||
lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
|
||||
if (mvChanged) {
|
||||
// ModelView
|
||||
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
mv.get(mat4Buf);
|
||||
if (locs[LOC_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_MODELVIEW] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// ModelView Inverse
|
||||
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
|
||||
mv.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
|
||||
}
|
||||
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
mv.normal(normalMatrix);
|
||||
normalMatrix.get(mat3Buf);
|
||||
if (locs[LOC_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_NORMAL] != -1) {
|
||||
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projChanged) {
|
||||
// Projection
|
||||
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
proj.get(mat4Buf);
|
||||
if (locs[LOC_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
if (locs[LOC_IRIS_PROJECTION] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Projection Inverse
|
||||
if (locs[LOC_PROJECTION_INVERSE] != -1) {
|
||||
proj.invert(scratchMatrix);
|
||||
scratchMatrix.get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (texMatChanged) {
|
||||
// Iris lightmap texture matrix (constant)
|
||||
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
|
||||
}
|
||||
|
||||
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
|
||||
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
|
||||
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
|
||||
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
if (locs[LOC_FOG_DENSITY] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
|
||||
}
|
||||
if (locs[LOC_FOG_START] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
|
||||
}
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y)
|
||||
.put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
|
||||
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
|
||||
if (locs[LOC_ALPHA_TEST_REF] != -1) {
|
||||
final float ref;
|
||||
if (!GLStateManager.getAlphaTest().isEnabled()
|
||||
|| GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
|
||||
ref = -1.0f;
|
||||
} else {
|
||||
ref = GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
|
||||
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
|
||||
*/
|
||||
private static void uploadSceneColor(int[] locs) {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final LightModelState lm = GLStateManager.getLightModel();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
|
||||
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
|
||||
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
|
||||
.put(mat.diffuse.w);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
final LightState light = lights[li];
|
||||
|
||||
if (locs[base + LF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
|
||||
if (locs[base + LF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
|
||||
if (locs[base + LF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
|
||||
if (locs[base + LF_POSITION] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
|
||||
if (locs[base + LF_HALF_VECTOR] != -1) {
|
||||
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
|
||||
float hx, hy, hz;
|
||||
if (light.position.w == 0.0f) {
|
||||
float len = (float) Math.sqrt(light.position.x * light.position.x
|
||||
+ light.position.y * light.position.y + light.position.z * light.position.z);
|
||||
if (len > 0) {
|
||||
hx = light.position.x / len;
|
||||
hy = light.position.y / len;
|
||||
hz = light.position.z / len + 1.0f;
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
|
||||
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
|
||||
GL20.glUniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
|
||||
}
|
||||
if (locs[base + LF_SPOT_DIRECTION] != -1)
|
||||
GL20.glUniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
|
||||
if (locs[base + LF_SPOT_EXPONENT] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
|
||||
if (locs[base + LF_SPOT_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
|
||||
if (locs[base + LF_SPOT_COS_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
|
||||
if (locs[base + LF_CONSTANT_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
|
||||
if (locs[base + LF_LINEAR_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
|
||||
if (locs[base + LF_QUADRATIC_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMaterial(int[] locs) {
|
||||
// Early exit: if the first material field isn't present, no material uniforms exist
|
||||
if (locs[LOC_MAT_BASE] == -1) return;
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
|
||||
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
|
||||
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
|
||||
GL20.glUniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
clipPlaneBuf.flip();
|
||||
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onDeleteProgram(int program) {
|
||||
programLocations.remove(program);
|
||||
}
|
||||
|
||||
public static boolean hasProgram(int program) {
|
||||
return programLocations.containsKey(program);
|
||||
}
|
||||
|
||||
public static int[] getLocations(int program) {
|
||||
return programLocations.get(program);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
* Copyright 2017 Valve Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
|
||||
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Original author: Samuel Pitoiset <samuel.pitoiset@gmail.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
}
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
}
|
||||
|
||||
public int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
public void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1270 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user