Compare commits

...
340 Commits
Author SHA1 Message Date
Jason Mitchell d7d2228a6c Target OpenGL 3.3 core profile (#1412) 2026-03-02 22:18:11 -08:00
Alexdoru cf9993fce4 change rfb exclusions (#1440) 2026-03-02 15:11:34 -08:00
Alexdoru 3ae57ca594 Fixes for backhand compat (#1437) 2026-03-01 13:04:11 -08:00
sisyphussyandMustafarYasmin 9816cb2148 Replace framebufferRender with a VBO (#1432)
Co-authored-by: MustafarYasmin <[email protected]>
2026-03-01 13:03:27 -08:00
Alexdoru b1e496f56d remove boxing unboxing in RenderPassHelper 2026-03-01 13:02:04 -08:00
danyadevandAlexdoru c8e50227fb Speedup asm redirector transformers and fix missing classes in AngelicaRedirector (#1433)
* add shouldTransform & make it significantly faster

* do not optimize non-FRB CeleritasBlockTransformer

* move closer to usage

---------

Co-authored-by: Alexdoru <[email protected]>
2026-02-28 20:12:29 +01:00
Jason Mitchell 066381a7c0 Stop demoting, per face, once we see a non demoted material 2026-02-27 21:29:20 -08:00
Jason Mitchell 9a0ea1ad65 Workaround apparent JVM 25 bug... 🤣 2026-02-27 21:29:20 -08:00
Alexdoru 8e26a0179b Speedup block transformer by transforming every class directly (#1420)
* speedup block transformer by transforming every class directly

* stop using the cstpoolparser.add method
2026-02-24 15:45:35 +01:00
Alexdoru 1d96af8e3a re-enable Block Transformer when not running with rfb (#1418) 2026-02-23 14:58:40 -08:00
Yan Huang f7eb86cdb6 Parallelizing CTMUtils (#1393) 2026-02-23 20:21:35 +00:00
Alexdoru b38fafa831 ASM redirectors micro optimizations (#1416)
* redirector transformer micro optimizations

* use map for block fields redirect
2026-02-23 20:20:38 +01:00
Jason Mitchell 7dc2625f8c Maybe less leaky (#1414) 2026-02-23 08:57:21 -08:00
Jason Mitchell 3d97977050 Maybe fix holo inventory again - v2 - final.xlsx (#1413) 2026-02-22 20:37:08 -08:00
Eclipse-Sol 5f41c60bc2 Remove force blend as commit 0325a96b fixed this properly. (#1409) 2026-02-22 11:35:35 -08:00
Jason Mitchell 0325a96bc2 Add unknown blend state and BufferBlendOverride handling from modern Iris (#1408) 2026-02-21 15:27:22 -08:00
Caedis ad123d0bd5 Now with 200% more screen shake (#1402) 2026-02-18 09:20:47 -05:00
Caedis 4b89997985 Add slider for hurt screen shake intensity (#1401) 2026-02-18 04:23:57 +01:00
Death Fuel f5c9fa3301 slightly less stupid glyph size loading (#1390) 2026-02-15 18:49:44 -08:00
Death Fuel d6a6d3b525 Less logging + small refactor (#1379)
* less logging

* refactor

* missed a spot

* consistency
2026-02-15 18:48:51 -08:00
Ruling-0 f079bed977 TESRs Translucency Sorting (#1384)
* Iterate blocks in region towards the camera

* Sort translucent TESRs when translucency sorting is enabled
2026-02-15 18:48:05 -08:00
Caedis c9ec40e94b use mc.renderViewEntity instead of mc.thePlayer in shader shadow rendering (#1392) 2026-02-15 18:47:19 -08:00
embeddedt 8380f3853d Detect quads rendered by vanilla AO pipeline and only recalculate lighting for those (#1391)
* Detect quads rendered by vanilla AO pipeline and only recalculate lighting for those

* Handle oldLighting correctly
2026-02-15 18:46:58 -08:00
Yan Huang 534ae6b05c Remove support for NotEnoughIds-Legacy and update BetterCrashes dependency version, as the old files have been removed from the GTNH source. (#1388) 2026-02-15 09:35:09 -08:00
Jason Mitchell 68d523d3df Sign Culling (#1386) 2026-02-14 04:09:00 -08:00
Jason Mitchell 2f22a7b3ad Swap vanilla clipping helper with joml (#1385) 2026-02-13 14:29:31 -08:00
Jason Mitchell 7b79b90dd6 Fix crash without speedupAnimations enabled (#1377) 2026-02-09 12:00:36 -08:00
Jason Mitchell 417f23f826 Add runtime toggle for HUD Caching, and disable by default (for now at least) (#1374) 2026-02-07 14:44:49 -08:00
TeterosandTeteros ca3c46c873 Add PFAA Geologica cobble/bricks to the BlockCrackFix Whitelist (#1370)
Co-authored-by: Teteros <[email protected]>
2026-02-06 11:36:41 -08:00
Jason Mitchell 82fa9ade2b Add new emitter modes (#1368)
* Indirect
* Individual
2026-02-05 20:58:10 -08:00
Jason Mitchell aaab8fd0bc Thread safe biome cache & VAO Redirection (#1367)
* Thread safe biome cache

* VAO Redirection
* Delegate GLSM VAO calls to UniversalVAO
* Redirect usages of UniversalVAO & LWJGLService VAO functions to GLSM
* Avoid infinite recursion
2026-02-05 13:56:44 -08:00
Jason Mitchell bd56ccc4b8 Totally not experimental 2026-02-04 22:42:03 -08:00
sisyphussy e17f60308d Display List Refactor (#1360) 2026-02-04 10:39:20 -08:00
Kotmatross 1dd98978ad [Hotfix] Fix NTM:Space mixin targets for X5593_H261 (#1356) 2026-02-02 10:02:36 -08:00
Jason Mitchell dae986eb0b Fix crash with clearBufferSubData (#1355) 2026-02-01 12:41:53 -08:00
Jason Mitchell 5fdf84edd0 Camera and Menu Tweaks (#1354) 2026-02-01 12:14:32 -08:00
Jason Mitchell b19d023c11 Bump gtnhgradle 2026-02-01 09:26:19 -08:00
Jason Mitchell 41eafbd151 GLSL Downgrading (#1348) 2026-01-31 13:23:02 -08:00
sisyphussy dd970b4c6a Fix nested display lists rendering twice (#1349)
* Fix nested display lists rendering twice

* cleanup
2026-01-31 05:45:48 -08:00
Eclipse-Sol f79ad7f22a Fix arrows inheriting item material IDs (#1347) 2026-01-30 18:35:04 -08:00
Jason Mitchell 6947ffa3e2 Add IS_IRIS define as an option. 2026-01-30 18:10:27 -08:00
Jason Mitchell 969d2c3710 Backports tessellation shader support, safe zone shadow culling, and shader pack rendering directives. 2026-01-30 18:10:27 -08:00
Jason Mitchell c5988d29c3 Fix camera position and nether portal 3rd party rendering 2026-01-30 18:10:27 -08:00
Jason Mitchell dc68c2c265 Fix end portal w/ Shaders 2026-01-30 18:10:27 -08:00
Jason Mitchell 51533a81ec Misc Shaderpack loading fixes (#1343) 2026-01-30 14:53:41 -08:00
Eclipse-Sol 90eabb5d40 Add hardcoded entities and player_cape (#1327) 2026-01-30 14:53:26 -08:00
sisyphussyandJason Mitchell 74843cb1f9 Glc redirects (#1340)
Co-authored-by: Jason Mitchell <[email protected]>
2026-01-30 14:50:54 -08:00
Death Fuel 5e0344a529 remove unnecessary resource refresh (#1342) 2026-01-30 14:40:51 -08:00
Jason Mitchell 3cdd934987 First pass at setup/begin pass support (#1335) 2026-01-29 07:53:04 -08:00
Léa Gris 9d90ff137c fix(render): adapt hodgepodge fixBottomFaceUV (#1334) 2026-01-28 10:55:53 -08:00
Jason Mitchell bee9541797 Shadow Compute Shaders (#1333) 2026-01-28 00:03:04 -08:00
Jason Mitchell b75a5d8f7d Ya that won't work (#1332) 2026-01-27 15:32:42 -08:00
Jason Mitchell 8d3ab41c53 Use entity position instead of camera pos (#1330) 2026-01-26 22:19:54 -08:00
Jason Mitchell 48d032e654 Tweaks (#1329) 2026-01-26 21:08:09 -08:00
sisyphussy 1968914582 Fix hud flickering (#1325) 2026-01-26 10:19:49 -08:00
Guus Waals a70bd76ff3 Fix deadlock in CTMUtils (#1189) 2026-01-26 10:08:32 -08:00
mist475 db919b5f37 add biomeDictionary and lotr compat to BiomeUniforms (#1324) 2026-01-26 09:58:08 -08:00
MalTeeez cde47ea423 Add option to disable f3 additions (#1308) 2026-01-26 09:56:11 -08:00
Jason Mitchell 5ef6cb11c0 Don't double check infinite bounds (#1321) 2026-01-25 21:13:14 -08:00
Eclipse-Sol b7015bf10e Adds "name_tag" feature (#1318) 2026-01-25 20:00:49 -08:00
Jason Mitchell 46602ab921 Chunk Thread Saftey (#1320) 2026-01-25 19:58:33 -08:00
Jason Mitchell 63e9fa0df1 Dynamic Lights Improvements (#1319) 2026-01-25 19:07:11 -08:00
Eclipse-Sol dcea970a77 Various Shader Fixes (#1315) 2026-01-25 16:56:54 -08:00
Jason Mitchell b29240d6da Use the proper blockAccess on the main thread (#1317) 2026-01-25 16:55:45 -08:00
Jason Mitchell 9d5a02ed21 Optimize TileEntities (#1314) 2026-01-25 10:54:45 -08:00
Jason Mitchell b454483aae Add a nvidia workarounds from celeritas (#1286) 2026-01-24 18:52:52 -08:00
Jason Mitchell 72b13543c6 AABB Caching (#1309) 2026-01-24 18:52:22 -08:00
Jason Mitchell 36d98fcb22 Fix currentRenderedItemId for ItemBlocks (#1307) 2026-01-24 12:43:03 -08:00
Jason Mitchell 44c5b9617a Lighting fixes (#1306) 2026-01-24 12:23:59 -08:00
Kotmatross f3f1677db8 [Shaders] Fix NTM:Space sunset not rendering {2.0 regression} (#1275) 2026-01-24 19:47:30 +00:00
Jason Mitchell d396b611ad Buffer fixes (#1304) 2026-01-24 11:30:05 -08:00
Jason Mitchell 08f4c0f9db Make feature flag defines available in shader.properties preprocessing (#1303) 2026-01-24 10:10:13 -08:00
Eclipse-Sol 50b0d22dc2 Fix entity material handling. (#1298) 2026-01-23 20:24:27 -08:00
Jason Mitchell 7fed54264e No more OpenGLHelper (#1297) 2026-01-23 19:48:52 -08:00
embeddedt be87401be6 Improve behavior of Celeritas-based lighting pipeline 2026-01-23 19:37:57 -08:00
Jason Mitchell 94254006df Properly cleanup (#1294) 2026-01-23 18:19:46 -08:00
Jason Mitchell f79774df65 Respect shader render layer overrides (#1293) 2026-01-23 16:43:01 -08:00
Eclipse-Sol 2f2fc22df3 Add support for dimension.properties (#1283) 2026-01-22 19:17:16 -08:00
Jason Mitchell 26300bbb1b Celeritas Smooth Lighting is now default and working. UseSeparateAO, Emissives, and oldLighting=false now work. 2026-01-22 16:32:15 -08:00
Jason Mitchell 88e4d2fd6c Use Separate AO & SmoothLightPipeline 2026-01-22 16:32:15 -08:00
Jason Mitchell bb52476d27 Custom images, textures, SSBO, and ShaderPack/ShaderProperties backports 2026-01-22 16:32:15 -08:00
Jason Mitchell 6d60477b93 Actually allocate the (minimal) display lists so we stop clobbering the hand renderer on shader reload (#1278) 2026-01-19 13:05:27 -08:00
Jason MitchellandEclipse-Sol 362724610c Add Various Features (#1209) (#1277)
Co-authored-by: Eclipse-Sol <[email protected]>
2026-01-19 13:03:56 -08:00
Jason Mitchell ea4a3cdf0b Fix a race condition (#1273) 2026-01-16 16:46:36 -08:00
Jason Mitchell 65959268d0 Support glCallLists (#1265) 2026-01-13 10:41:09 -08:00
Jason Mitchell 58d820cd7e Fix OSX F3 crash
Fixes #1256
2026-01-13 09:37:28 -08:00
Death Fuel 685d68bd9c Improve glyph replacement logic (#1247) 2026-01-12 15:17:20 -08:00
Jason MitchellandCaedis ad63d2d62c Dynamic light improvements (#1255)
Co-authored-by: Caedis <[email protected]>
2026-01-10 23:50:32 -08:00
Jason Mitchell d2730ccca8 Better video settings search (#1254) 2026-01-10 21:25:03 -08:00
Jason Mitchell e9257b8739 Fixes (#1251) 2026-01-10 15:20:19 -08:00
Jason Mitchell 3984225faa Better OSX J8 Support (#1248) 2026-01-09 12:58:42 -08:00
Jason Mitchell 52db6b56d6 Fix GLSL 120 support (#1243) 2026-01-08 21:34:40 -08:00
Jason Mitchell 34bfe513f1 Maybe less deadlock-y (#1241) 2026-01-08 20:53:32 -08:00
Jason Mitchell 42d7ed58aa 2.0.0 Alpha1 Fixes (#1240) 2026-01-08 19:26:56 -08:00
Jason Mitchell 9a4f0f9b99 Adapt some Beddium features & add FPS Cap info 2026-01-07 13:22:16 -08:00
Jason Mitchell be90a01725 Angelica 2.0
* GLStateManagerService & RenderSystemService
* Wire up Celeritas
* Iris working
* Dynamic lights working
* Goodbye Sodium 0.2
* Add Multi-Threading for check meshing again
2026-01-07 13:22:16 -08:00
Jason Mitchell e5e8250d44 JVMDG 2026-01-07 13:22:16 -08:00
Wilhelm Schuster 062a03ffc2 Add more menu backgrounds (#1225) 2026-01-07 12:46:33 -08:00
Jason Mitchell 9f5afaaec3 Better Splash support (#1220) 2026-01-05 10:24:12 -08:00
Death Fuel b05f17a477 Disable custom fonts with GraphicsEnvironment.isHeadless() (#1218)
* disable custom fonts with GraphicsEnvironment.isHeadless()
2026-01-04 10:19:17 -08:00
Death Fuel 57caf08212 remove a horizontal shift (#1216) 2026-01-03 13:34:23 -08:00
Jason Mitchell 085ec79c21 Set defaults once per TE pass instead of once per TE (#1213) 2026-01-03 12:46:40 -08:00
Wilhelm Schuster 38641268ae Random menu backgrounds (#1217) 2026-01-03 12:01:48 -08:00
Jason Mitchell fe8e723638 Update 000-report-bug.yml 2025-12-31 22:35:40 -08:00
Jason Mitchell 16debfd518 Move EntityID shader mixin to shaders, and optimize (#1208) 2025-12-29 18:23:48 -08:00
Jason Mitchell 8cfc3e1f4a Why were these bypassing GLSM? (#1207) 2025-12-28 22:42:39 -05:00
Eclipse-Sol 2c808fae43 Update entity.properties formatting (#1197)
* Update entity.properties formatting

1.7.10 doesn't have namespaces like modern does, this code updates this so that Angelica can handle names such as XPOrb or Creeper.

* Allows shaders to target Lightning Bolts

Lightning bolts within 1.7.10 are not registered, this allows shader devs to target the lightning bolt like it does in modern.

* Additional fixes

* Adds back getEntityId as a fallback
2025-12-27 09:04:37 -08:00
Eclipse-Sol 1649c3c0c6 Adds entity_flame ID to Angelic (#1202) 2025-12-26 13:05:08 -08:00
Jason Mitchell b84286ba3b Nvidia test fix (#1201)
confirmed working in #meta-dev (nvidia/windows 11)
2025-12-25 13:36:19 -08:00
Jason Mitchell ee6c79802a Display List Overhaul (#1200) 2025-12-24 20:30:48 -08:00
Jason Mitchell d3366ad31a Update Buildscript (#1199) 2025-12-23 19:56:48 -08:00
Jason Mitchell 92b6dcc010 Texture cache fixes (#1198) 2025-12-23 17:38:52 -08:00
Jason Mitchell 89132f3ef9 Parallel shader transformation and use Transformer instead of Utils (#1191) 2025-12-23 09:11:32 -08:00
Eclipse-Sol f0d45b0d62 Fix metadata parsing by removing space in split (#1196) 2025-12-23 01:53:31 +00:00
Jason Mitchell 173d986a5b Add a test for the server side texture cache fix (#1194) 2025-12-21 12:14:53 -08:00
mist475 473a690054 fix #1190: remove remap = false from GuiIngameForge (#1192) 2025-12-21 10:34:01 -08:00
sisyphussy 2515b3520f Always delete texture parameter cache (#1193) 2025-12-21 10:33:44 -08:00
glowredman a32f9ca244 Only catch Exceptions not Errors (#1186) 2025-12-21 14:14:50 +01:00
Jason Mitchell 941fc88484 Shader boot fix + more DSA (#1184) 2025-12-16 16:54:50 -08:00
Creeper-banner 4106dd3865 Fix broken text rendering after certain characters (#1178)
* Font render fix

* re-fix

* Update fix for consistency
2025-12-14 07:10:12 -08:00
Jason Mitchell 61f6aa19a8 glDrawBuffers Support 2025-12-09 18:46:47 -08:00
Jason Mitchell 77875f7f91 Stop recording normals... we're already baking them into a VBO 2025-12-08 15:28:07 -08:00
Jason Mitchell 241c32def1 Add some transformer exclusions 2025-12-08 14:57:27 -08:00
sisyphussy a8107f97e8 Add VAO's (#1126) 2025-12-07 16:51:32 -08:00
Death FuelandJason Mitchell 8bec433699 GT5 custom glyph handling and visual fixes (#1148)
Co-authored-by: Jason Mitchell <[email protected]>
2025-12-06 09:48:03 -08:00
Kotmatross 2d39fb2397 [BCF] Fix z-fighting on GT ores 2: here we go again edition (#1161) 2025-12-06 08:17:26 -08:00
Jason Mitchell cfd8eb1411 fix compatability profile detection (#1159) 2025-12-05 17:01:20 -08:00
Jason Mitchell 59c270a5b9 glFrontFace, glHint, and glBlendColor (#1156) 2025-12-05 16:11:04 -08:00
Jason Mitchell 87ac03281c Add clear depth in display lists 2025-12-04 09:21:22 -08:00
Jason Mitchell 36b82fa2ce Update README.MD 2025-12-03 16:01:26 -08:00
Jason Mitchell 2fc2e2e38d Fix some small things
* Bump nhlib dep due to crash when Iris is disabled
* Use jabel for tests
* use joml isIdentity
* remove Integer usage
2025-12-03 10:54:20 -08:00
Jason Mitchell 33de1de9c4 Add display list compilation/optimization (#1141) 2025-12-02 21:43:17 -08:00
Jason Mitchell 55811b9d7b Set some defaults for tile entity rendering: (#1144) 2025-12-02 09:54:48 -08:00
Alexdoru a10d92f52e [Memory-opti:fix leak] Fix world leak in MixinRenderBlockFluid (#1131) 2025-11-30 10:26:49 -08:00
Christoffer Hjalmarsson 7d2fe12ce9 Fix PBR atlas failing to load all textures (#1132) 2025-11-30 10:26:09 -08:00
Death Fuel 006ded801e Font config UI improvements (#1128) 2025-11-30 10:25:21 -08:00
Alexdoru 03afd6939e Revert "limit the amount of dropped items rendered exactly to the config value (#973)" (#1125)
This reverts commit 891715b113.
2025-11-19 19:41:29 +01:00
sisyphussy adc5421e00 Optimize CloudRenderer (#1123) 2025-11-18 09:38:27 -08:00
sisyphussy dfe60e4b3e Optimize hud caching (#1103) 2025-11-17 19:52:23 +00:00
MalTeeez 72c6cdfa60 Disable conflicting mixins when DragonAPI is present (#1119)
* Exclude dapi from problematic mixins

* Don't force hodgepodge at runtime
2025-11-15 18:27:44 -05:00
Nikita Manchuk c5daea86f8 Fix: entity/item textures and lighting break with shaders when name tags render (batched font GL state) (#1091) 2025-11-10 08:57:13 -08:00
Kotmatross f8958ee990 [BCF] Fix z-fighting on GT ores (#1113) 2025-11-09 16:19:04 -08:00
sisyphussy 073260060b Fix GLSM display list (#1110) 2025-11-08 19:20:20 -08:00
Jason Mitchell 771fee124d Fall back to display lists if not using VBOs 2025-11-08 13:05:37 -08:00
NealDeal34 680285bf54 Update zh_CN.lang (#1036) 2025-11-08 10:19:48 -08:00
Tomasz Sobczyk df8784536e Use separate queue and workers for chunk renders that can block and wait for the main thread. Increase number of renders done on main thread to 16. (#1084) 2025-11-07 15:11:24 -08:00
KotmatrossandJason Mitchell c8991d810c Port FIX_BLOCK_CRACK from FalseTweaks 2 (#1023)
Co-authored-by: Jason Mitchell <[email protected]>
2025-11-07 15:07:04 -08:00
Alexdoru e3b02642ac split AngelicaRedirector from SodiumBlockTransform (#1018) 2025-11-07 14:49:46 -08:00
ALongStringOfNumbers 2759dd6689 Increase Scroll Speed in Settings GUIs (#1106) 2025-11-07 14:48:09 -08:00
Kotmatross 996dbaef2c [Shaders] Basic compatibility with NTM:Space skybox & more (#1096) 2025-11-07 14:44:38 -08:00
Jason Mitchell 72ca675c93 Uniform Additions and fixes (#1102) 2025-11-07 14:39:10 -08:00
sisyphussy e6e230e270 Add more VAO tests (#1093) 2025-11-07 19:42:05 +00:00
ALongStringOfNumbers 43d7282ba5 Set maxShiftBy to fix some log warnings (#1105) 2025-11-07 17:47:16 +00:00
ah-OOG-ah 4b2d57a32d Update model API (#842)
* Bump GTNHLib

* Change test block's properties

This makes it easier to test the block overlay

* Update VanillaModels.java

# Conflicts:
#	src/main/java/com/gtnewhorizons/angelica/models/VanillaModels.java

* Revert rename

* Rename models

* Sledgehammer out removed abstractions

* Switch to ModelISBRH

* Replace old models

With a single new and interesting model!

* Adapt to GTNHLib

* Remove dead code

* Fix JSON model detection + remove dead code

* Remove direct model renderer

* yet more yote

* Remove ColorABGR

* Remove unused line methods

* Yeet color fully

* publicize nextLong

Technically you don't *need* this on Java 8, but it silences a warning and doesn't break anything so 🤷

* yoten

* Register Angelica for automatic texture loading

* Remove TX-Loader dep

* Make the list mutable

* Remove rebase mistake

* Bump BS + deps
2025-11-06 17:40:45 +00:00
Darren Eberly 06bf71126f Add several missing built-in uniforms (#1101) 2025-11-05 15:38:57 -08:00
Darren Eberly 4c0f2f2685 Disable hardcoded custom uniforms by default 2025-11-05 12:25:18 -08:00
Jason Mitchell 78334717e7 Fix TAA:
* Return an immutable view of the underlying camera position to avoid mutating it
* Update Camera Uniforms to utilize the view instead of mutating in place
2025-11-05 09:59:47 -08:00
MalTeeez a77765a2c9 Allow avoiding other mods spash screen handlers (#1080) 2025-10-31 16:06:28 -07:00
sisyphussy 1a9fe45ee9 Add VAO test (#1089) 2025-10-31 10:10:01 -07:00
Jason Mitchell 21ef5905c8 Roll back some of the hacks (#1085) 2025-10-30 20:50:43 -07:00
ABKQPO 5b67eaa474 Quick fix PBR Resourcepack loader error (#1043) 2025-10-29 08:01:52 -07:00
SuperSoupr c9d59c3b90 Allow overriding of blend function in batching font renderer (#1075) 2025-10-29 07:59:27 -07:00
Death Fuel fa7ff74fec make an exception for the splash screen (#1069) 2025-10-29 07:57:41 -07:00
Kotmatross a34a3ebd39 Fix missing depth buffer writing before GUI rendering (#1073) 2025-10-29 07:56:45 -07:00
Nikita Manchuk 2e7476d287 render: set frustum position before BE culling (#1077) 2025-10-29 07:23:25 -07:00
Death Fuel 3244071012 font shader changes (#1071) 2025-10-24 14:44:32 -07:00
DarkShadow44 7035dbde02 Rework ANGELICA_VERSION into an integer (#873) 2025-10-22 01:19:15 +00:00
sisyphussy 97ae04c2fa Fix CME (#1008) 2025-10-20 14:38:30 -07:00
sisyphussy e7fa7f62d0 Fix framebuffer depth not clearing (#1051) 2025-10-20 14:36:02 -07:00
Wilhelm Schuster ea39712749 Add null check for zoom reset (#1061) 2025-10-20 14:35:29 -07:00
Death Fuel 66dd3eaa94 Custom/Smooth Font follow-up (#1040) 2025-10-20 14:35:06 -07:00
ah-OOG-ah 270697cd10 Refactor vendor detection (#1049)
* Refactor vendor detection

Only one vendor can be active at any given time, so make it an enum.

* lowercase vendor string
2025-10-13 15:48:21 -07:00
sisyphussy 2b53a851ef Fix intel build tests (#1048) 2025-10-13 10:31:48 -07:00
Jason Mitchell 34b6efde7b Maybe fix TAA (#1047) 2025-10-12 17:40:41 -07:00
Jason Mitchell 86edb721da GLSM updates (#1046) 2025-10-12 14:51:56 -07:00
Kotmatross 39c5f4bf7b Fix missing GL20 in cstPoolParser (#1042) 2025-10-09 12:26:55 -04:00
Death Fuel 5dc54bb1da remove implicit float cast (#1037) 2025-10-05 05:03:50 -07:00
Death Fuel 7924769ab1 Custom/Smooth Font (#1017) 2025-10-04 11:20:38 -07:00
sisyphussy 8afe2e2ccf Optimize in-world item rendering (#1034) 2025-10-01 09:05:06 -07:00
Wilhelm Schuster e8dd60e088 Check if module is enabled before resetting zoom (#1032) 2025-09-28 09:01:23 -07:00
Kotmatross 9a621405ba Fix Tileentities not casting shadows (#1024) 2025-09-26 12:04:46 -07:00
slprime 45a677030d Disabled force unicode (#1029) 2025-09-26 09:59:08 -07:00
Wilhelm Schuster 4d531022d2 Improve zoom key handling (reset on GUI enter and compatibility with mouse buttons) (#1016) 2025-09-26 09:41:08 -07:00
Pedro Ortiz Bledow 48ba2160ad Fix fast clouds rendering with setting off after game restart (#1028) 2025-09-26 13:04:47 +02:00
Eldrinn-Elantey 334ec81f9b Font rendering: configurable shadow & glyph fix (#1011) 2025-09-03 08:53:39 -07:00
Alexdoru 86c726e50b Revert "Port FIX_BLOCK_CRACK from FalseTweaks (#999)" (#1005)
This reverts commit dce5013ffb.
2025-08-16 19:09:18 -07:00
AlexdoruandJason Mitchell e6d778dad3 ASM Housekeeping (#1003)
Co-authored-by: Jason Mitchell <[email protected]>
2025-08-16 17:29:50 -07:00
Kotmatross dce5013ffb Port FIX_BLOCK_CRACK from FalseTweaks (#999) 2025-08-16 22:10:55 +00:00
glowredman 6eef63619c Update BatchingFontRenderer.java (#1002) 2025-08-10 22:27:29 +02:00
Alexdoru a8153fa0f7 remove access transformer to invoke EntityRenderer#getNightVisionBrightness (#1001) 2025-08-10 16:31:30 +00:00
Niclas Klugmann 49c04cf776 German translation (#997) 2025-07-27 20:02:26 +07:00
Alexdoru 85b0277a66 use EnumChatFormatting and not Realms enum to avoid crash (#996) 2025-07-21 10:59:04 +02:00
Alexdoru 0525b31262 Cache Tessellator#instance in local var (#995) 2025-07-18 12:44:03 -07:00
boubou19 fdb2053c50 Speed up BlockError event registration (#993) 2025-07-17 20:38:13 +02:00
Alexdoru 31abbcce32 Convert to new IMixins and MixinsBuilder system (#991) 2025-07-15 08:54:06 -07:00
063b2242e8 Initial FluidLogged compat (#985)
* Initial FluidLogged compat

* FluidLogged works(probably mostly)

* Make FluidLogged optional

* Remove a leftover print

* Fix fluidlogged fluids rendering incorrectly with shaders on

* Rmemove unused code

* Cleanup unused imports

---------

Co-authored-by: embeddedt <[email protected]>
Co-authored-by: Jason Mitchell <[email protected]>
2025-07-09 10:31:45 -04:00
Rad 577f905dc5 Update README.MD (#981) 2025-07-06 13:56:20 -07:00
Jason MitchellandAlexdoru 071d2d4a39 Add a guard to only init once (#984)
Co-authored-by: Alexdoru <[email protected]>
2025-07-01 10:33:49 -07:00
Alexdoru 891715b113 limit the amount of dropped items rendered exactly to the config value (#973) 2025-06-16 07:55:28 +02:00
jss2a98aj 30a35ef25e Misc. CTM resource tweaks 2025-06-13 18:04:31 -07:00
jss2a98aj 33453b3b8c Fix crash with OpenAutism
Brandyyn says this fixes the crash but I have not tested it myself.
2025-06-13 18:04:31 -07:00
jss2a98aj 62e699f6d0 Remove unneeded debug print 2025-06-13 18:04:31 -07:00
Jason Mitchell f107d173b7 Stop loading HoloInventory too early in the compat class, which was causing fake fluids to not render properly. 2025-06-09 14:22:12 -07:00
glowredman d0948b36f3 No longer use Reflection for SplashProgress.fontRenderer (#966)
* Access SplashProgress font renderer via mixins

* Use AT
2025-06-07 12:07:14 +02:00
YannickMG ae1211f280 Battlegear 2 for Backhand compat 2025-05-31 08:01:28 -07:00
David Lindström 3d76cb3073 Use instanceof instead of direct cast to protect against scenarios where mods like FMB use the same rendering code for world, inventory and preview, where the two latter happens in passes that are not rendered using a CapturingTesselator 2025-05-27 14:10:07 -07:00
kotmatross28729 45fb120223 Fix COLOR_BUFFER_BIT leak in ShaderPackScreen 2025-05-25 08:25:59 -07:00
Jason Mitchell d7a196eb5c Use invokeExact instead of invoke 2025-05-25 08:25:05 -07:00
Jason Mitchell 4d53e2af9b Holoinventory compat via reflection 2025-05-25 08:25:05 -07:00
Jason Mitchell cb6336a365 Backhand Compat via Reflection 2025-05-25 08:25:05 -07:00
Jason Mitchell 5095021e0e Capture shader outline 2025-05-16 18:08:27 -07:00
Caedis f88ecbf68d Replace Baubles with Baubles Expanded (#949) 2025-05-13 10:26:50 -04:00
Darren Eberly 7da9b9eb1c Compat for security craft (#948) 2025-05-12 23:34:15 -04:00
Pxx500 02c394108f enable ctm, disable everything unnecessary 2025-05-05 12:44:46 -07:00
Fabian Maurer 720aaca1b7 Add functionality to fake blockId for shaders 2025-04-24 14:11:24 -07:00
Midnight145 1e71b1c4d8 liteloader docs update 2025-04-17 17:41:58 -07:00
くぁーりぃ df4c423e93 fix config load (#914) 2025-04-07 19:10:39 +00:00
JuliaandJason Mitchell 78e0074e5a Apply linear filter to unifont glyphs at odd scale factors (#907)
* Apply linear filter to unifont glyphs at odd scale factors

* Prevent glyph texture bleeding by adjusting UV coordinates with a slight offset

* Tiny bit of tuning

---------

Co-authored-by: Jason Mitchell <[email protected]>
2025-04-07 17:37:46 +02:00
くぁーりぃ 0350ea2cb6 fix stencil buffer does not exist (#912) 2025-03-20 07:53:27 -07:00
Darren Eberly bed724ba59 Support for custom uniforms (#902)
* wip backport of custom uniforms

* Functioning custom uniforms maybe?

* Workon separating dynamic/non-dynamic uniforms and making variables available to custom uniforms (currently the JVM dies horribly)

* wip

* It no longer crashes horrifically

* star imports
2025-03-18 10:04:31 -07:00
Jason Mitchell 6fa973dbc8 Adds two new overloads (#906) 2025-03-13 08:24:57 -07:00
RecursivePineapple c4ad460f34 Update (#899) 2025-03-09 23:04:46 +00:00
Glease 2a8404c6c2 add an API to retrieve world instance from WorldSlice (#897) 2025-03-08 23:00:58 -08:00
ah-OOG-ah 108db9031b Add an option to disable terrain fog (#896)
* Add an option to disable terrain fog

* forgot a remap = false
2025-03-09 05:38:30 +00:00
amyandJason Mitchell d6d8a5dad6 Catch CancellationException while building chunks (#889)
Co-authored-by: Jason Mitchell <[email protected]>
2025-03-08 20:45:10 -08:00
RecursivePineappleandJason Mitchell ad2b73edf9 Fix hologlasses crash (#891)
Co-authored-by: Jason Mitchell <[email protected]>
2025-03-08 20:44:36 -08:00
glowredman bb5526598e Various Mixin-related Fixes (#890)
* Update deps

* Remove dead code

* Add missing `@Override` annotations

* Fix generics

* Add `serialVersionUID`s

* Address deprecation warnings

* Use `@WrapWithCondition` v2

* Misc cleanup

* Use AccessTransformers if possible

* Fix mixin conflicts

* Add `@Mutable` annotations

* Fix injection targets

* Update deps
2025-03-08 20:41:47 -08:00
RecursivePineapple 611bbf02c8 Optimize NEI rendering 2025-02-28 11:32:26 -08:00
OBlankenship ca16036604 Update GTNHLib dependency version in README (#885) 2025-02-27 09:22:04 +01:00
Caedis f118211257 Fix IC2 universal cell rendering w/offhand (#876) 2025-02-18 10:49:29 -08:00
RecursivePineapple 81d03ea552 Fix PBR specular & normal textures getting ignored (#874)
* Fix PBR textures not loading

- Stack was being overfilled by MemoryStack
- Added tons of logging for ignored important exceptions
- Added missing interface on mixin (should it be moved to gtnhlib?)

* Move PBR debug to angelica config

* Free malloc'd memory
2025-02-18 18:34:38 +00:00
RecursivePineapple 67c8628aeb Fix a handful of broken animations (#868)
* Fix warning

* Improve animated sprite tracking

* Fix NPE in animation code

* Fix animations for abnormally rendered items

* Swap Stack for ArrayDeque
2025-02-18 18:28:54 +00:00
embeddedt f9b9873637 Avoid checking frustum visibility of infinite bounding boxes 2025-02-18 10:24:09 -08:00
DarkShadow44 c66bf8658e Fix issue with dynamic lights world mixin not respecting opaque blocks (#872) 2025-02-17 10:36:34 -06:00
DarkShadow44 052824017d Fix crash when keycode is negative (#871) 2025-02-16 21:43:15 +00:00
Caedis 6a5d914a43 add offhand support via Backhand (up required version to 1.6.9) (#859) 2025-02-04 12:41:47 -05:00
Calvin Reveraster 208373deaa Fix any dimension without a sky getting flagged as the nether (#857)
* fix crash when connected textures are disabled, but custom colors are enabled.

* Dimension Check Fix

Dimension check set any dimension with no sky as the nether; this includes the end, and several custom dims such as Witchery's mirror dim, etc.
2025-02-01 09:18:29 -08:00
TotallyNotOndre f22a3f612b Fix slider dragging in iris options (#858) 2025-02-01 09:17:26 -08:00
TotallyNotOndre 9ff1015d19 Fix shadow render distance reset to 32 (#856) 2025-01-31 11:18:09 -08:00
Calvin Reveraster 0023fe3da2 fix crash when connected textures are disabled, but custom colors are enabled. (#855) 2025-01-29 08:19:50 -08:00
Raven Szewczyk 7e2399714c Use GTNHLib MemoryUtils instead of the builtin compat ones (#853) 2025-01-26 11:20:43 -08:00
George Paton 9029182353 Fix Hodgepodge animation speedup breaking animated textures (#850) 2025-01-25 15:34:51 -08:00
Caedis e6039691b5 Remove forced even unicode gui scaling (#840) 2025-01-15 14:39:21 -06:00
paulzzh 5db1edba33 fix MC-76356 Bold unicode characters appear doubled (#834) 2025-01-05 15:24:16 +00:00
Martin Robertz d450461726 update bs + deps (#823) 2025-01-04 13:50:46 -08:00
embeddedt 95f8afc0f3 Remove use of empty build tasks, submit empty data directly to result queue (#821) 2024-12-27 21:39:05 -08:00
Caedis 19635e20ec Disable optimizeInWorldItemRendering by default for now (#820) 2024-12-25 23:25:42 -06:00
BucketBrigade ead4e11376 Expose texture updates to API (#805)
* Expose texture updates to API

* Expose for blocks as well

* Fix mistake

* Fixed API
2024-12-19 19:04:24 -08:00
FM Bellomy b0ceb5306a the most boring typo PR of all time (#810) 2024-12-19 13:25:56 -08:00
embeddedt 99a81369f0 fix origin weirdness (#799) 2024-12-17 10:09:22 -08:00
nshepperd e2ee822948 Only clear the translucency sorting render queue when we're not rendering shadows. (#798) 2024-12-16 13:30:20 -08:00
nshepperd 9fe7e800f6 Remove broken reflection in ChunkBufferSorter. (#797) 2024-12-16 21:11:05 +00:00
embeddedt debfcdfda0 Properly prevent block face culling when TS is active (#796) 2024-12-16 13:07:00 -08:00
embeddedt df662188f4 Fix terrain rendering incorrectly when far from origin (#790) 2024-12-13 18:14:00 +00:00
embeddedt 842916405f Fix rebuild check being wrong when shaders are on (#788) 2024-12-13 16:55:58 +00:00
embeddedt 09afe81290 Fix chunk render manager state not swapping, causing lower render distance with shaders on 2024-12-13 08:52:32 -08:00
Caedis 292809d8e8 Actually fix xaero's crosshair (#781) 2024-12-11 21:50:04 +00:00
Caedis 47503ce254 Fix invisible crosshair with Xaero's Minimap (#775) 2024-12-11 16:32:49 -05:00
ah-OOG-ah f738adbdef Frametime exploration (#742)
* Sleep before swap

* Add a config

* I think this is slightly less jittery
2024-12-06 09:09:26 -08:00
Alexdoru 70d15f2383 Set render distance for mob inside mod spawner to 16 blocks (#761)
* set render distance for mob inside mod spawner to 16 blocks

* add config in angelica

* add slider in video setting menu
2024-12-02 15:20:44 -10:00
kurrycatandDarren Eberly d66b5577f4 add missing GL calls in GLStateManager (#767)
Co-authored-by: Darren Eberly <[email protected]>
2024-12-02 09:12:44 -05:00
ah-OOG-ah e293f12fb8 Refresh Notfine options on world load (#760) 2024-12-01 00:03:21 +00:00
kurrycat b69eca6749 add missing Pipeline::syncProgram for glCallList (#762) 2024-11-30 10:38:00 -10:00
Jason Mitchell 03342f08c6 Update README.MD 2024-11-27 09:03:01 -08:00
ah-OOG-ah b85614f983 Null check (#756) 2024-11-26 22:19:05 +00:00
kurrycat 5afb5d8f5a properly integrate mcpatcherforge connected textures + better grass (#745) 2024-11-25 21:38:01 +00:00
ah-OOG-ah c353c83cf3 Backport MemoryStack to reduce direct buffer allocations (#751)
* Initial MemoryStack backport

* Remove stubs, for now

Will re-add as needed

* Implement mallocInt

* Replace some buffer allocation

* Add mallocFloat

* Replace more uses of direct buffer allocation

* Reorder methods

Fixes a clinit crash and makes it easier to compare to upstream

* Remove another direct buffer creation
2024-11-25 16:26:07 -05:00
Darren Eberly 78072236d2 GLSL 120 Tweaks to Iris Shader Transformation (#743) 2024-11-24 01:04:30 -05:00
Darren Eberly 350fdcd7e4 Fix for Extra Utilities ethereal blocks with connected textures (#746) 2024-11-22 03:22:14 +00:00
Darren Eberly 3d98e57178 Gracefully handle nested display list compilations in GLSM (#744) 2024-11-21 14:26:40 -08:00
Jason Mitchell 55c1786811 Update README.MD 2024-11-21 11:13:55 -08:00
Robert 356d826387 non silly pr title [ci skip] (#728) 2024-11-21 10:56:13 -08:00
Darren Eberly c34d8548a5 Item Renderer Optimization (#741) 2024-11-20 17:22:07 -05:00
Kotmatross 81a000af75 Backport F3 crosshair directions (#740)
* Backport F3 crosshair directions

* Remove render duplicate
2024-11-20 11:02:54 -05:00
ah-OOG-ahandkurrycat 711c23c722 Add frametime and TPS graphs (#739)
* Track frametimes

* impl rest of frametime graph, missing resize to not go over middle of screen when small

* only show fps graph with alt+f3 like in modern

* Add integrated server TPS reporting

---------

Co-authored-by: kurrycat <[email protected]>
2024-11-20 05:53:24 +00:00
Jason Mitchell 5aa1d504b0 Update README.MD 2024-11-18 00:12:04 -08:00
Jason Mitchell 60ab3b7900 Null check 2024-11-17 08:21:34 -08:00
Alexdoru 912e0e83b8 fix ArrayOutOfBounds in F3 debug text handler (#736) 2024-11-16 20:25:31 -08:00
Jason Mitchell 104eda0b51 Handle more incompatible mods (#735) 2024-11-16 19:23:39 +00:00
Alexdoruandkurrycat 7fb8cde66d Zoom (#724)
Co-authored-by: kurrycat <[email protected]>
2024-11-08 09:00:05 -08:00
kurrycat 0e4421f2ce fix wrong mixin remap values (#726) 2024-11-07 15:51:18 -08:00
Darren EberlyandAlexdoru 3cf3d096fd New Compat System for ASM Transformations + Immersive Engineering patches (#719)
Co-authored-by: Alexdoru <[email protected]>
2024-11-06 16:59:43 -08:00
Darren Eberly ffa1193dd2 Better ANTLR error handling for GLSL transformations (#715) 2024-11-01 17:40:34 -07:00
kurrycat d180023469 Fix entity render (#707) 2024-10-31 13:36:24 -07:00
Darren Eberly 762275a6da Cursor based positioning for shader setting tooltips (#711) 2024-10-31 16:31:33 -04:00
ah-OOG-ah 0e66bcf42f Fix inverted config (#710) 2024-10-31 19:59:17 +00:00
TotallyNotOndre 979ad344a1 Show tooltips for shader options (#705)
* Fix tooltiops

* Foolproof
2024-10-31 16:03:52 +00:00
Darren Eberly 5f328e9b57 Add ASM for handling field level tessellator usage, and also Stacks on Stacks compatibility (#703)
* Add new FieldLevelTessellator and StacksOnStacks ASM transformers

* star imports
2024-10-31 01:08:15 -04:00
Jason Mitchell 2dba62bf20 Update README.MD 2024-10-30 18:05:34 -07:00
kurrycat 0fd4d457e2 implement Iris ShadowRenderer 2024-10-30 14:42:16 -07:00
ah-OOG-ah b56e803622 Add a config to the total world time patch (#701)
* Add a config to the total world time patch

Shaders will still desync from time of day, but only when the (default off) setting is enabled.

* Namespace the configs
2024-10-30 12:47:10 -07:00
Caedis 3ab965ded7 Add realtime dynamic lights mode with no delay (#694)
Fancy changed to 50ms
Added config to force dynamic lights even when shaders are active
2024-10-30 14:45:03 -04:00
Jason Mitchell 538b38898c "Fix" world time.... (#693) 2024-10-29 20:22:24 -07:00
Jason Mitchell e43cd6f18e Update 000-report-bug.yml 2024-10-29 19:24:47 -07:00
Darren Eberly 38285087c9 Forcibly disable separateAo 2024-10-29 16:26:10 -07:00
Darren Eberly deb6c2b6e5 Add an IS_ANGELICA and ANGELICA_VERSION define for shaders 2024-10-29 16:24:48 -07:00
Darren Eberly e11985a913 Handle auto enablement of texture LOD extension for GLSL 120 (#684)
* Handle auto enablement of texture LOD extension for GLSL 120

* Add some more clear comments for random hacks

* Remove extraneous semicolon
2024-10-29 16:22:57 -07:00
Jason Mitchell 4bbf39eb4d null guard 2024-10-28 21:22:41 -07:00
Darren Eberly 8c90f79c17 Add reserved keywords and replace GLSLPreParser with regex (#683)
* Add reserved keywords and replace GLSLPreParser with regex

* Add a newline to the version directive in patched shader output

* Only process reserved keywords on GLSL < 400

* Better reserved keyword handling
2024-10-28 16:21:05 -07:00
kurrycat e4e657846d implement Iris HandRenderer (#681) 2024-10-28 16:20:00 -07:00
Darren Eberly 2dace708b4 Get frame start time properly for Iris (#682) 2024-10-28 11:12:57 -07:00
Darren Eberly 1df1ec3530 Conversion to glsl-transformation-lib (#680)
* Initial conversion to glsl-transformation-lib

* compatibility transformer

* Add taumc maven repo for glsl-transformation-lib

* Add credits for glsl-transformation-lib

* Remove extra storage drawers dependency

* Enable shaders by default

* Update issue templates
2024-10-27 21:17:29 -07:00
kurrycat d96ac28043 fix destroy block animation like 1.12 (#679) 2024-10-26 16:48:39 -07:00
kurrycat 72c13cff67 add correct pipeline phase on block destroy animation 2024-10-26 16:44:18 -07:00
kurrycat f233ba65e3 add syncProgram call to glDrawElements + glBegin 2024-10-26 13:47:26 -07:00
kurrycat c0d636d4f7 fix entities being rendered completely black 2024-10-26 12:10:52 -07:00
RecursivePineappleandDarren Eberly bd5cd350c3 Replace BlockError mixin with event handler (#671)
* Replace BlockError mixin with event handler

Something is calling TextureMap.registerIcons before preInit() sets up
the BlockError reference. While adding a null check to the mixin would
also fix this, a mixin isn't needed here since forge exposes an event
for this purpose.

* Add client sideonly for texture stitch event

---------

Co-authored-by: Darren Eberly <[email protected]>
2024-10-22 16:33:11 -04:00
kurrycat 88936d3713 fix horizon rendering before camera transform (#669) 2024-10-21 16:31:44 -04:00
Darren Eberly cf5bf2e2e7 Handle logging of ISBRH exception and multiple exception types 2024-10-21 10:44:52 -07:00
chochem cfd2388c85 add minimal tooltips 2024-10-20 09:47:40 -07:00
Darren Eberly fd78f63f12 Handle FMLRenderAccessLibrary obfuscation properly (#663)
* Handle FMLRenderAccessLibrary obfuscation properly

* Add a comment explaining jankness
2024-10-16 22:35:12 -04:00
AlexdoruandJason Mitchell 6cf119dfee Dynamically modify the render distance for dropped items entities to preserve performance (#653)
* change sodium EntityMixin to use modify arg instead of @inject that cancels all the time

* restore the 16 block render distance for dropped items

* set EntityItem render distance to 32 blocks

* dynamically reduce the render distance for dropped Item entities when there is too many

* Add dropped item limit setting to video settings

Add some missing notfine tooltip entries while I was in there

* set maximum of dropped items to 2048

---------

Co-authored-by: Jason Mitchell <[email protected]>
2024-10-16 08:55:03 -07:00
embeddedtandJason Mitchell 8c5f4d2543 Replace chunk tracker (#661)
* Replace chunk tracker

---------

Co-authored-by: Jason Mitchell <[email protected]>
2024-10-15 22:17:33 -04:00
Caedis 9e1fa7b80f Remove compat with original release of BG2 (#652) 2024-10-14 05:12:12 +00:00
Darren Eberly 6c99872459 Add an error block, and render it in case of NPE in ISBRH (#658)
* Catch and ignore NPE during ISBRH rendering

* Make a fake ErrorBlock

* working block for rendering with no registration

* More suitable texture for BlockError
2024-10-13 14:59:49 -07:00
Jason Mitchell f0809410ee Update 000-report-bug.yml 2024-10-13 10:47:39 -07:00
Jason Mitchell 98299b69d2 Update 000-report-bug.yml 2024-10-13 10:47:00 -07:00
Jason Mitchell fed0aa77c5 Update 000-report-bug.yml 2024-10-13 10:46:23 -07:00
Jason Mitchell f7fb3186c8 Revert back to old behavior
* RFB Plugin loads earlier and can run in both obf and deobf
2024-10-09 18:12:49 -07:00
Alexdoru ab243bb936 fix obfuscation error from last commit D: (#648) 2024-10-09 22:45:03 +02:00
Alexdoru 60cce7ea1d Deal with obfuscation properly (#645)
* make one method to dump the classes instead of copy pasting 6 times

* deal with obfuscation properly in ASM transformers instead of brute forcing both obfuscated and clear names

* remove sorting index on AngelicaTweaker to avoid confusion

* use static final variables for the class names with "."
2024-10-09 08:07:22 -07:00
Alexdoru b837b0571d fix strikethrough line being off by one 2024-10-08 09:47:20 -07:00
Alexdoru 7abc6213f5 only init NotFine's obfuscation helper if at least one transformer is loaded 2024-10-03 15:34:41 -07:00
Alexdoru b46643ae8c use GTNHLib ASMUtil to dump the class files + always dump in dev env 2024-10-03 15:34:41 -07:00
Gleaseandah-OOG-ah e3d1fedd48 replace MixinBlockLiquid with non-mixin implementation (#642)
* replace MixinBlockLiquid with non-mixin implementation

* Update GTNHLib

---------

Co-authored-by: ah-OOG-ah <[email protected]>
2024-09-29 19:25:53 +00:00
Caedis ebcee499eb Fancy Dynamic Lights is now default (#629) 2024-09-08 15:31:51 -05:00
PlayfulPiano ad26b1ad6f Update README.MD - add more links for compatibility (#626)
No links were listed for a couple mod version incompatibilities. they're now listed.
2024-09-04 16:32:45 +00:00
Safixo 73338e3c3e Fix cull state leaking sometimes (#614) 2024-09-02 13:50:53 -07:00
mist475 2c89244fbe Archaicfix branding fix (#621)
* move angelica branding text up if archaicfix is present
2024-09-02 13:50:10 -07:00
Jason Mitchell 991f658819 Add GLSM to transformer exclusions (#612) 2024-08-25 16:51:33 -07:00
ah-OOG-ah 1588c7000d Remove model loading from Angelica (#610) 2024-08-21 08:18:58 -07:00
NealDeal34andMartin Robertz 260d57e308 Update zh_CN.lang (#565)
* Update zh_CN.lang

* Update zh_CN.lang

* Update zh_CN.lang

* Update zh_CN.lang

---------

Co-authored-by: Martin Robertz <[email protected]>
2024-08-17 17:05:00 +02:00
Caedis 3a1d1b03b3 [Dynamic Lights] Add Baubles Support (#595)
* [Dynamic Lights] Add Baubles Support

* Add IDynamicLightProducer
2024-08-15 10:25:06 -07:00
cutiegin 66563f41c8 Update ru_RU.lang (#593)
* Update ru_RU.lang
2024-08-07 07:50:22 -07:00
Unicornbl00d a52adb9190 Make shaders automatically work with modded leaves using the ore dictionary tag treeLeaves if the shader calls for minecraft:leaves 2024-08-06 18:13:07 -07:00
Jason Mitchell 1c6f9aa131 Bump gtnhlib dep 2024-08-06 17:57:02 -07:00
czerwonogrodzki 8c584fed05 Update README.MD 2024-08-05 07:36:05 -07:00
Jason Mitchell 558ea0fe12 Moves CapturingTesselator, JSON Models, and misc utility classes to GTNHlib (#587)
* Moves CapturingTesselator, JSON Models, and misc utility classes to GTNHlib
* Switch to separate source set for mixins - faster incremental builds
2024-08-04 15:41:58 -07:00
Darren EberlyandCaedis 1315d8971a More AO Fixes (#578)
* Only use sodium AO for standard blocks and disable flat pipeline optimization

* Option for sodium smooth lighting. Currently default to true

* Remove tessellator ignore from renderQuadList

* Add back cull and sodium lighting check in renderQuadList

* Disable sodium AO by default

* fix culling

---------

Co-authored-by: Caedis <[email protected]>
2024-08-02 23:51:30 +07:00
Caedis e33f2ff7e3 Fix sodium smooth dynamic lights (#577) 2024-07-31 16:13:15 -04:00
1241 changed files with 55647 additions and 29145 deletions

No files matched your search

+29 -8
View File
@@ -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
View File
@@ -36,3 +36,4 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
local/
+38 -15
View File
@@ -5,39 +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
## **Beta Quality - Here there be Dragons**
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
* Complementary & BSL mostly function, but have some bugs
* Sildurs, Seus, and any other shader probably doesn't work
* 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.3.2
* [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'
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
# 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]
* SmoothFont - Not currently planned (Looking for volunteers)
* RPLE - Not currently planned (Looking for volunteers)
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
# Permanent Incompatibilities
* OptiFine [Disabled, won't fix]
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, 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)
* 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
@@ -52,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
@@ -74,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)
+36 -2
View File
@@ -1,9 +1,15 @@
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.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -11,17 +17,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 +44,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")
}
+56 -36
View File
@@ -46,48 +46,60 @@ 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")
// Use modern models+textures to inject QuadProviders
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.1:dev')
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.3.2:dev")
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.33: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.github.GTNewHorizons:ThaumicHorizons:1.6.1:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.0:dev") { transitive = false }
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.0:dev") { transitive = false }
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
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 }
@@ -97,28 +109,36 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.0.3:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
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
View File
@@ -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
+12
View File
@@ -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/5ee463876563a46fff651cce1bc53a4c/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/5ee463876563a46fff651cce1bc53a4c/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/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/832229f0f6f0be60ed817c5a5e5848eb/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/b96cb7670c095245220e7d01d5927b58/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -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.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -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/HEAD/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
View File
@@ -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
View File
@@ -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()
+3 -3
View File
@@ -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.24'
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.20")
}
@@ -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.3.2,);",
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,19 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.compat.mojang.BlockPosImpl;
import net.minecraftforge.common.util.ForgeDirection;
public interface BlockPos {
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) {
return CoordinatePacker.pack(x, y, 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,122 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.google.common.annotations.Beta;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import com.gtnewhorizons.angelica.models.json.JsonModel;
import com.gtnewhorizons.angelica.utils.Callback;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Model loading is as follows: <ul>
* <li>Models to load are registered in PREINIT or INIT. See {@link #registerModels(Callback, Collection)}.</li>
* <li>A resource rebuild is triggered, then registered models and their parents are recursively loaded in POSTINIT.
* </li>
* <li>Models registered for baking are baked on the first client tick.</li>
*</ul>
* <p>As for icons, register them in {@link Block#registerBlockIcons}. Whatever gets them in the block texture atlas.
*/
@Beta
public class ModelLoader {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(JsonModel.class, new JsonModel.Deserializer()).create();
private static final List<ResourceLocation> unloadedModels = new ObjectArrayList<>();
private static final Map<ResourceLocation, JsonModel> loadedModels = new Object2ObjectOpenHashMap<>();
private static final Map<Variant, JsonModel> modelsToBake = new Object2ObjectOpenHashMap<>();
private static final List<Callback> postBakeCallbacks = new ObjectArrayList<>();
/**
* Convenience method to register a baker callback, which will be run after JSON models are baked. Useful if
* you implemented your own non-JSON model that still needs baking.
*/
public static void registerBaker(Callback baker) {
postBakeCallbacks.add(baker);
}
/**
* Convenience method to register multiple variants. See {@link #registerModels(Callback, Collection)}.
*/
public static void registerModels(Callback loader, Variant... variants) {
registerModels(loader, Arrays.asList(variants));
}
/**
* Pass the variant(s) you want to load, and if needed a callback which puts the models in a useful place, e.g.
* saving them to a static field after baking.
*/
public static void registerModels(Callback loader, Collection<Variant> variants) {
for (Variant v : variants) {
unloadedModels.add(v.getModel());
modelsToBake.put(v, null);
}
postBakeCallbacks.add(loader);
}
public static void loadModels() {
for (ResourceLocation l : unloadedModels) {
if (l == null) continue;
if (loadedModels.containsKey(l)) continue;
final JsonModel model = loadJson(l, JsonModel.class);
unloadedModels.addAll(model.getParents());
loadedModels.put(l, model);
}
}
private static <T> T loadJson(ResourceLocation path, Class<T> clazz) {
try {
final InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(path).getInputStream();
return GSON.fromJson(new InputStreamReader(is), clazz);
} catch (IOException e) {
AngelicaTweaker.LOGGER.fatal("Could not find " + path.getResourceDomain() + " " + path.getResourcePath());
throw new RuntimeException(e);
}
}
public static void bakeModels() {
for (Map.Entry<Variant, JsonModel> l : modelsToBake.entrySet()) {
final JsonModel dough = new JsonModel(loadedModels.get(l.getKey().getModel()));
// Resolve the parent chain
//noinspection SuspiciousMethodCalls
dough.resolveParents(loadedModels::get);
// Bake
dough.bake(l.getKey());
l.setValue(dough);
}
for (Callback c : postBakeCallbacks)
c.run();
}
public static QuadProvider getModel(Variant loc) {
return modelsToBake.get(loc);
}
}
@@ -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,233 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.gtnewhorizons.angelica.models.NdQuadBuilder;
import me.jellysquid.mods.sodium.client.model.quad.ModelQuadView;
import me.jellysquid.mods.sodium.client.model.quad.ModelQuadViewMutable;
import net.minecraft.util.IIcon;
import net.minecraftforge.common.util.ForgeDirection;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3f;
public interface QuadBuilder {
/**
* Causes texture to appear with no rotation.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_ROTATE_NONE = 0;
/**
* Causes texture to appear rotated 90 deg. clockwise relative to nominal face.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_ROTATE_90 = 1;
/**
* Causes texture to appear rotated 180 deg. relative to nominal face.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_ROTATE_180 = 2;
/**
* Causes texture to appear rotated 270 deg. clockwise relative to nominal face.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_ROTATE_270 = 3;
/**
* When enabled, texture coordinate are assigned based on vertex position.
* Any existing UV coordinates will be replaced.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*
* <p>UV lock always derives texture coordinates based on nominal face, even
* when the quad is not co-planar with that face, and the result is
* the same as if the quad were projected onto the nominal face, which
* is usually the desired result.
*/
int BAKE_LOCK_UV = 4;
/**
* When set, U texture coordinates for the given sprite are
* flipped as part of baking. Can be useful for some randomization
* and texture mapping scenarios. Results are different from what
* can be obtained via rotation and both can be applied.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_FLIP_U = 8;
/**
* Same as {@link #BAKE_FLIP_U} but for V coordinate.
*/
int BAKE_FLIP_V = 16;
/**
* UV coordinates by default are assumed to be 0-16 scale for consistency
* with conventional Minecraft model format. This is scaled to 0-1 during
* baking before interpolation. Model loaders that already have 0-1 coordinates
* can avoid wasteful multiplication/division by passing 0-1 coordinates directly.
* Pass in bakeFlags parameter to {@link #spriteBake(IIcon, int)}.
*/
int BAKE_NORMALIZED = 32;
/**
* Tolerance for determining if the depth parameter to {@link #square(ForgeDirection, float, float, float, float, float)}
* is effectively zero - meaning the face is a cull face.
*/
float CULL_FACE_EPSILON = 0.00001f;
static QuadBuilder getBuilder() {
return new NdQuadBuilder();
}
/**
* Copies data to out, returns it, and resets builder for further use.
*/
QuadView build(QuadView out);
/**
* See {@link #build(QuadView)}. This also rotates the output by the given matrix.
*/
QuadView build(QuadView out, Matrix4f rotMat);
/**
* Tries to automatically set the culling and nominal face from the quad's geometry.
*/
void setCullFace();
/**
* Provides a hint to renderer about the facing of this quad. Not required,
* but if provided can shortcut some geometric analysis if the quad is parallel to a block face.
* Should be the expected value of {@link ModelQuadView#getLightFace()}. Value will be confirmed
* and if invalid the correct light face will be calculated.
*
* <p>Null by default, and set automatically by {@link ModelQuadViewMutable#setCullFace(ForgeDirection)}.
*
* <p>Models may also find this useful as the face for texture UV locking and rotation semantics.
*
* <p>Note: This value is not persisted independently when the quad is encoded.
* When reading encoded quads, this value will always be the same as {@link ModelQuadView#getLightFace()}.
*/
void nominalFace(@Nullable ForgeDirection face);
/**
* See {@link #nominalFace(ForgeDirection)}
*/
ForgeDirection nominalFace();
/**
* Sets the geometric vertex position for the given vertex,
* relative to block origin, (0,0,0). Minecraft rendering is designed
* for models that fit within a single block space and is recommended
* that coordinates remain in the 0-1 range, with multi-block meshes
* split into multiple per-block models.
*/
void pos(int vertexIndex, float x, float y, float z);
/**
* Convenience: set pos with a vector. See {@link #pos(int, float, float, float)}.
*/
void pos(int vertexIndex, Vector3f vec);
/**
* Gets the vertex position as a vector. This allocates a new vector, do not use in dynamic rendering!
*/
Vector3f pos(int vertexIndex);
/**
* Convenience: access x, y, z by index 0-2.
*/
float posByIndex(int vertexIndex, int coordinateIndex);
/**
* Convienence, gets the icon from the block atlas. See {@link #spriteBake(IIcon, int)}
*/
void spriteBake(String spriteName, int bakeFlags);
/**
* Assigns sprite atlas u,v coordinates to this quad for the given sprite.
* Can handle UV locking, rotation, interpolation, etc. Control this behavior
* by passing additive combinations of the BAKE_ flags defined in this interface.
*/
void spriteBake(IIcon sprite, int bakeFlags);
/**
* Helper method to assign vertex coordinates for a square aligned with the given face.
* Ensures that vertex order is consistent with vanilla convention. (Incorrect order can
* lead to bad AO lighting unless enhanced lighting logic is available/enabled.)
*
* <p>Square will be parallel to the given face and coplanar with the face (and culled if the
* face is occluded) if the depth parameter is approximately zero. See {@link #CULL_FACE_EPSILON}.
*
* <p>All coordinates should be normalized (0-1).
*
* <p>The directions for the faces assume you're looking at them. For top and bottom, they assume you're looking north.
*/
void square(ForgeDirection nominalFace, float left, float bottom, float right, float top, float depth);
int tag();
/**
* Encodes an integer tag with this quad that can later be retrieved via
* {@link #tag()}. Useful for models that want to perform conditional
* transformation or filtering on static meshes.
*/
void tag(int tag);
/**
* Set texture coordinates.
*/
void uv(int vertexIndex, float u, float v);
/**
* Modern Minecraft uses magic arrays to do this without breaking AO. This is the same thing, but without arrays.
*/
static Vector3f mapSideToVertex(Vector3f from, Vector3f to, int index, ForgeDirection side) {
return switch (side) {
case DOWN -> switch (index) {
case 0 -> new Vector3f(from.x, from.y, to.z);
case 1 -> new Vector3f(from.x, from.y, from.z);
case 2 -> new Vector3f(to.x, from.y, from.z);
case 3 -> new Vector3f(to.x, from.y, to.z);
default -> throw new RuntimeException("Too many indices!");
};
case UP -> switch (index) {
case 0 -> new Vector3f(from.x, to.y, from.z);
case 1 -> new Vector3f(from.x, to.y, to.z);
case 2 -> new Vector3f(to.x, to.y, to.z);
case 3 -> new Vector3f(to.x, to.y, from.z);
default -> throw new RuntimeException("Too many indices!");
};
case NORTH -> switch (index) {
case 0 -> new Vector3f(to.x, to.y, from.z);
case 1 -> new Vector3f(to.x, from.y, from.z);
case 2 -> new Vector3f(from.x, from.y, from.z);
case 3 -> new Vector3f(from.x, to.y, from.z);
default -> throw new RuntimeException("Too many indices!");
};
case SOUTH -> switch (index) {
case 0 -> new Vector3f(from.x, to.y, to.z);
case 1 -> new Vector3f(from.x, from.y, to.z);
case 2 -> new Vector3f(to.x, from.y, to.z);
case 3 -> new Vector3f(to.x, to.y, to.z);
default -> throw new RuntimeException("Too many indices!");
};
case WEST -> switch (index) {
case 0 -> new Vector3f(from.x, to.y, from.z);
case 1 -> new Vector3f(from.x, from.y, from.z);
case 2 -> new Vector3f(from.x, from.y, to.z);
case 3 -> new Vector3f(from.x, to.y, to.z);
default -> throw new RuntimeException("Too many indices!");
};
case EAST -> switch (index) {
case 0 -> new Vector3f(to.x, to.y, to.z);
case 1 -> new Vector3f(to.x, from.y, to.z);
case 2 -> new Vector3f(to.x, from.y, from.z);
case 3 -> new Vector3f(to.x, to.y, from.z);
default -> throw new RuntimeException("Too many indices!");
};
case UNKNOWN -> null;
};
}
}
@@ -1,49 +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;
/**
* Called once per block and 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 your own function, don't override this. But if you only
* want to change color once per block, like vanilla leaves, override this. The default vanilla adapter is
* {@link #getDefaultColor}
*/
default int getColor(IBlockAccess world, BlockPos pos, Block block, int meta, Random random) {
return -1;
}
static int getDefaultColor(IBlockAccess world, BlockPos pos, Block block) {
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,28 +0,0 @@
package com.gtnewhorizons.angelica.api;
import me.jellysquid.mods.sodium.client.model.quad.ModelQuadViewMutable;
import me.jellysquid.mods.sodium.client.model.quad.Quad;
import me.jellysquid.mods.sodium.client.render.pipeline.BlockRenderer;
import net.minecraftforge.common.util.ForgeDirection;
public interface QuadView extends ModelQuadViewMutable {
/**
* Allocates a new quad - use sparingly, and not at all in render paths if you can help it. We don't need another
* Malice Doors.
*/
static QuadView allocate() {
return new Quad();
}
boolean isShade();
boolean isDeleted();
ForgeDirection getFace();
QuadView copyFrom(QuadView src);
int[] getRawData();
/**
* 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,34 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.google.common.annotations.Beta;
import lombok.Getter;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import static java.lang.Math.toRadians;
@Beta
public class Variant {
@Getter
private final ResourceLocation model;
private final float x;
private final float y;
private final boolean uvLock;
public Variant(ResourceLocation model, int x, int y, boolean uvLock) {
this.model = model;
this.x = (float) toRadians(x);
this.y = (float) toRadians(y);
this.uvLock = uvLock;
}
public Matrix4f getAffineMatrix() {
return new Matrix4f()
.translation(-.5f, -.5f, -.5f)
.rotateLocalY(x)
.rotateLocalX(y)
.translateLocal(.5f, .5f, .5f);
}
}
@@ -1,26 +1,38 @@
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.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 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.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
/**
* A batching replacement for {@code FontRenderer}
@@ -31,8 +43,6 @@ 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. */
@@ -44,50 +54,59 @@ public class BatchingFontRenderer {
private 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 int fontVAO = 0;
private static int vboPositions, vboColors, vboTexCoords, vboTexBounds, vboIndices;
private static int capPositions, capColors, capTexCoords, capTexBounds, capIndices;
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");
}
// === Batched rendering
@@ -96,27 +115,32 @@ public class BatchingFontRenderer {
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);
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = memAlloc(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = memAllocInt(INITIAL_BATCH_SIZE / 2 * 3);
private FloatBuffer batchVtxTexBounds = memAllocFloat(INITIAL_BATCH_SIZE * 4);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
/** */
private void pushVtx(float x, float y, int rgba, float u, float v) {
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
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);
batchVtxPositions = memRealloc(batchVtxPositions, newCap * 2);
batchVtxColors = memRealloc(batchVtxColors, newCap * 4);
batchVtxTexCoords = memRealloc(batchVtxTexCoords, newCap * 2);
batchVtxTexBounds = memRealloc(batchVtxTexBounds, newCap * 4);
final int oldIdxCap = batchIndices.capacity();
final int newIdxCap = oldIdxCap * 2;
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
batchIndices = memRealloc(batchIndices, newIdxCap);
}
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
@@ -130,15 +154,28 @@ public class BatchingFontRenderer {
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
batchVtxTexBounds.put(idx4, uMin);
batchVtxTexBounds.put(idx4 + 1, uMax);
batchVtxTexBounds.put(idx4 + 2, vMin);
batchVtxTexBounds.put(idx4 + 3, vMax);
vtxWriterIndex++;
}
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);
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(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
final int vtxId = vtxWriterIndex;
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(vtxId);
}
@@ -155,7 +192,7 @@ public class BatchingFontRenderer {
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 +208,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 +216,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
@@ -243,7 +282,24 @@ public class BatchingFontRenderer {
}
}
private static int streamUpload(int target, int vbo, long address, int bytes, int cap) {
GL15.glBindBuffer(target, vbo);
final ByteBuffer data = memByteBuffer(address, bytes);
if (bytes > cap) {
GL15.glBufferData(target, data, GL15.GL_STREAM_DRAW);
return bytes;
}
GL15.glBufferData(target, cap, GL15.GL_STREAM_DRAW);
GL15.glBufferSubData(target, 0, data);
return cap;
}
private static final Matrix4f scratchMvp = new Matrix4f();
private int fontAAModeLast = -1;
private int fontAAStrengthLast = -1;
private void flushBatch() {
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -255,17 +311,71 @@ 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);
final boolean useFontShader = prevProgram == 0;
if (useFontShader) {
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 = org.lwjgl.opengl.GL30.glGenVertexArrays();
vboPositions = GL15.glGenBuffers();
vboColors = GL15.glGenBuffers();
vboTexCoords = GL15.glGenBuffers();
vboTexBounds = GL15.glGenBuffers();
vboIndices = GL15.glGenBuffers();
}
GLStateManager.glBindVertexArray(fontVAO);
final int vtxCount = vtxWriterIndex;
// Location 0: position (vec2, float)
batchVtxPositions.position(0).limit(vtxCount * 2);
capPositions = streamUpload(GL15.GL_ARRAY_BUFFER, vboPositions, memAddress(batchVtxPositions), batchVtxPositions.remaining() * 4, capPositions);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(0);
// Location 1: color (vec4, unsigned byte normalized)
batchVtxColors.position(0).limit(vtxCount * 4);
capColors = streamUpload(GL15.GL_ARRAY_BUFFER, vboColors, memAddress(batchVtxColors), batchVtxColors.remaining(), capColors);
GL20.glVertexAttribPointer(1, 4, GL11.GL_UNSIGNED_BYTE, true, 0, 0L);
GL20.glEnableVertexAttribArray(1);
// Location 2: texcoord0 (vec2, float)
batchVtxTexCoords.position(0).limit(vtxCount * 2);
capTexCoords = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexCoords, memAddress(batchVtxTexCoords), batchVtxTexCoords.remaining() * 4, capTexCoords);
GL20.glVertexAttribPointer(2, 2, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(2);
// Location 3: texbounds (vec4, float)
batchVtxTexBounds.position(0).limit(vtxCount * 4);
capTexBounds = streamUpload(GL15.GL_ARRAY_BUFFER, vboTexBounds, memAddress(batchVtxTexBounds), batchVtxTexBounds.remaining() * 4, capTexBounds);
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(3);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
// Stream upload index buffer
batchIndices.position(0).limit(idxWriterIndex);
capIndices = streamUpload(GL15.GL_ELEMENT_ARRAY_BUFFER, vboIndices, memAddress(batchIndices), batchIndices.remaining() * 4, capIndices);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
final int cmdsSize = batchCommands.size();
@@ -283,14 +393,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_INT, (long) cmd.startVtx * 4L);
}
if (useFontShader) {
GLStateManager.glUseProgram(prevProgram);
}
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
GLStateManager.glBindVertexArray(0);
if (isTextureEnabledBefore) {
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
@@ -304,8 +414,11 @@ public class BatchingFontRenderer {
batchCommands.clear();
vtxWriterIndex = 0;
idxWriterIndex = 0;
batchVtxPositions.limit(batchVtxPositions.capacity());
batchVtxColors.limit(batchVtxColors.capacity());
batchVtxTexCoords.limit(batchVtxTexCoords.capacity());
batchVtxTexBounds.limit(batchVtxTexBounds.capacity());
batchIndices.limit(batchIndices.capacity());
batchIndices.position(0);
}
// === Actual text mesh generation
@@ -314,17 +427,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 +461,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 +483,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 +503,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 +513,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 +560,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 +650,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
1.0f,
glyphScaleY,
curColor);
pushDrawCmd(ulIdx, 6, null);
pushDrawCmd(ulIdx, 6, null, false);
}
} finally {
@@ -588,4 +661,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,34 +1,20 @@
package com.gtnewhorizons.angelica.common;
import com.gtnewhorizons.angelica.api.QuadProvider;
import com.gtnewhorizons.angelica.mixins.interfaces.ModeledBlock;
import com.gtnewhorizons.angelica.utils.AssetLoader;
import lombok.Getter;
import lombok.Setter;
import me.jellysquid.mods.sodium.common.util.DirectionUtil;
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.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.jetbrains.annotations.NotNull;
import static com.gtnewhorizons.angelica.models.VanillaModels.LECTERN;
public class BlockTest extends Block implements ModeledBlock {
@Getter
@Setter
private QuadProvider model;
/// 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);
this.setBlockTextureName("missingno");
this.setModel((world, pos, block, meta, dir, random, color, quadPool) -> {
if (meta < 2 || meta > 5) meta = 2;
return LECTERN.models[meta - 2].getQuads(world, pos, block, meta, dir, random, color, quadPool);
});
super(Material.wood);
setHardness(0.7f);
}
@Override
@@ -36,13 +22,6 @@ public class BlockTest extends Block implements ModeledBlock {
return false;
}
@Override
public void registerBlockIcons(IIconRegister reg) {
for (String s : AssetLoader.testTexs) {
reg.registerIcon(s);
}
}
/**
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
*/
@@ -50,11 +29,16 @@ public class BlockTest extends Block implements ModeledBlock {
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 = DirectionUtil.ALL_DIRECTIONS[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,23 +1,18 @@
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 xonin.backhand.Backhand;
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;
@@ -28,39 +23,40 @@ public class ModStatus {
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");
isBackhandLoaded = Loader.isModLoaded("backhand");
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;
}
if (isBackhandLoaded){
isBackhandLoaded = new DefaultArtifactVersion("1.4.1")
.compareTo(
LoadControllerHelper.getOwningMod(Backhand.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;
}
@@ -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();
}
}
@@ -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,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,68 +0,0 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.MutableBlockPos;
import net.minecraft.world.ChunkPosition;
import net.minecraftforge.common.util.ForgeDirection;
import org.joml.Vector3i;
// 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 CoordinatePacker.pack(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) {
CoordinatePacker.unpack(packedPos, this);
return this;
}
}
@@ -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;
}
}
@@ -1,5 +1,6 @@
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 net.minecraftforge.common.util.ForgeDirection;
@@ -35,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));
}
}
}
@@ -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));
}
}
}
@@ -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));
}
}
}
@@ -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));
}
}
}
@@ -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 +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> {
@@ -145,11 +148,10 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
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.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;
@@ -163,11 +165,11 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
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.fog, this.layering, this.target, 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;
@@ -179,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 + ']';
}
@@ -205,7 +209,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
private RenderPhase.Target target;
private RenderPhase.Texturing texturing;
private RenderPhase.WriteMaskState writeMaskState;
private RenderPhase.LineWidth lineWidth;
private Builder() {
this.texture = RenderPhase.NO_TEXTURE ;
@@ -221,7 +224,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
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) {
@@ -279,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.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);
}
}
}
@@ -295,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,9 +63,6 @@ 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 DiffuseLighting ENABLE_DIFFUSE_LIGHTING;
@@ -80,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;
@@ -108,6 +97,7 @@ public abstract class RenderPhase {
this.endAction.run();
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
@@ -119,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
@@ -149,29 +127,6 @@ 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);
@@ -185,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", () -> {
@@ -220,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", () -> {
@@ -228,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);
@@ -236,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);
}
@@ -246,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") + ']';
}
}
@@ -506,7 +345,7 @@ public abstract class RenderPhase {
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();
@@ -515,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();
}
@@ -557,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,20 +38,25 @@ 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
public static boolean enableIris;
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
@Config.DefaultBoolean(false)
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVBO;
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)
@@ -77,12 +88,16 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableHudCachingEventTransformer;
@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 [Experimental]")
@Config.Comment("Batch drawScreen fonts")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableFontRenderer;
@@ -97,7 +112,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;
@@ -107,28 +122,121 @@ 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 thread-safety issues in MineFactory Reloaded")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixMineFactoryReloadedSodiumCompat;
@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;
}
@@ -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,14 +1,19 @@
package com.gtnewhorizons.angelica.dynamiclights;
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.mojang.BlockPosImpl;
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 mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
@@ -19,26 +24,37 @@ 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 xonin.backhand.api.core.BackhandUtils;
import xonin.backhand.api.core.IBackhandPlayer;
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;
@@ -49,7 +65,23 @@ public class DynamicLights {
}
public static boolean isEnabled() {
return AngelicaConfig.enableDynamicLights && Mode.isEnabled() && !IrisApi.getInstance().isShaderPackInUse();
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;
}
/**
@@ -57,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++;
}
@@ -81,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();
}
@@ -99,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;
}
}
@@ -122,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;
}
@@ -145,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();
}
@@ -195,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());
}
@@ -221,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.
*
@@ -229,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);
}
@@ -237,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.
*
@@ -267,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());
}
@@ -277,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) {
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.
*
@@ -292,7 +469,7 @@ 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) {
final long pos = chunkPos.asLong();
if (old != null)
@@ -325,16 +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) {
// TODO only have certain items not glow in water?
if (submergedInWater) return 0;
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();
@@ -344,39 +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.isInsideOfMaterial(Material.water));
return getLuminanceFromItemStack(item.getEntityItem());
}
int luminance = 0;
if (entity instanceof EntityLivingBase living) {
int luminance = 0;
boolean inWater = living.isInsideOfMaterial(Material.water);
// 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 (ModStatus.isBattlegearLoaded &&
living instanceof EntityPlayer player &&
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
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 && living instanceof EntityPlayer player){
ItemStack offhand = BackhandUtils.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
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));
}
}
}
}
}
@@ -384,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);
}
@@ -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());
}
}
}
@@ -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) {
GL15.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public int glGenTextures() {
return GL11.glGenTextures();
}
@Override
public void glGenTextures(int[] textures) {
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) {
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) {
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 1241 files, more files were not shown because too many files have changed in this diff. Show more