Compare commits

...

165 Commits

Author SHA1 Message Date
Astrash 73baaec6cd Bump version to 6.3.1 2023-07-11 09:33:57 +10:00
dfsek 549edd11ea update ServerCommandSourceMixin 2023-06-15 17:27:26 -07:00
dfsek 36f89946d4 update SignBlockEntityMixin 2023-06-15 17:26:04 -07:00
dfsek 18644d6100 update fabric dependencies 2023-06-15 17:22:21 -07:00
dfsek 9d38ee4329 repackage NMS 1.20.1 2023-06-15 17:18:36 -07:00
dfsek b75a8f85e4 restore NMSChunkGeneratorDelegate#getBaseColumn 2023-06-15 17:16:20 -07:00
dfsek aad58f5968 update nms imports 2023-06-15 17:14:45 -07:00
dfsek a548c30484 clean up unused bstats relocation 2023-06-15 17:11:53 -07:00
dfsek 9ba46ae3a5 make new bukkit NMS module 2023-06-15 17:11:32 -07:00
dfsek 49efbed6f5 update versions 2023-06-15 17:11:04 -07:00
dfsek 4001a56100 Merge pull request #400 from PolyhedralDev/ver/6.3.0
make forge compile
2023-06-15 15:55:49 -07:00
dfsek f46f35d2ad make forge compile 2023-06-15 15:52:36 -07:00
dfsek 70dbd2f2c0 Merge pull request #399 from PolyhedralDev/ver/6.3.0
Ver/6.3.0
2023-06-15 14:05:02 -07:00
dfsek bf1be62d54 Merge pull request #385 from PolyhedralDev/dev/img-lib
Image library
2023-06-15 14:00:44 -07:00
Astrash a5cbce3667 Optimize cardinal rotations for RotateColorSampler 2023-06-14 12:07:37 +10:00
Astrash d0591f292e Use primitive int over Integer 2023-06-14 11:15:25 +10:00
dfsek 27874ce0a5 Merge pull request #396 from ccorp2002/master
1.19.4 Support...?
2023-06-10 15:55:12 -07:00
dfsek 170687abdb remove unused mixin 2023-06-10 15:53:27 -07:00
dfsek 46b61d841d remove unneeded buildscript 2023-06-10 15:50:45 -07:00
dfsek 41b7021121 fix fabric pack reloading 2023-06-10 15:49:33 -07:00
dfsek 183255140b remove unused RegistrarInstance class 2023-06-10 15:28:46 -07:00
dfsek bea8f97179 fix bukkit pack reloading 2023-06-09 03:33:47 -07:00
dfsek 60fec05e12 Merge remote-tracking branch 'origin/ver/6.3.0'
# Conflicts:
#	build.gradle.kts
#	buildSrc/src/main/kotlin/Versions.kt
#	platforms/bukkit/build.gradle.kts
#	platforms/bukkit/nms/v1_19_R1/build.gradle.kts
#	platforms/bukkit/nms/v1_19_R2/build.gradle.kts
#	platforms/mixin-common/src/main/java/com/dfsek/terra/mod/ModPlatform.java
#	platforms/mixin-common/src/main/java/com/dfsek/terra/mod/generation/TerraBiomeSource.java
#	platforms/mixin-common/src/main/java/com/dfsek/terra/mod/util/PresetUtil.java
#	platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/LifecyclePlatform.java
#	platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/mixin/lifecycle/RegistryLoaderMixin.java
#	platforms/mixin-lifecycle/src/main/java/com/dfsek/terra/lifecycle/util/LifecycleUtil.java
2023-06-09 03:16:18 -07:00
dfsek e79cc21c11 1.19.4 fabric 2023-06-09 03:04:41 -07:00
Astrash 5188477a6d Merge branch 'ver/6.3.0' into dev/img-lib 2023-05-02 14:21:31 +10:00
Astrash 58b743e6e8 Distance transform documentation 2023-05-02 14:15:20 +10:00
Astrash 9fa5307a60 Remove image sampler addon
The image sampler is implemented as the 'CHANNEL' sampler provided
directly by the library-image addon instead.
2023-05-02 13:41:42 +10:00
Astrash b4ea09929c ColorUtil documentation 2023-05-02 13:37:22 +10:00
Astrash 09d847bc5a Add argb method to Channel class 2023-05-02 13:12:23 +10:00
Astrash dacddef5d6 rgb -> argb 2023-05-02 13:04:23 +10:00
Astrash 3c593c7013 Fix method call 2023-05-02 13:03:13 +10:00
Astrash 36d0ef77fb Add image support addon for biome pipeline 2023-05-02 12:58:37 +10:00
Astrash 105be0c346 Implement color parsing from strings 2023-05-02 12:58:29 +10:00
Astrash 39d21fbe08 Implement channel noise sampler 2023-05-01 13:58:56 +10:00
Astrash 50fc589001 Move distance transform to appropriate package 2023-05-01 13:58:18 +10:00
Astrash ffa55cb7a3 Implement premultiplication in ColorUtil 2023-05-01 13:57:07 +10:00
Astrash ff0985bd31 Make imagelib MathUtil 2023-05-01 13:10:58 +10:00
Astrash 622fed96e5 Use colorsampler package name instead of sampler 2023-05-01 12:48:13 +10:00
Astrash c219eff149 Implement distance transform sampler 2023-05-01 12:45:26 +10:00
Astrash 514e7065e2 Update image provider v2 readme 2023-05-01 12:02:53 +10:00
dfsek 0a16453f98 remove devlaunch lazydfu 2023-04-13 18:20:16 -07:00
dfsek ca2fe27fb3 fix preset creation 2023-04-13 18:15:35 -07:00
dfsek 6f2c01ceb3 update biome builder 2023-04-13 17:20:20 -07:00
dfsek 8b74a5dee0 implement biomeStream 2023-04-13 16:45:09 -07:00
dfsek 460a7651bc fix bukkit build 2023-04-13 16:44:01 -07:00
dfsek b7a6b839e6 Merge remote-tracking branch 'origin/dev/1.19.3'
# Conflicts:
#	buildSrc/src/main/kotlin/Versions.kt
#	platforms/bukkit/build.gradle.kts
2023-04-13 16:34:43 -07:00
dfsek 75bff93ecd update mod versions 2023-04-13 16:31:49 -07:00
dfsek 57a45f08f0 re-package bukkit 1.19.4 code 2023-04-13 16:31:41 -07:00
C_Corp2002 ba35b56016 Delete AwfulBukkitHacks.java.txt
Unneeded.
2023-04-13 15:40:13 -07:00
C_Corp2002 8fd10956e4 Update build.gradle.kts 2023-04-12 00:51:49 -07:00
C_Corp2002 9a5c1302ac Fixing...?
We gottem.
2023-04-12 00:51:12 -07:00
C_Corp2002 f918f1ef66 An attempt at 1.19.4 Support
I tried.
2023-04-09 21:08:38 -07:00
Astrash 2afcee28a6 Base key names on internal names 2023-03-01 13:39:09 +11:00
Astrash 6efff02c19 Implement stitched image support 2023-03-01 13:38:36 +11:00
Astrash d3e0831d9e Use config type based image loading
Rename `path` key to `image`
2023-03-01 13:28:45 +11:00
Astrash 345012810a Fix biome-provider-image-v2 addon manifest 2023-03-01 13:22:39 +11:00
Astrash 73e0899e7c Only use WorldEdit relative offset for v2 sponge schematics 2023-02-28 13:05:36 +11:00
Astrash 8deae0480c Add support for WorldEdit schematic relative offsets 2023-02-28 11:58:39 +11:00
Astrash 7b87498751 Don't load same image multiple times 2023-02-07 11:59:03 +11:00
Astrash 33f1aa07d3 Mark BufferedImageLoader as deprecated 2023-02-07 11:38:12 +11:00
Astrash 3ab671827d Re-add old image sampler to config-noise-function w/ deprecation notice 2023-02-07 11:31:23 +11:00
Astrash 4d17edef80 Add deprecation notice for biome-provider-image addon 2023-02-07 11:31:23 +11:00
Astrash 9d5b33f130 Re-add original image biome provider
This is so we aren't breaking existing packs
2023-02-07 11:31:23 +11:00
Astrash 97c0dcad9d Rename image lib biome provider to v2 2023-02-07 11:31:23 +11:00
Astrash ef4fe4eb7a Revert "Bump image provider major version"
This reverts commit f1bf3990c1.
2023-02-07 11:31:23 +11:00
Astrash 9514641e1e Revert "Bump config-noise-function major version"
This reverts commit 68875cc17b.
2023-02-07 11:31:23 +11:00
Astrash e6c51bcfd0 Merge branch 'ver/6.3.0' into dev/img-lib
fogor import
2023-02-07 11:30:54 +11:00
Astrash 084ecb9ad8 Forgot to delet sout 2023-02-06 20:36:28 +11:00
Astrash 5cc58babca Bump version to 6.3.0 2023-02-06 19:18:08 +11:00
Astrash b10130c5c6 Cache loaded BufferedImages 2023-02-06 19:18:08 +11:00
dfsek 5bc34eb626 update version to 6.2.2 2023-02-06 19:18:08 +11:00
dfsek 7d74245109 make forge compile 2023-02-06 19:18:08 +11:00
dfsek 11b6080413 update bukkit to 1.19.3 2023-02-06 19:18:08 +11:00
dfsek 4df23e464b begin working on Forge 2023-02-06 19:18:08 +11:00
dfsek 7ea5747f8e clean up sysout logging 2023-02-06 19:18:08 +11:00
dfsek caad76f6dd add mixin to inject flora 2023-02-06 19:18:08 +11:00
dfsek ba2f24f1f5 1.19.3 launch and enter world 2023-02-06 19:18:08 +11:00
dfsek 57bb6bca94 evil registry hacks 2023-02-06 19:18:08 +11:00
dfsek 4bb09b126a continue registry wrangling 2023-02-06 19:18:08 +11:00
dfsek ae96d8f526 fix inject parameters in NoiseConfigMixin 2023-02-06 19:18:08 +11:00
dfsek 4a918d00a3 allow registry manager to be overwritten 2023-02-06 19:18:08 +11:00
dfsek cd65785de4 fix SaveLoadingMixin 2023-02-06 19:18:08 +11:00
dfsek 51cd4cd4b7 fix RegistryMixin target class 2023-02-06 19:18:08 +11:00
dfsek e7efdd61a6 fabric compiles now 2023-02-06 19:18:08 +11:00
dfsek 0006762ff3 update platform biome delegate logic 2023-02-06 19:18:08 +11:00
dfsek 4e4627d11d update minecraft data config templates 2023-02-06 19:18:08 +11:00
dfsek 228b26f7c4 more work on evil update 2023-02-06 19:18:08 +11:00
dfsek ef846d53ad update MinecraftChunkGeneratorWrapper and its codec 2023-02-06 19:18:08 +11:00
dfsek f6f7529cb5 begin working on fabric 1.19.3 2023-02-06 19:18:08 +11:00
dfsek 606315ea64 update platform versions to 1.19.3 2023-02-06 19:18:08 +11:00
Astrash 46f7c95314 Implement BiomeProvider#getBaseBiome in pipeline v2 2023-01-30 12:57:50 +11:00
dfsek 4d826c880c update version to 6.2.2 2022-12-21 23:49:15 -07:00
dfsek 6da3acc8a1 make forge compile 2022-12-21 23:49:06 -07:00
dfsek 8fff27fddd update bukkit to 1.19.3 2022-12-21 23:00:40 -07:00
dfsek 75673b5b8e begin working on Forge 2022-12-21 21:59:39 -07:00
dfsek 5ded3552d3 clean up sysout logging 2022-12-21 21:38:40 -07:00
dfsek ded308c01c add mixin to inject flora 2022-12-21 21:34:44 -07:00
dfsek ee336b01a6 1.19.3 launch and enter world 2022-12-21 21:14:38 -07:00
dfsek 66465f27ff evil registry hacks 2022-12-21 14:41:43 -07:00
dfsek 764a4fa535 continue registry wrangling 2022-12-19 01:27:35 -07:00
dfsek 5dd5c37055 fix inject parameters in NoiseConfigMixin 2022-12-18 22:54:30 -07:00
dfsek 2e0f892fff allow registry manager to be overwritten 2022-12-18 22:53:10 -07:00
dfsek cad0e4105c fix SaveLoadingMixin 2022-12-18 22:49:59 -07:00
dfsek b10898b837 fix RegistryMixin target class 2022-12-18 22:46:57 -07:00
dfsek 6255ac7379 fabric compiles now 2022-12-18 22:44:53 -07:00
dfsek c90ca076ab update platform biome delegate logic 2022-12-18 22:44:06 -07:00
dfsek 73af05bf09 update minecraft data config templates 2022-12-18 22:39:18 -07:00
dfsek 84eab0de4a more work on evil update 2022-12-18 22:03:00 -07:00
dfsek 393a868e6a update MinecraftChunkGeneratorWrapper and its codec 2022-12-18 20:05:17 -07:00
dfsek 8955e4bb81 begin working on fabric 1.19.3 2022-12-14 19:14:16 -07:00
dfsek bae2af80c8 update platform versions to 1.19.3 2022-12-14 18:28:25 -07:00
Astrashh 6826f44770 Merge pull request #386 from PolyhedralDev/dev/dot-product-slant-again
Refactor Noise3D palettes to optionally use dot product slants
2022-11-29 13:28:23 +11:00
Astrash 1b5095dd36 Refactor Noise3d palettes to support multiple slant methods 2022-11-26 19:18:58 +11:00
Astrash 95992cc49b Don't apply align to single image fallback 2022-11-26 14:43:15 +11:00
Astrash 7b0185ba7c Add translate color sampler 2022-11-26 14:42:12 +11:00
Astrash 6b7fb82202 Put mutator color samplers in their own packages 2022-11-26 14:21:02 +11:00
Astrash f246c8ada3 Put image based color samplers in own packages 2022-11-26 14:14:15 +11:00
Astrash a97273f358 Skip color dist calc if exact match 2022-11-26 14:05:39 +11:00
Astrash 878bede60b Name changes 2022-11-26 13:52:15 +11:00
Astrash b771e108b6 Use color sampler as fallback instead of constant 2022-11-26 13:52:15 +11:00
Astrash 5c916f7758 Add rotate color sampler 2022-11-26 13:52:05 +11:00
Astrash e9db14f52c Wrap BufferedImage access 2022-11-26 13:49:10 +11:00
Astrash b3f072d689 Properly ignore alpha of lookup map 2022-11-25 14:11:16 +11:00
Astrash 4fdef98bd9 Use generic key mapping function 2022-11-25 14:10:07 +11:00
Astrash 8670c4cdf3 ColorPicker -> ColorSampler 2022-11-25 10:17:56 +11:00
Astrash 64c2a41d19 Ignore alpha channel by default with ExactColorConverter 2022-11-24 13:56:11 +11:00
Astrash 4e225a6592 Add utility methods for zero-ing color channels 2022-11-24 13:55:36 +11:00
Astrash c491ac5b24 ColorUtil.Channel getChannel() -> from() 2022-11-24 13:55:16 +11:00
Astrash 68875cc17b Bump config-noise-function major version
* Removed IMAGE sampler
2022-11-24 12:06:51 +11:00
Astrash f1bf3990c1 Bump image provider major version 2022-11-24 12:06:51 +11:00
Astrash 4334b16ded Use color pickers in image sampler 2022-11-24 12:06:51 +11:00
Astrash 274f864d6a Move channels into image lib 2022-11-24 12:06:51 +11:00
Astrash 329d94ba9c Move image sampler to separate addon 2022-11-24 12:06:51 +11:00
Astrash 03ab463723 Rewrite the image biome provider to use the image library 2022-11-24 12:06:51 +11:00
Astrash 3580267532 Image library initial implementation
*Moves the BufferedImage loader into the library, rather than being
a loader implemented in common/implementation
2022-11-24 12:06:51 +11:00
dfsek 358e09d05b Merge pull request #383 from PolyhedralDev/ver/6.2.1
6.2.1 but for real this time
2022-11-16 14:03:21 -07:00
dfsek 1f7c4ee4e7 rename from "2" to "v2" 2022-11-16 14:02:34 -07:00
dfsek b3100e01e2 Merge remote-tracking branch 'origin/master' into ver/6.2.1 2022-11-16 13:49:13 -07:00
Astrash 02d3445dd9 Include deprecation notice for pipeline v1 2022-11-16 16:01:28 +11:00
Astrash c1c85174ed Re-add original pipeline implementation 2022-11-16 16:01:28 +11:00
Astrash 6cdef5d4ad Move pipeline reimplementation to separate addon 2022-11-16 16:01:27 +11:00
Astrash 57cbea4000 Replace sout with logger debug calls 2022-11-16 16:01:27 +11:00
Astrash 3522b5f17c Remove test class from source 2022-11-16 16:01:27 +11:00
Astrash 5c58bd54a3 Ephemeral -> Placeholder 2022-11-16 16:01:27 +11:00
Astrash ee6d475ad0 Move reimplementation classes into main package 2022-11-16 16:01:27 +11:00
Astrash d170b4e0fd Handle resolution and biome blending 2022-11-16 16:01:27 +11:00
Astrash bc324df7ca Working state for pipeline implementation 2022-11-16 16:01:27 +11:00
dfsek 9b75410ea7 configure bukkit resource config properly 2022-11-15 21:09:16 -07:00
dfsek 44550e8d8e Merge pull request #382 from PolyhedralDev/ver/6.2.1
update version
2022-11-15 19:20:18 -07:00
dfsek 12845a6004 update version 2022-11-15 19:17:14 -07:00
dfsek 140295602c Merge pull request #381 from PolyhedralDev/ver/6.2.1
Ver/6.2.1
2022-11-10 01:16:39 -07:00
dfsek 547f4884aa fix beard 2022-11-09 18:52:36 -07:00
dfsek 72e5ac76e6 update paper dev bundle to 1.19.2 2022-11-09 18:48:09 -07:00
dfsek 7d3bdeee8f Merge pull request #379 from PolyhedralDev/fix/ender_crystal
Fix End Crystals on Bukkit
2022-11-09 18:18:57 -07:00
dfsek e471950047 Merge pull request #380 from PolyhedralDev/fix/bukkit-beard
implement beardification on Bukkit (closes #370)
2022-11-09 18:18:42 -07:00
dfsek 1306dad776 implement beardification on Bukkit (closes #370) 2022-11-07 17:49:49 -07:00
dfsek eb9685c734 fix #378 2022-11-07 17:31:41 -07:00
solonovamax e8fa5514cc Clean up language in code of conduct's license & attribution section 2022-09-06 10:42:05 -04:00
solonovamax f1290b36d8 Fix more weird list formatting in code of conduct 2022-09-06 10:40:43 -04:00
solonovamax befc0c6636 Fix an oopsie in the code of conduct with weird list formatting 2022-09-06 10:15:01 -04:00
Zoë f851276fc2 Merge pull request #351 from Browsit/patch-2
Fix type mismatch
2022-08-26 10:56:45 -05:00
Zoë bc334fa214 Merge pull request #350 from Browsit/patch-1
Update BUG_REPORT.md
2022-08-26 10:56:26 -05:00
Browsit 829696ccc8 Use jar URI prefix
Fixes `Path component should be '/'` on Windows
2022-07-21 02:35:38 -04:00
Browsit 43adf056c3 Fix type mismatch
Just pulled the master branch and got the error:

`C:\Users\{owner}\IdeaProjects\Terra\buildSrc\src\main\kotlin\DistributionConfig.kt: (49, 39): Type mismatch: inferred type is Path! but URI! was expected`
2022-07-20 22:27:22 -04:00
Browsit f8a602790a Update BUG_REPORT.md 2022-07-20 08:28:58 -04:00
215 changed files with 6931 additions and 640 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ assignees: ""
is Terra that is causing the issue.
- [ ] I have checked that this is an issue with Terra and not an issue with the
pack I am using.
<!-- If this is an issue with the default Terra pack, please open an issue on the pack repo: https://github.com/PolyhedralDev/TerraDefaultConfig/issues/new -->
<!-- If this is an issue with the default Terra pack, please open an issue on the pack repo: https://github.com/PolyhedralDev/TerraOverworldConfig/issues/new -->
- [ ] I have attached a copy of the `latest.log` file
- [ ] I have filled out and provided all the appropriate information.
+14 -34
View File
@@ -315,19 +315,15 @@ Terra has a global moderation team which is currently comprised of the following
members:
- solonovamax
-
discord: [@solonovamax#6983](https://discord.com/channels/@me/566146322273402881)*
- discord: [@solonovamax#6983](https://discord.com/channels/@me/566146322273402881)*
- github: [@solonovamax](https://github.com/solonovamax)
-
email: [solonovamax@12oclockpoint.com](mailto:solonovamax@12oclockpoint.com)
- email: [solonovamax@12oclockpoint.com](mailto:solonovamax@12oclockpoint.com)
- dfsek
-
discord: [@dfsek#4208](https://discord.com/channels/@me/378350362236682240)*
- discord: [@dfsek#4208](https://discord.com/channels/@me/378350362236682240)*
- github: [@dfsek](https://github.com/dfsek)
- email: [dfsek@protonmail.com](mailto:dfsek@protonmail.com)
- duplex (duplexsystem)
-
discord: [@Duplex#0797](https://discord.com/channels/@me/356822848641171456)*
- discord: [@Duplex#0797](https://discord.com/channels/@me/356822848641171456)*
- github: [@duplexsystem](https://github.com/duplexsystem)
- email: [duplexsys@protonmail.com](mailto:duplexsys@protonmail.com)
@@ -401,32 +397,17 @@ issue on this github repo, or contact a member of the global moderation team.
## License and Attribution
This set of guidelines is distributed under a
[Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/3.0/)
.
[Creative Commons Attribution-ShareAlike license](https://creativecommons.org/licenses/by-sa/3.0/).
These guidelines have been adapted from
[Mozilla's Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/)
, which were adapted from:
These guidelines have been adapted with modifications from the following sources:
- Mozilla's original Community Participation Guidelines
- [Mozilla's Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/)
- The [Ubuntu Code of Conduct](https://ubuntu.com/community/code-of-conduct)
-
Mozilla's [View Source Conference Code of Conduct](https://viewsourceconf.org/berlin-2016/code-of-conduct/)
- And
the [Rust Language Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct)
which in turn were based
on [Stumptown Syndicate's Citizen Code of Conduct](http://citizencodeofconduct.org/)
, along with some adapted text from
the [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html) and
the [WisCon code of conduct](http://wiscon.net/policies/anti-harassment/code-of-conduct/)
.
It was then modified by solonovamax with various inclusions from
the [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html) and
a few other sources.
- Mozilla's [View Source Conference Code of Conduct](https://viewsourceconf.org/berlin-2016/code-of-conduct/)
- The [Rust Language Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct)
- The [Stumptown Syndicate's Citizen Code of Conduct](http://citizencodeofconduct.org/)
- The [LGBTQ in Technology Code of Conduct](https://lgbtq.technology/coc.html)
- The [WisCon code of conduct](http://wiscon.net/policies/anti-harassment/code-of-conduct/)
## Notes
@@ -443,7 +424,6 @@ people are not comfortable
using [neopronouns](https://www.mypronouns.org/neopronouns). But if someone
refuses to use your more common pronouns, you should report them to us.
Additionally, you may not ask people to use unreasonable pronouns, such as '
acab/acabself', 'that/bitch', 'ur/mom', or
'dream/dreamself' (pronouns related to real people, eg. the minecraft youtuber '
dreamwastaken'). Doing so will be considered mockery of individuals who use
acab/acabself', 'that/bitch', 'ur/mom', or anything else that may be considered
disrectful. Doing so will be considered mockery of individuals who use
non-standard pronouns and is very disrespectful.
+4 -3
View File
@@ -1,8 +1,8 @@
preRelease(true)
versionProjects(":common:api", version("6.2.0"))
versionProjects(":common:implementation", version("6.2.0"))
versionProjects(":platforms", version("6.2.0"))
versionProjects(":common:api", version("6.3.1"))
versionProjects(":common:implementation", version("6.3.1"))
versionProjects(":platforms", version("6.3.1"))
allprojects {
@@ -42,6 +42,7 @@ afterEvaluate {
forImmediateSubProjects(":platforms") {
configureDistribution()
}
project(":platforms:bukkit:common").configureDistribution()
forSubProjects(":common:addons") {
apply(plugin = "com.github.johnrengelman.shadow")
@@ -1,6 +1,7 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import java.io.File
import java.io.FileWriter
import java.net.URI
import java.net.URL
import java.nio.file.FileSystems
import java.nio.file.Files
@@ -44,7 +45,7 @@ fun Project.configureDistribution() {
doLast {
// https://github.com/johnrengelman/shadow/issues/111
val dest = tasks.named<ShadowJar>("shadowJar").get().archiveFile.get().asFile.toPath()
val dest = URI.create("jar:" + tasks.named<ShadowJar>("shadowJar").get().archiveFile.get().asFile.toURI())
FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
+9 -10
View File
@@ -4,7 +4,7 @@ object Versions {
const val paralithic = "0.7.0"
const val strata = "1.1.1"
const val cloud = "1.7.0"
const val cloud = "1.8.0"
const val slf4j = "1.7.36"
const val log4j_slf4j_impl = "2.14.1"
@@ -19,39 +19,38 @@ object Versions {
object Fabric {
const val fabricLoader = "0.14.8"
const val fabricAPI = "0.57.0+1.19"
const val fabricAPI = "0.83.1+1.20.1"
}
object Quilt {
const val quiltLoader = "0.17.0"
const val fabricApi = "2.0.0-beta.4+0.57.0-1.19"
const val fabricApi = "6.0.0-beta.3+0.76.0-1.19.4"
}
object Mod {
const val mixin = "0.11.2+mixin.0.8.5"
const val minecraft = "1.19"
const val yarn = "$minecraft+build.1"
const val fabricLoader = "0.14.2"
const val minecraft = "1.20.1"
const val yarn = "$minecraft+build.2"
const val fabricLoader = "0.14.21"
const val architecuryLoom = "0.12.0.290"
const val architecturyPlugin = "3.4-SNAPSHOT"
const val loomQuiltflower = "1.7.1"
const val lazyDfu = "0.1.2"
}
object Forge {
const val forge = "${Mod.minecraft}-41.0.63"
const val forge = "${Mod.minecraft}-47.0.3"
const val burningwave = "12.53.0"
}
object Bukkit {
const val paper = "1.18.2-R0.1-SNAPSHOT"
const val paperLib = "1.0.5"
const val minecraft = "1.19"
const val minecraft = "1.20.1"
const val reflectionRemapper = "0.1.0-SNAPSHOT"
const val paperDevBundle = "1.20.1-R0.1-SNAPSHOT"
}
object Sponge {
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Polyhedral Development
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, sublicense, 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 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 NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS 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.
@@ -0,0 +1,5 @@
# biome-provider-image-v2
Implements and registers the `IMAGE` biome provider, which
utilizes various config types provided by the `library-image` addon to
distribute biomes based on images.
@@ -0,0 +1,12 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
compileOnlyApi(project(":common:addons:library-image"))
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
}
tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
relocate("net.jafama", "com.dfsek.terra.addons.biome.image.lib.jafama")
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.image.v2;
import java.util.Optional;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ImageBiomeProvider implements BiomeProvider {
private final int resolution;
private final ColorConverter<Biome> colorConverter;
private final ColorSampler colorSampler;
public ImageBiomeProvider(ColorConverter<Biome> colorConverter, ColorSampler colorSampler, int resolution) {
this.resolution = resolution;
this.colorConverter = colorConverter;
this.colorSampler = colorSampler;
}
@Override
public Biome getBiome(int x, int y, int z, long seed) {
return getBiome(x, z);
}
public Biome getBiome(int x, int z) {
x /= resolution;
z /= resolution;
return colorConverter.apply(colorSampler.apply(x, z));
}
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z));
}
@Override
public Iterable<Biome> getBiomes() {
return colorConverter.getEntries();
}
}
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.image.v2;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.function.Supplier;
import com.dfsek.terra.addons.biome.image.v2.config.ImageProviderTemplate;
import com.dfsek.terra.addons.biome.image.v2.config.converter.ClosestBiomeColorConverterTemplate;
import com.dfsek.terra.addons.biome.image.v2.config.converter.ExactBiomeColorConverterTemplate;
import com.dfsek.terra.addons.biome.image.v2.config.converter.mapping.DefinedBiomeColorMappingTemplate;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.addons.image.converter.mapping.BiomeDefinedColorMapping;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ImageBiomeProviderAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorConverter<Biome>>>> BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorMapping<Biome>>>> BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() {
};
@Inject
private Platform platform;
@Inject
private BaseAddon addon;
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.priority(501)
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
PROVIDER_REGISTRY_KEY);
providerRegistry.register(addon.key("IMAGE"), ImageProviderTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<ColorConverter<Biome>>>> biomeColorConverterRegistry = event.getPack().getOrCreateRegistry(
BIOME_COLOR_CONVERTER_REGISTRY_KEY);
biomeColorConverterRegistry.register(addon.key("EXACT"), ExactBiomeColorConverterTemplate::new);
biomeColorConverterRegistry.register(addon.key("CLOSEST"), ClosestBiomeColorConverterTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<ColorMapping<Biome>>>> biomeColorMappingRegistry = event.getPack().getOrCreateRegistry(
BIOME_COLOR_MAPPING_REGISTRY_KEY);
biomeColorMappingRegistry.register(addon.key("USE_BIOME_COLORS"), () -> () -> new BiomeDefinedColorMapping<>(event.getPack().getRegistry(Biome.class), b -> b));
biomeColorMappingRegistry.register(addon.key("MAP"), DefinedBiomeColorMappingTemplate::new);
})
.failThrough();
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.image.v2.config;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Description;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.biome.image.v2.ImageBiomeProvider;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@SuppressWarnings("FieldMayBeFinal")
public class ImageProviderTemplate implements ObjectTemplate<BiomeProvider> {
@Value("resolution")
@Default
@Description("Sets the resolution at which to sample the image.")
private int resolution = 1;
@Value("color-sampler")
private ColorSampler colorSampler;
@Value("color-conversion")
private ColorConverter<Biome> colorConverter;
@Override
public BiomeProvider get() {
return new ImageBiomeProvider(colorConverter, colorSampler, resolution);
}
}
@@ -0,0 +1,19 @@
package com.dfsek.terra.addons.biome.image.v2.config.converter;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.image.config.converter.ClosestColorConverterTemplate;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
import com.dfsek.terra.api.world.biome.Biome;
public class ClosestBiomeColorConverterTemplate extends ClosestColorConverterTemplate<Biome> {
@Value("match")
private ColorMapping<Biome> match;
@Override
protected ColorMapping<Biome> getMapping() {
return match;
}
}
@@ -0,0 +1,37 @@
package com.dfsek.terra.addons.biome.image.v2.config.converter;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.image.config.converter.ExactColorConverterTemplate;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
import com.dfsek.terra.api.world.biome.Biome;
public class ExactBiomeColorConverterTemplate extends ExactColorConverterTemplate<Biome> {
@Value("match")
private ColorMapping<Biome> match;
@Value("else")
private Biome fallback;
@Value("ignore-alpha")
@Default
private boolean ignoreAlpha = true;
@Override
protected ColorMapping<Biome> getMapping() {
return match;
}
@Override
protected Biome getFallback() {
return fallback;
}
@Override
protected boolean ignoreAlpha() {
return ignoreAlpha;
}
}
@@ -0,0 +1,24 @@
package com.dfsek.terra.addons.biome.image.v2.config.converter.mapping;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.Map;
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
import com.dfsek.terra.addons.image.util.MapUtil;
import com.dfsek.terra.api.world.biome.Biome;
public class DefinedBiomeColorMappingTemplate implements ObjectTemplate<ColorMapping<Biome>> {
@Value("map")
Map<ColorString, Biome> map;
@Override
public ColorMapping<Biome> get() {
var map = MapUtil.mapKeys(this.map, ColorString::getColor);
return () -> map;
}
}
@@ -0,0 +1,14 @@
schema-version: 1
contributors:
- Terra contributors
id: biome-provider-image-v2
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.biome.image.v2.ImageBiomeProviderAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
docs: https://terra.polydev.org
license: MIT License
depends:
library-image: "1.+"
@@ -8,6 +8,8 @@
package com.dfsek.terra.addons.biome.image;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
@@ -24,6 +26,9 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class ImageBiomeProviderAddon implements AddonInitializer {
private static final Logger logger = LoggerFactory.getLogger(ImageBiomeProviderAddon.class);
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
@@ -45,5 +50,6 @@ public class ImageBiomeProviderAddon implements AddonInitializer {
() -> new ImageProviderTemplate(event.getPack().getRegistry(Biome.class)));
})
.failThrough();
logger.warn("The biome-provider-image addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-image-v2 addon for future pack development instead.");
}
}
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Polyhedral Development
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, sublicense, 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 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 NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS 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.
@@ -0,0 +1,12 @@
# biome-provider-pipeline-2
The second version of the Biome Pipeline, a procedural biome provider that uses a series
of "stages" to apply "mutations" to a 2D grid of biomes.
Version 2 is a re-implementation of the original addon with the primary goal of providing
consistent scaling for noise relative to the world
(See https://github.com/PolyhedralDev/Terra/issues/264 for more details), and has been
included as a separate addon to maintain parity with packs utilizing the first version.
This addon registers the `PIPELINE` biome provider type, and all associated
configurations.
@@ -0,0 +1,12 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
}
tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
relocate("net.jafama", "com.dfsek.terra.addons.biome.pipeline.lib.jafama")
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.function.Supplier;
import com.dfsek.terra.addons.biome.pipeline.v2.config.BiomePipelineTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.PipelineBiomeLoader;
import com.dfsek.terra.addons.biome.pipeline.v2.config.source.SamplerSourceTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander.ExpanderStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderListStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceListStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.SmoothStageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPostLoadEvent;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomePipelineAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<Source>>> SOURCE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<Stage>>> STAGE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
};
@Inject
private Platform platform;
@Inject
private BaseAddon addon;
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
PROVIDER_REGISTRY_KEY);
providerRegistry.register(addon.key("PIPELINE"), BiomePipelineTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Source>>> sourceRegistry = event.getPack().getOrCreateRegistry(
SOURCE_REGISTRY_KEY);
sourceRegistry.register(addon.key("SAMPLER"), SamplerSourceTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<Stage>>> stageRegistry = event.getPack().getOrCreateRegistry(
STAGE_REGISTRY_KEY);
stageRegistry.register(addon.key("FRACTAL_EXPAND"), ExpanderStageTemplate::new);
stageRegistry.register(addon.key("SMOOTH"), SmoothStageTemplate::new);
stageRegistry.register(addon.key("REPLACE"), ReplaceStageTemplate::new);
stageRegistry.register(addon.key("REPLACE_LIST"), ReplaceListStageTemplate::new);
stageRegistry.register(addon.key("BORDER"), BorderStageTemplate::new);
stageRegistry.register(addon.key("BORDER_LIST"), BorderListStageTemplate::new);
})
.failThrough();
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPostLoadEvent.class)
.then(event -> {
Registry<Biome> biomeRegistry = event.getPack().getRegistry(Biome.class);
event.getPack().applyLoader(PipelineBiome.class, new PipelineBiomeLoader(biomeRegistry));
});
}
}
@@ -0,0 +1,71 @@
package com.dfsek.terra.addons.biome.pipeline.v2;
import java.util.function.Consumer;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.util.function.IntIntObjConsumer;
import com.dfsek.terra.api.util.function.IntObjConsumer;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomePipelineColumn implements Column<Biome> {
private final int min;
private final int max;
private final int x;
private final int z;
private final Biome biome;
protected BiomePipelineColumn(BiomeProvider biomeProvider, int min, int max, int x, int z, long seed) {
this.min = min;
this.max = max;
this.x = x;
this.z = z;
this.biome = biomeProvider.getBiome(x, 0, z, seed);
}
@Override
public int getMinY() {
return min;
}
@Override
public int getMaxY() {
return max;
}
@Override
public int getX() {
return x;
}
@Override
public int getZ() {
return z;
}
@Override
public Biome get(int y) {
return biome;
}
@Override
public void forRanges(int resolution, IntIntObjConsumer<Biome> consumer) {
consumer.accept(min, max, biome);
}
@Override
public void forEach(Consumer<Biome> consumer) {
for(int y = min; y < max; y++) {
consumer.accept(biome);
}
}
@Override
public void forEach(IntObjConsumer<Biome> consumer) {
for(int y = min; y < max; y++) {
consumer.accept(y, biome);
}
}
}
@@ -0,0 +1,115 @@
package com.dfsek.terra.addons.biome.pipeline.v2;
import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline;
import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import net.jafama.FastMath;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.StreamSupport;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.registry.key.StringIdentifiable;
import com.dfsek.terra.api.util.Column;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class PipelineBiomeProvider implements BiomeProvider {
private final LoadingCache<SeededVector, BiomeChunk> biomeChunkCache;
private final int chunkSize;
private final int resolution;
private final NoiseSampler mutator;
private final double noiseAmp;
private final Set<Biome> biomes;
public PipelineBiomeProvider(Pipeline pipeline, int resolution, NoiseSampler mutator, double noiseAmp) {
this.resolution = resolution;
this.mutator = mutator;
this.noiseAmp = noiseAmp;
this.chunkSize = pipeline.getChunkSize();
this.biomeChunkCache = Caffeine.newBuilder()
.maximumSize(1024)
.build(pipeline::generateChunk);
Set<PipelineBiome> biomeSet = new HashSet<>();
pipeline.getSource().getBiomes().forEach(biomeSet::add);
Iterable<PipelineBiome> result = biomeSet;
for(Stage stage : pipeline.getStages()) {
result = stage.getBiomes(result);
}
this.biomes = new HashSet<>();
Iterable<PipelineBiome> finalResult = result;
result.forEach(pipelineBiome -> {
if(pipelineBiome.isPlaceholder()) {
StringBuilder biomeList = new StringBuilder("\n");
StreamSupport.stream(finalResult.spliterator(), false)
.sorted(Comparator.comparing(StringIdentifiable::getID))
.forEach(delegate -> biomeList
.append(" - ")
.append(delegate.getID())
.append(':')
.append(delegate.getClass().getCanonicalName())
.append('\n'));
throw new IllegalArgumentException("Biome Pipeline leaks placeholder biome \"" + pipelineBiome.getID() +
"\". Ensure there is a stage to guarantee replacement of the placeholder biome. Biomes: " +
biomeList);
}
this.biomes.add(pipelineBiome.getBiome());
});
}
@Override
public Biome getBiome(int x, int y, int z, long seed) {
return getBiome(x, z, seed);
}
public Biome getBiome(int x, int z, long seed) {
x += mutator.noise(seed + 1, x, z) * noiseAmp;
z += mutator.noise(seed + 2, x, z) * noiseAmp;
x /= resolution;
z /= resolution;
int chunkX = FastMath.floorDiv(x, chunkSize);
int chunkZ = FastMath.floorDiv(z, chunkSize);
int chunkWorldX = chunkX * chunkSize;
int chunkWorldZ = chunkZ * chunkSize;
int xInChunk = x - chunkWorldX;
int zInChunk = z - chunkWorldZ;
return biomeChunkCache.get(new SeededVector(seed, chunkWorldX, chunkWorldZ)).get(xInChunk, zInChunk).getBiome();
}
@Override
public Iterable<Biome> getBiomes() {
return biomes;
}
@Override
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
return Optional.of(getBiome(x, z, seed));
}
@Override
public Column<Biome> getColumn(int x, int z, long seed, int min, int max) {
return new BiomePipelineColumn(this, min, max, x, z, seed);
}
@Override
public int resolution() {
return resolution;
}
}
@@ -0,0 +1,10 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
public interface BiomeChunk {
PipelineBiome get(int xInChunk, int zInChunk);
}
@@ -0,0 +1,29 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
/**
* Resizes the internal grid of a BiomeChunk when applied, serves the purpose of
* filling in null biomes as a result of this resizing.
*/
public interface Expander extends Stage {
PipelineBiome fillBiome(ViewPoint viewPoint);
@Override
default int maxRelativeReadDistance() {
return 0;
}
@Override
default PipelineBiome apply(ViewPoint viewPoint) {
PipelineBiome currentBiome = viewPoint.getBiome();
if(currentBiome == null) {
return fillBiome(viewPoint);
} else {
return currentBiome;
}
}
}
@@ -0,0 +1,14 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import java.util.List;
public interface Pipeline {
BiomeChunk generateChunk(SeededVector worldCoordinates);
int getChunkSize();
Source getSource();
List<Stage> getStages();
}
@@ -0,0 +1,19 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
public record SeededVector(long seed, int x, int z) {
@Override
public boolean equals(Object obj) {
if(obj instanceof SeededVector that) {
return this.z == that.z && this.x == that.x && this.seed == that.seed;
}
return false;
}
@Override
public int hashCode() {
int code = x;
code = 31 * code + z;
return 31 * code + ((int) (seed ^ (seed >>> 32)));
}
}
@@ -0,0 +1,11 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
public interface Source {
PipelineBiome get(long seed, int x, int z);
Iterable<PipelineBiome> getBiomes();
}
@@ -0,0 +1,15 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
public interface Stage {
PipelineBiome apply(ViewPoint viewPoint);
int maxRelativeReadDistance();
default Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
return biomes;
}
}
@@ -0,0 +1,40 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
public final class DelegatedPipelineBiome implements PipelineBiome {
private final Biome biome;
public DelegatedPipelineBiome(Biome biome) {
this.biome = biome;
}
@Override
public Biome getBiome() {
return biome;
}
@Override
public int hashCode() {
return biome.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof DelegatedPipelineBiome that)) return false;
return that.biome.equals(this.biome);
}
@Override
public Set<String> getTags() {
return biome.getTags();
}
@Override
public String getID() {
return biome.getID();
}
}
@@ -0,0 +1,35 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Set;
import com.dfsek.terra.api.registry.key.StringIdentifiable;
import com.dfsek.terra.api.world.biome.Biome;
public interface PipelineBiome extends StringIdentifiable {
Biome getBiome();
static PipelineBiome placeholder(String id) {
return new PlaceholderPipelineBiome(id);
}
static PipelineBiome from(Biome biome) {
return new DelegatedPipelineBiome(biome);
}
static PipelineBiome self() {
return SelfPipelineBiome.INSTANCE;
}
Set<String> getTags();
default boolean isPlaceholder() {
return false;
}
default boolean isSelf() {
return false;
}
}
@@ -0,0 +1,51 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.HashSet;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
final class PlaceholderPipelineBiome implements PipelineBiome {
private final Set<String> tags;
private final String id;
public PlaceholderPipelineBiome(String id) {
this.id = id;
tags = new HashSet<>();
tags.add(id);
tags.add("ALL");
}
@Override
public Biome getBiome() {
throw new UnsupportedOperationException("Cannot get raw biome from placeholder pipeline biome");
}
@Override
public Set<String> getTags() {
return tags;
}
@Override
public String getID() {
return id;
}
@Override
public boolean isPlaceholder() {
return true;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof PlaceholderPipelineBiome that)) return false;
return this.id.equals(that.id);
}
}
@@ -0,0 +1,40 @@
package com.dfsek.terra.addons.biome.pipeline.v2.api.biome;
import java.util.Collections;
import java.util.Set;
import com.dfsek.terra.api.world.biome.Biome;
final class SelfPipelineBiome implements PipelineBiome {
public static final SelfPipelineBiome INSTANCE = new SelfPipelineBiome();
private SelfPipelineBiome() {
}
@Override
public Biome getBiome() {
throw new UnsupportedOperationException("Cannot get biome from self delegate");
}
@Override
public boolean isSelf() {
return true;
}
@Override
public boolean isPlaceholder() {
return true;
}
@Override
public Set<String> getTags() {
return Collections.emptySet();
}
@Override
public String getID() {
return "SELF";
}
}
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Description;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.List;
import com.dfsek.terra.addons.biome.pipeline.v2.PipelineBiomeProvider;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.PipelineImpl;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
@SuppressWarnings({ "FieldMayBeFinal", "unused" })
public class BiomePipelineTemplate implements ObjectTemplate<BiomeProvider> {
@Value("resolution")
@Default
@Description("""
The resolution at which to sample biomes.
Larger values are quadratically faster, but produce lower quality results.
For example, a value of 3 would sample every 3 blocks.""")
protected @Meta int resolution = 1;
@Value("pipeline.source")
@Description("The Biome Source to use for initial population of biomes.")
private @Meta Source source;
@Value("pipeline.stages")
@Description("A list of pipeline stages to apply to the result of #source")
private @Meta List<@Meta Stage> stages;
@Value("blend.sampler")
@Default
@Description("A sampler to use for blending the edges of biomes via domain warping.")
protected @Meta NoiseSampler blendSampler = NoiseSampler.zero();
@Value("blend.amplitude")
@Default
@Description("The amplitude at which to perform blending.")
protected @Meta double blendAmplitude = 0d;
@Override
public BiomeProvider get() {
return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 500), resolution, blendSampler, blendAmplitude);
}
}
@@ -0,0 +1,32 @@
package com.dfsek.terra.addons.biome.pipeline.v2.config;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.api.registry.Registry;
import com.dfsek.terra.api.world.biome.Biome;
public class PipelineBiomeLoader implements TypeLoader<PipelineBiome> {
private final Registry<Biome> biomeRegistry;
public PipelineBiomeLoader(Registry<Biome> biomeRegistry) {
this.biomeRegistry = biomeRegistry;
}
@Override
public PipelineBiome load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker)
throws LoadException {
if(c.equals("SELF")) return PipelineBiome.self();
return biomeRegistry
.getByID((String) c)
.map(PipelineBiome::from)
.orElseGet(() -> PipelineBiome.placeholder((String) c));
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.source;
import com.dfsek.tectonic.api.config.template.annotations.Description;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.source.SamplerSource;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class SamplerSourceTemplate extends SourceTemplate {
@Value("sampler")
@Description("The sampler used to distribute biomes.")
private @Meta NoiseSampler noise;
@Value("biomes")
@Description("The biomes to be distributed.")
private @Meta ProbabilityCollection<@Meta PipelineBiome> biomes;
@Override
public Source get() {
return new SamplerSource(biomes, noise);
}
}
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.source;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
public abstract class SourceTemplate implements ObjectTemplate<Source> {
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage;
import com.dfsek.tectonic.api.config.template.annotations.Description;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.noise.NoiseSampler;
public abstract class StageTemplate implements ObjectTemplate<Stage> {
@Value("sampler")
@Description("Sampler to use for stage distribution.")
protected @Meta NoiseSampler noise;
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.expander.FractalExpander;
public class ExpanderStageTemplate extends StageTemplate {
@Override
public Expander get() {
return new FractalExpander(noise);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import java.util.Map;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderListStage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
@SuppressWarnings("unused")
public class BorderListStageTemplate extends StageTemplate {
@Value("from")
private @Meta String from;
@Value("default-replace")
private @Meta String defaultReplace;
@Value("default-to")
private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo;
@Value("replace")
private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace;
@Override
public Stage get() {
return new BorderListStage(replace, from, defaultReplace, noise, defaultTo);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderStage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
@SuppressWarnings("unused")
public class BorderStageTemplate extends StageTemplate {
@Value("from")
private @Meta String from;
@Value("replace")
private @Meta String replace;
@Value("to")
private @Meta ProbabilityCollection<@Meta PipelineBiome> to;
@Override
public Stage get() {
return new BorderStage(from, replace, noise, to);
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import java.util.Map;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceListStage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
@SuppressWarnings("unused")
public class ReplaceListStageTemplate extends StageTemplate {
@Value("default-from")
private @Meta String defaultFrom;
@Value("default-to")
private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo;
@Value("to")
private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace;
@Override
public Stage get() {
return new ReplaceListStage(replace, defaultFrom, defaultTo, noise);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceStage;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
@SuppressWarnings("unused")
public class ReplaceStageTemplate extends StageTemplate {
@Value("from")
private @Meta String from;
@Value("to")
private @Meta ProbabilityCollection<@Meta PipelineBiome> to;
@Override
public Stage get() {
return new ReplaceStage(from, to, noise);
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator;
import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.SmoothStage;
public class SmoothStageTemplate extends StageTemplate {
@Override
public Stage get() {
return new SmoothStage(noise);
}
}
@@ -0,0 +1,217 @@
package com.dfsek.terra.addons.biome.pipeline.v2.pipeline;
import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import net.jafama.FastMath;
import java.util.List;
public class BiomeChunkImpl implements BiomeChunk {
private PipelineBiome[][] biomes;
private final SeededVector worldOrigin;
private final int chunkOriginArrayIndex;
private final int worldCoordinateScale;
public BiomeChunkImpl(SeededVector worldOrigin, PipelineImpl pipeline) {
this.worldOrigin = worldOrigin;
this.chunkOriginArrayIndex = pipeline.getChunkOriginArrayIndex();
this.worldCoordinateScale = pipeline.getResolution();
int size = pipeline.getArraySize();
int expanderCount = pipeline.getExpanderCount();
int expansionsApplied = 0;
// Allocate working arrays
this.biomes = new PipelineBiome[size][size];
PipelineBiome[][] lookupArray = new PipelineBiome[size][size];
// A second lookup array is required such that stage application doesn't affect lookups, otherwise application may cascade
// Construct working grid
int gridOrigin = 0;
int gridInterval = calculateGridInterval(expanderCount, expansionsApplied);
int gridSize = (size / gridInterval);
gridSize += expanderCount > 0 ? 1 : 0; // Add an extra border if expansion occurs
// Fill working grid with initial cells
for(int gridX = 0; gridX < gridSize; gridX++) {
for(int gridZ = 0; gridZ < gridSize; gridZ++) {
int xIndex = gridOrigin + gridX * gridInterval;
int zIndex = gridOrigin + gridZ * gridInterval;
biomes[xIndex][zIndex] = pipeline.getSource().get(worldOrigin.seed(), xIndexToWorldCoordinate(xIndex), zIndexToWorldCoordinate(zIndex));
}
}
for(Stage stage : pipeline.getStages()) {
if(stage instanceof Expander) {
// Shrink working grid size, the expander will fill in null cells (as a result of shrinking the grid) during mutation
expansionsApplied++;
gridInterval = calculateGridInterval(expanderCount, expansionsApplied);
gridSize = expandSize(gridSize);
}
int stageReadDistance = stage.maxRelativeReadDistance();
if(stageReadDistance > 0) {
// Discard edges such that adjacent lookups are only ran on valid cells
gridSize = contractBordersFromSize(gridSize, stageReadDistance);
gridOrigin += stageReadDistance * gridInterval;
}
// Cycle arrays, the previously populated array is swapped to be used for lookups, and the result of the stage application
// overwrites the previous lookup array. This saves having to allocate a new array copy each time
PipelineBiome[][] tempArray = biomes;
biomes = lookupArray;
lookupArray = tempArray;
// Apply stage to working grid
for(int gridZ = 0; gridZ < gridSize; gridZ = gridZ + 1) {
for(int gridX = 0; gridX < gridSize; gridX = gridX + 1) {
int xIndex = gridOrigin + gridX * gridInterval;
int zIndex = gridOrigin + gridZ * gridInterval;
biomes[xIndex][zIndex] = stage.apply(new ViewPoint(this, gridInterval, gridX, gridZ, xIndex, zIndex, lookupArray));
}
}
}
}
@Override
public PipelineBiome get(int xInChunk, int zInChunk) {
int xIndex = xInChunk + chunkOriginArrayIndex;
int zIndex = zInChunk + chunkOriginArrayIndex;
return biomes[xIndex][zIndex];
}
private int xIndexToWorldCoordinate(int xIndex) {
return (worldOrigin.x() + xIndex - chunkOriginArrayIndex) * worldCoordinateScale;
}
private int zIndexToWorldCoordinate(int zIndex) {
return (worldOrigin.z() + zIndex - chunkOriginArrayIndex) * worldCoordinateScale;
}
protected static int initialSizeToArraySize(int expanderCount, int initialSize) {
int size = initialSize;
for(int i = 0; i < expanderCount; i++) {
size = expandSize(size);
}
return size;
}
protected static int calculateChunkOriginArrayIndex(int totalExpanderCount, List<Stage> stages) {
int finalGridOrigin = calculateFinalGridOrigin(totalExpanderCount, stages);
int initialGridInterval = calculateGridInterval(totalExpanderCount, 0);
// Round the final grid origin up to the nearest multiple of initialGridInterval, such that each
// chunk samples points on the same overall grid.
// Without this, shared chunk borders (required because of adjacent cell reads) will not be identical
// because points would be sampled on grids at different offsets, resulting in artifacts at borders.
return FastMath.ceilToInt((double) finalGridOrigin / initialGridInterval) * initialGridInterval;
}
private static int calculateFinalGridOrigin(int totalExpanderCount, List<Stage> stages) {
int gridOrigin = 0;
int expansionsApplied = 0;
int gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied);
for (Stage stage : stages) {
if (stage instanceof Expander) {
expansionsApplied++;
gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied);
}
gridOrigin += stage.maxRelativeReadDistance() * gridInterval;
}
return gridOrigin;
}
protected static int calculateChunkSize(int arraySize, int chunkOriginArrayIndex, int totalExpanderCount) {
return contractBordersFromSize(arraySize, chunkOriginArrayIndex) - (totalExpanderCount > 0 ? 1 : 0);
}
private static int expandSize(int size) {
return size * 2 - 1;
}
private static int contractBordersFromSize(int size, int border) {
return size - border * 2;
}
private static int calculateGridInterval(int totalExpansions, int expansionsApplied) {
return 1 << (totalExpansions - expansionsApplied);
}
private SeededVector getOrigin() {
return worldOrigin;
}
/**
* Represents a point on the operating grid within the biomes array
*/
public static class ViewPoint {
private final BiomeChunkImpl chunk;
private final PipelineBiome biome;
private final int gridInterval;
private final int gridX;
private final int gridZ;
private final int xIndex;
private final int zIndex;
private final PipelineBiome[][] lookupArray;
private ViewPoint(BiomeChunkImpl chunk, int gridInterval, int gridX, int gridZ, int xIndex, int zIndex, PipelineBiome[][] lookupArray) {
this.chunk = chunk;
this.gridInterval = gridInterval;
this.gridX = gridX;
this.gridZ = gridZ;
this.xIndex = xIndex;
this.zIndex = zIndex;
this.lookupArray = lookupArray;
this.biome = lookupArray[xIndex][zIndex];
}
public PipelineBiome getRelativeBiome(int x, int z) {
int lookupXIndex = this.xIndex + x * gridInterval;
int lookupZIndex = this.zIndex + z * gridInterval;
return lookupArray[lookupXIndex][lookupZIndex];
}
public PipelineBiome getBiome() {
return biome;
}
/**
* @return X position of the point relative to the operating grid
*/
public int gridX() {
return gridX;
}
/**
* @return Z position of the point relative to the operating grid
*/
public int gridZ() {
return gridZ;
}
/**
* @return X position of the point in the world
*/
public int worldX() {
return chunk.xIndexToWorldCoordinate(xIndex);
}
/**
* @return Z position of the point in the world
*/
public int worldZ() {
return chunk.zIndexToWorldCoordinate(zIndex);
}
public long worldSeed() {
return chunk.getOrigin().seed();
}
}
}
@@ -0,0 +1,92 @@
package com.dfsek.terra.addons.biome.pipeline.v2.pipeline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline;
import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
public class PipelineImpl implements Pipeline {
private static final Logger logger = LoggerFactory.getLogger(PipelineImpl.class);
private final Source source;
private final List<Stage> stages;
private final int chunkSize;
private final int expanderCount;
private final int arraySize;
private final int chunkOriginArrayIndex;
private final int resolution;
public PipelineImpl(Source source, List<Stage> stages, int resolution, int idealChunkArraySize) {
this.source = source;
this.stages = stages;
this.resolution = resolution;
this.expanderCount = (int) stages.stream().filter(s -> s instanceof Expander).count();
// Optimize for the ideal array size
int arraySize;
int chunkOriginArrayIndex;
int chunkSize;
int initialSize = 1;
while (true) {
arraySize = BiomeChunkImpl.initialSizeToArraySize(expanderCount, initialSize);
chunkOriginArrayIndex = BiomeChunkImpl.calculateChunkOriginArrayIndex(expanderCount, stages);
chunkSize = BiomeChunkImpl.calculateChunkSize(arraySize, chunkOriginArrayIndex, expanderCount);
if (chunkSize > 1 && arraySize >= idealChunkArraySize) break;
initialSize++;
}
this.arraySize = arraySize;
this.chunkOriginArrayIndex = chunkOriginArrayIndex;
this.chunkSize = chunkSize;
logger.debug("Initialized a new biome pipeline:");
logger.debug("Array size: {} (Target: {})", arraySize, idealChunkArraySize);
logger.debug("Internal array origin: {}", chunkOriginArrayIndex);
logger.debug("Chunk size: {}", chunkSize);
}
@Override
public BiomeChunk generateChunk(SeededVector worldCoordinates) {
return new BiomeChunkImpl(worldCoordinates, this);
}
@Override
public int getChunkSize() {
return chunkSize;
}
@Override
public Source getSource() {
return source;
}
@Override
public List<Stage> getStages() {
return stages;
}
protected int getExpanderCount() {
return expanderCount;
}
protected int getArraySize() {
return arraySize;
}
protected int getChunkOriginArrayIndex() {
return chunkOriginArrayIndex;
}
protected int getResolution() {
return resolution;
}
}
@@ -0,0 +1,27 @@
package com.dfsek.terra.addons.biome.pipeline.v2.source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class SamplerSource implements Source {
private final ProbabilityCollection<PipelineBiome> biomes;
private final NoiseSampler sampler;
public SamplerSource(ProbabilityCollection<PipelineBiome> biomes, NoiseSampler sampler) {
this.biomes = biomes;
this.sampler = sampler;
}
@Override
public PipelineBiome get(long seed, int x, int z) {
return biomes.get(sampler, x, z, seed);
}
@Override
public Iterable<PipelineBiome> getBiomes() {
return biomes.getContents();
}
}
@@ -0,0 +1,27 @@
package com.dfsek.terra.addons.biome.pipeline.v2.source;
import java.util.Collections;
import java.util.Set;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
public class SingleSource implements Source {
private final PipelineBiome biome;
public SingleSource(PipelineBiome biome) {
this.biome = biome;
}
@Override
public PipelineBiome get(long seed, int x, int z) {
return biome;
}
@Override
public Set<PipelineBiome> getBiomes() {
return Collections.singleton(biome);
}
}
@@ -0,0 +1,37 @@
package com.dfsek.terra.addons.biome.pipeline.v2.stage.expander;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
public class FractalExpander implements Expander {
private final NoiseSampler sampler;
public FractalExpander(NoiseSampler sampler) {
this.sampler = sampler;
}
@Override
public PipelineBiome fillBiome(BiomeChunkImpl.ViewPoint viewPoint) {
int xMod2 = viewPoint.gridX() % 2;
int zMod2 = viewPoint.gridZ() % 2;
double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ());
if (xMod2 == 1 && zMod2 == 0) { // Pick one of 2 neighbors on X axis randomly
return roll > 0 ? viewPoint.getRelativeBiome(-1, 0) : viewPoint.getRelativeBiome(1, 0);
} else if (xMod2 == 0 && zMod2 == 1) { // Pick one of 2 neighbors on Z axis randomly
return roll > 0 ? viewPoint.getRelativeBiome(0, -1) : viewPoint.getRelativeBiome(0, 1);
} else { // Pick one of 4 corners randomly
return roll > 0 ?
roll > 0.25 ? viewPoint.getRelativeBiome(-1, 1) : viewPoint.getRelativeBiome(1, 1) :
roll > -0.25 ? viewPoint.getRelativeBiome(-1, -1) : viewPoint.getRelativeBiome(1, -1);
}
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
import com.dfsek.terra.api.util.vector.Vector2Int;
public class BorderListStage implements Stage {
private final String border;
private final NoiseSampler noiseSampler;
private final ProbabilityCollection<PipelineBiome> replaceDefault;
private final String defaultReplace;
private final Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace;
private final Vector2Int[] borderPoints;
public BorderListStage(Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace, String border, String defaultReplace,
NoiseSampler noiseSampler, ProbabilityCollection<PipelineBiome> replaceDefault) {
this.border = border;
this.noiseSampler = noiseSampler;
this.replaceDefault = replaceDefault;
this.defaultReplace = defaultReplace;
this.replace = replace;
List<Vector2Int> points = new ArrayList<>();
for(int x = -1; x <= 1; x++) {
for(int z = -1; z <= 1; z++) {
if(x == 0 && z == 0) continue;
points.add(Vector2Int.of(x, z));
}
}
this.borderPoints = points.toArray(new Vector2Int[0]);
}
@Override
public Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
Set<PipelineBiome> biomeSet = new HashSet<>();
biomes.forEach(biomeSet::add);
biomeSet.addAll(replaceDefault.getContents().stream().filter(Predicate.not(PipelineBiome::isSelf)).toList());
replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents()));
return biomeSet;
}
@Override
public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
PipelineBiome center = viewPoint.getBiome();
if(center.getTags().contains(defaultReplace)) {
for(Vector2Int point : borderPoints) {
PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ());
if(current != null && current.getTags().contains(border)) {
if(replace.containsKey(center)) {
PipelineBiome replacement = replace.get(center).get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(),
viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
PipelineBiome replacement = replaceDefault.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
}
}
return center;
}
@Override
public int maxRelativeReadDistance() {
return 1;
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
import com.dfsek.terra.api.util.vector.Vector2Int;
public class BorderStage implements Stage {
private final String border;
private final NoiseSampler noiseSampler;
private final ProbabilityCollection<PipelineBiome> replace;
private final String replaceTag;
private final Vector2Int[] borderPoints;
public BorderStage(String border, String replaceTag, NoiseSampler noiseSampler, ProbabilityCollection<PipelineBiome> replace) {
this.border = border;
this.noiseSampler = noiseSampler;
this.replace = replace;
this.replaceTag = replaceTag;
List<Vector2Int> points = new ArrayList<>();
for(int x = -1; x <= 1; x++) {
for(int z = -1; z <= 1; z++) {
if(x == 0 && z == 0) continue;
points.add(Vector2Int.of(x, z));
}
}
this.borderPoints = points.toArray(new Vector2Int[0]);
}
@Override
public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
PipelineBiome center = viewPoint.getBiome();
if(center.getTags().contains(replaceTag)) {
for(Vector2Int point : borderPoints) {
PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ());
if(current != null && current.getTags().contains(border)) {
PipelineBiome replacement = replace.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return replacement.isSelf() ? center : replacement;
}
}
}
return center;
}
@Override
public Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
Set<PipelineBiome> biomeSet = new HashSet<>();
biomes.forEach(biomeSet::add);
biomeSet.addAll(
replace
.getContents()
.stream()
.filter(
Predicate.not(PipelineBiome::isSelf)
)
.toList()
);
return biomeSet;
}
@Override
public int maxRelativeReadDistance() {
return 1;
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class ReplaceListStage implements Stage {
private final Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace;
private final NoiseSampler sampler;
private final ProbabilityCollection<PipelineBiome> replaceDefault;
private final String defaultTag;
public ReplaceListStage(Map<PipelineBiome, ProbabilityCollection<PipelineBiome>> replace, String defaultTag,
ProbabilityCollection<PipelineBiome> replaceDefault, NoiseSampler sampler) {
this.replace = replace;
this.sampler = sampler;
this.defaultTag = defaultTag;
this.replaceDefault = replaceDefault;
}
@Override
public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
PipelineBiome center = viewPoint.getBiome();
if(replace.containsKey(center)) {
PipelineBiome biome = replace.get(center).get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return biome.isSelf() ? viewPoint.getBiome() : biome;
}
if(viewPoint.getBiome().getTags().contains(defaultTag)) {
PipelineBiome biome = replaceDefault.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return biome.isSelf() ? viewPoint.getBiome() : biome;
}
return center;
}
@Override
public int maxRelativeReadDistance() {
return 0;
}
@Override
public Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
Set<PipelineBiome> biomeSet = new HashSet<>();
Set<PipelineBiome> reject = new HashSet<>();
biomes.forEach(biome -> {
if(!biome.getTags().contains(defaultTag) && !replace.containsKey(biome)) {
biomeSet.add(biome);
} else {
reject.add(biome);
}
});
biomeSet.addAll(replaceDefault.getContents().stream().flatMap(terraBiome -> {
if(terraBiome.isSelf()) return reject.stream();
return Stream.of(terraBiome);
}).toList());
replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents().stream().map(terraBiome -> {
if(terraBiome.isSelf()) return biome;
return terraBiome;
}).toList()));
return biomeSet;
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public class ReplaceStage implements Stage {
private final String replaceableTag;
private final ProbabilityCollection<PipelineBiome> replace;
private final NoiseSampler sampler;
public ReplaceStage(String replaceable, ProbabilityCollection<PipelineBiome> replace, NoiseSampler sampler) {
this.replaceableTag = replaceable;
this.replace = replace;
this.sampler = sampler;
}
@Override
public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
if(viewPoint.getBiome().getTags().contains(replaceableTag)) {
PipelineBiome biome = replace.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed());
return biome.isSelf() ? viewPoint.getBiome() : biome;
}
return viewPoint.getBiome();
}
@Override
public int maxRelativeReadDistance() {
return 0;
}
@Override
public Iterable<PipelineBiome> getBiomes(Iterable<PipelineBiome> biomes) {
Set<PipelineBiome> biomeSet = new HashSet<>();
Set<PipelineBiome> reject = new HashSet<>();
biomes.forEach(biome -> {
if(!biome.getTags().contains(replaceableTag)) {
biomeSet.add(biome);
} else {
reject.add(biome);
}
});
biomeSet.addAll(replace.getContents().stream().flatMap(terraBiome -> {
if(terraBiome.isSelf()) return reject.stream();
return Stream.of(terraBiome);
}).toList());
return biomeSet;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators;
import java.util.Objects;
import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage;
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl;
import com.dfsek.terra.api.noise.NoiseSampler;
public class SmoothStage implements Stage {
private final NoiseSampler sampler;
public SmoothStage(NoiseSampler sampler) {
this.sampler = sampler;
}
@Override
public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) {
PipelineBiome top = viewPoint.getRelativeBiome(1, 0);
PipelineBiome bottom = viewPoint.getRelativeBiome(-1, 0);
PipelineBiome left = viewPoint.getRelativeBiome(0, 1);
PipelineBiome right = viewPoint.getRelativeBiome(0, -1);
double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ());
boolean vert = Objects.equals(top, bottom);
boolean horiz = Objects.equals(left, right);
if(vert && horiz) {
return roll > 0 ?
roll > 0.25 ? left : right :
roll > -0.25 ? top : bottom;
}
if(vert) {
return roll > 0 ? top : bottom;
}
if(horiz) {
return roll > 0 ? left : right;
}
return viewPoint.getBiome();
}
@Override
public int maxRelativeReadDistance() {
return 1;
}
}
@@ -0,0 +1,12 @@
schema-version: 1
contributors:
- Terra contributors
id: biome-provider-pipeline-v2
version: @VERSION@
entrypoints:
- "com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon"
website:
issues: https://github.com/PolyhedralDev/Terra/issues
source: https://github.com/PolyhedralDev/Terra
docs: https://terra.polydev.org
license: MIT License
@@ -8,6 +8,8 @@
package com.dfsek.terra.addons.biome.pipeline;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
@@ -39,6 +41,8 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
public class BiomePipelineAddon implements AddonInitializer {
private static final Logger logger = LoggerFactory.getLogger(BiomePipelineAddon.class);
public static final TypeKey<Supplier<ObjectTemplate<BiomeSource>>> SOURCE_REGISTRY_KEY = new TypeKey<>() {
};
@@ -85,5 +89,7 @@ public class BiomePipelineAddon implements AddonInitializer {
Registry<Biome> biomeRegistry = event.getPack().getRegistry(Biome.class);
event.getPack().applyLoader(BiomeDelegate.class, new BiomeDelegateLoader(biomeRegistry));
});
logger.warn("The biome-provider-pipeline addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-pipeline-v2 addon for future pack development instead.");
}
}
@@ -11,9 +11,10 @@ import com.dfsek.terra.addons.chunkgenerator.config.NoiseChunkGeneratorPackConfi
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseConfigTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.addons.chunkgenerator.config.palette.BiomePaletteTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.config.palette.SlantLayer;
import com.dfsek.terra.addons.chunkgenerator.config.palette.slant.SlantLayerTemplate;
import com.dfsek.terra.addons.chunkgenerator.generation.NoiseChunkGenerator3D;
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
@@ -36,15 +37,20 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
@Override
public void initialize() {
PropertyKey<PaletteInfo> paletteInfoPropertyKey = Context.create(PaletteInfo.class);
PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey = Context.create(BiomePaletteInfo.class);
PropertyKey<BiomeNoiseProperties> noisePropertiesPropertyKey = Context.create(BiomeNoiseProperties.class);
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.priority(1000)
.then(event -> {
event.getPack().applyLoader(SlantHolder.CalculationMethod.class,
(type, o, loader, depthTracker) -> SlantHolder.CalculationMethod.valueOf((String) o));
NoiseChunkGeneratorPackConfigTemplate config = event.loadTemplate(new NoiseChunkGeneratorPackConfigTemplate());
event.getPack().getContext().put(config);
event.getPack()
.getOrCreateRegistry(ChunkGeneratorProvider.class)
.register(addon.key("NOISE_3D"),
@@ -53,7 +59,7 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
config.getVerticalRes(), noisePropertiesPropertyKey,
paletteInfoPropertyKey));
event.getPack()
.applyLoader(SlantLayer.class, SlantLayer::new);
.applyLoader(SlantHolder.Layer.class, SlantLayerTemplate::new);
})
.failThrough();
@@ -62,8 +68,10 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
.register(addon, ConfigurationLoadEvent.class)
.then(event -> {
if(event.is(Biome.class)) {
NoiseChunkGeneratorPackConfigTemplate config = event.getPack().getContext().get(NoiseChunkGeneratorPackConfigTemplate.class);
event.getLoadedObject(Biome.class).getContext().put(paletteInfoPropertyKey,
event.load(new BiomePaletteTemplate(platform)).get());
event.load(new BiomePaletteTemplate(platform, config.getSlantCalculationMethod())).get());
event.getLoadedObject(Biome.class).getContext().put(noisePropertiesPropertyKey,
event.load(new BiomeNoiseConfigTemplate()).get());
}
@@ -4,10 +4,12 @@ import com.dfsek.tectonic.api.config.template.ConfigTemplate;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.properties.Properties;
public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate, Properties {
@Value("blend.terrain.elevation")
@Default
private @Meta int elevationBlend = 4;
@@ -20,6 +22,10 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
@Default
private @Meta int verticalRes = 2;
@Value("slant.calculation-method")
@Default
private SlantHolder.@Meta CalculationMethod slantCalculationMethod = SlantHolder.CalculationMethod.Derivative;
public int getElevationBlend() {
return elevationBlend;
}
@@ -31,4 +37,8 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
public int getVerticalRes() {
return verticalRes;
}
public SlantHolder.CalculationMethod getSlantCalculationMethod() {
return slantCalculationMethod;
}
}
@@ -15,25 +15,23 @@ import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder;
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
public class BiomePaletteTemplate implements ObjectTemplate<BiomePaletteInfo> {
private final Platform platform;
@Value("slant")
@Default
@Description("The slant palettes to use in this biome.")
private @Meta List<@Meta SlantLayer> slant = Collections.emptyList();
private @Meta List<SlantHolder.@Meta Layer> slantLayers = Collections.emptyList();
@Value("slant-depth")
@Default
@@ -63,27 +61,16 @@ public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
@Default
private @Meta boolean updatePalette = false;
public BiomePaletteTemplate(Platform platform) { this.platform = platform; }
private final SlantHolder.CalculationMethod slantCalculationMethod;
public BiomePaletteTemplate(Platform platform, SlantHolder.CalculationMethod slantCalculationMethod) {
this.platform = platform;
this.slantCalculationMethod = slantCalculationMethod;
}
@Override
public PaletteInfo get() {
PaletteHolderBuilder builder = new PaletteHolderBuilder();
for(Map<Palette, Integer> layer : palettes) {
for(Entry<Palette, Integer> entry : layer.entrySet()) {
builder.add(entry.getValue(), entry.getKey());
}
}
TreeMap<Double, PaletteHolder> slantLayers = new TreeMap<>();
double minThreshold = Double.MAX_VALUE;
for(SlantLayer layer : slant) {
double threshold = layer.getThreshold();
if(threshold < minThreshold) minThreshold = threshold;
slantLayers.put(threshold, layer.getPalette());
}
return new PaletteInfo(builder.build(), SlantHolder.of(slantLayers, minThreshold), oceanPalette, seaLevel, slantDepth,
updatePalette);
public BiomePaletteInfo get() {
return new BiomePaletteInfo(PaletteHolder.of(palettes), SlantHolder.of(slantLayers, slantDepth, slantCalculationMethod),
oceanPalette, seaLevel, updatePalette);
}
}
@@ -1,22 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.chunkgenerator.config.palette;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
import com.dfsek.terra.api.properties.Properties;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public record PaletteInfo(PaletteHolder paletteHolder,
SlantHolder slantHolder,
Palette ocean,
int seaLevel,
int maxSlantDepth,
boolean updatePaletteWhenCarving) implements Properties {
}
@@ -1,42 +0,0 @@
package com.dfsek.terra.addons.chunkgenerator.config.palette;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public class SlantLayer implements ObjectTemplate<SlantLayer> {
@Value("threshold")
private @Meta double threshold;
@Value("palette")
private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes;
@Override
public SlantLayer get() {
return this;
}
public double getThreshold() {
return threshold;
}
public PaletteHolder getPalette() {
PaletteHolderBuilder builder = new PaletteHolderBuilder();
for(Map<Palette, Integer> layer : palettes) {
for(Entry<Palette, Integer> entry : layer.entrySet()) {
builder.add(entry.getValue(), entry.getKey());
}
}
return builder.build();
}
}
@@ -0,0 +1,27 @@
package com.dfsek.terra.addons.chunkgenerator.config.palette.slant;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.List;
import java.util.Map;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public class SlantLayerTemplate implements ObjectTemplate<SlantHolder.Layer> {
@Value("threshold")
private @Meta double threshold;
@Value("palette")
private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes;
@Override
public SlantHolder.Layer get() {
return new SlantHolder.Layer(PaletteHolder.of(palettes), threshold);
}
}
@@ -12,7 +12,7 @@ import net.jafama.FastMath;
import org.jetbrains.annotations.NotNull;
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.generation.math.PaletteUtil;
import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.LazilyEvaluatedInterpolator;
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
@@ -40,13 +40,13 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
private final int carverHorizontalResolution;
private final int carverVerticalResolution;
private final PropertyKey<PaletteInfo> paletteInfoPropertyKey;
private final PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey;
private final PropertyKey<BiomeNoiseProperties> noisePropertiesKey;
public NoiseChunkGenerator3D(ConfigPack pack, Platform platform, int elevationBlend, int carverHorizontalResolution,
int carverVerticalResolution,
PropertyKey<BiomeNoiseProperties> noisePropertiesKey,
PropertyKey<PaletteInfo> paletteInfoPropertyKey) {
PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey) {
this.platform = platform;
this.air = platform.getWorldHandle().air();
this.carverHorizontalResolution = carverHorizontalResolution;
@@ -97,7 +97,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
for(int y = world.getMaxHeight() - 1; y >= world.getMinHeight(); y--) {
Biome biome = biomeColumn.get(y);
PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
int sea = paletteInfo.seaLevel();
Palette seaPalette = paletteInfo.ocean();
@@ -131,7 +131,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
Biome biome = biomeProvider.getBiome(x, y, z, world.getSeed());
Sampler3D sampler = samplerCache.get(x, z, world, biomeProvider);
PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
int fdX = FastMath.floorMod(x, 16);
int fdZ = FastMath.floorMod(z, 16);
@@ -7,40 +7,23 @@
package com.dfsek.terra.addons.chunkgenerator.generation.math;
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public final class PaletteUtil {
/**
* Derivative constant.
*/
private static final double DERIVATIVE_DIST = 0.55;
public static Palette getPalette(int x, int y, int z, Sampler3D sampler, PaletteInfo paletteInfo, int depth) {
SlantHolder slant = paletteInfo.slantHolder();
if(!slant.isEmpty() && depth <= paletteInfo.maxSlantDepth()) {
double slope = derivative(sampler, x, y, z);
if(slope > slant.getMinSlope()) {
return slant.getPalette(slope).getPalette(y);
public static Palette getPalette(int x, int y, int z, Sampler3D sampler, BiomePaletteInfo paletteInfo, int depth) {
SlantHolder slantHolder = paletteInfo.slantHolder();
if(slantHolder.isAboveDepth(depth)) {
double slant = slantHolder.calculateSlant(sampler, x, y, z);
if(slantHolder.isInSlantThreshold(slant)) {
return slantHolder.getPalette(slant).getPalette(y);
}
}
return paletteInfo.paletteHolder().getPalette(y);
}
public static double derivative(Sampler3D sampler, double x, double y, double z) {
double baseSample = sampler.sample(x, y, z);
double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1)));
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.chunkgenerator.palette;
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
import com.dfsek.terra.api.properties.Properties;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public record BiomePaletteInfo(PaletteHolder paletteHolder,
SlantHolder slantHolder,
Palette ocean,
int seaLevel,
boolean updatePaletteWhenCarving) implements Properties {
}
@@ -7,6 +7,13 @@
package com.dfsek.terra.addons.chunkgenerator.palette;
import net.jafama.FastMath;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
@@ -27,4 +34,43 @@ public class PaletteHolder {
: palettes[palettes.length - 1]
: palettes[0];
}
public static PaletteHolder of(List<Map<Palette, Integer>> palettes) {
PaletteHolderBuilder builder = new PaletteHolderBuilder();
for(Map<Palette, Integer> layer : palettes) {
for(Entry<Palette, Integer> entry : layer.entrySet()) {
builder.add(entry.getValue(), entry.getKey());
}
}
return builder.build();
}
private static class PaletteHolderBuilder {
private final TreeMap<Integer, Palette> paletteMap = new TreeMap<>();
public PaletteHolderBuilder add(int y, Palette palette) {
paletteMap.put(y, palette);
return this;
}
public PaletteHolder build() {
int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0);
int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255);
Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min];
for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) {
Palette d = null;
for(Entry<Integer, Palette> e : paletteMap.entrySet()) {
if(e.getKey() >= y) {
d = e.getValue();
break;
}
}
if(d == null) throw new IllegalArgumentException("No palette for Y=" + y);
palettes[y - min] = d;
}
return new PaletteHolder(palettes, -min);
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.chunkgenerator.palette;
import net.jafama.FastMath;
import java.util.Map;
import java.util.TreeMap;
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
public class PaletteHolderBuilder {
private final TreeMap<Integer, Palette> paletteMap = new TreeMap<>();
public PaletteHolderBuilder add(int y, Palette palette) {
paletteMap.put(y, palette);
return this;
}
public PaletteHolder build() {
int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0);
int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255);
Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min];
for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) {
Palette d = null;
for(Map.Entry<Integer, Palette> e : paletteMap.entrySet()) {
if(e.getKey() >= y) {
d = e.getValue();
break;
}
}
if(d == null) throw new IllegalArgumentException("No palette for Y=" + y);
palettes[y - min] = d;
}
return new PaletteHolder(palettes, -min);
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.chunkgenerator.palette;
import java.util.Map.Entry;
import java.util.TreeMap;
public class SlantHolder {
private final TreeMap<Double, PaletteHolder> layers;
private final double minSlope;
private SlantHolder(TreeMap<Double, PaletteHolder> layers, double minSlope) {
this.layers = layers;
this.minSlope = minSlope;
}
public static SlantHolder of(TreeMap<Double, PaletteHolder> layers, double minSlope) {
if(layers.size() == 1) {
Entry<Double, PaletteHolder> firstEntry = layers.firstEntry();
return new Single(firstEntry.getValue(), minSlope);
}
return new SlantHolder(layers, minSlope);
}
public boolean isEmpty() {
return layers.isEmpty();
}
public PaletteHolder getPalette(double slope) {
return layers.floorEntry(slope).getValue();
}
public double getMinSlope() {
return minSlope;
}
private static final class Single extends SlantHolder {
private final PaletteHolder layers;
public Single(PaletteHolder layers, double minSlope) {
super(of(minSlope, layers), minSlope);
this.layers = layers;
}
private static TreeMap<Double, PaletteHolder> of(double v, PaletteHolder layer) {
TreeMap<Double, PaletteHolder> map = new TreeMap<>();
map.put(v, layer);
return map;
}
@Override
public PaletteHolder getPalette(double slope) {
return layers;
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2020-2021 Polyhedral Development
*
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in this module's root directory.
*/
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
public class MultipleSlantHolder extends SlantHolderImpl {
private final NavigableMap<Double, PaletteHolder> layers;
private final double slantThreshold;
MultipleSlantHolder(List<SlantHolder.Layer> slant, int slantDepth, CalculationMethod calculationMethod) {
super(slantDepth, calculationMethod);
NavigableMap<Double, PaletteHolder> layers = new TreeMap<>(slant.stream().collect(Collectors.toMap(SlantHolder.Layer::threshold, SlantHolder.Layer::palette)));
Stream<Double> thresholds = layers.keySet().stream();
double slantThreshold = floorToThreshold ?
thresholds.min(Double::compare).orElseThrow() :
thresholds.max(Double::compare).orElseThrow();
this.layers = layers;
this.slantThreshold = slantThreshold;
}
@Override
protected double getSlantThreshold() {
return slantThreshold;
}
@Override
public PaletteHolder getPalette(double slant) {
return (floorToThreshold ?
layers.floorEntry(slant) :
layers.ceilingEntry(slant)
).getValue();
}
}
@@ -0,0 +1,24 @@
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
final class SingleSlantHolder extends SlantHolderImpl {
private final SlantHolder.Layer layer;
public SingleSlantHolder(SlantHolder.Layer layer, int slantDepth, CalculationMethod calculationMethod) {
super(slantDepth, calculationMethod);
this.layer = layer;
}
@Override
public PaletteHolder getPalette(double slant) {
return layer.palette();
}
@Override
protected double getSlantThreshold() {
return layer.threshold();
}
}
@@ -0,0 +1,116 @@
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
import java.util.List;
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
import com.dfsek.terra.api.util.vector.Vector3;
public interface SlantHolder {
static SlantHolder of(List<SlantHolder.Layer> layers, int slantDepth, CalculationMethod calculationMethod) {
if(layers.isEmpty()) {
return EMPTY;
} else if(layers.size() == 1) {
return new SingleSlantHolder(layers.get(0), slantDepth, calculationMethod);
}
return new MultipleSlantHolder(layers, slantDepth, calculationMethod);
}
double calculateSlant(Sampler3D sampler, double x, double y, double z);
boolean isAboveDepth(int depth);
boolean isInSlantThreshold(double slant);
PaletteHolder getPalette(double slant);
record Layer(PaletteHolder palette, double threshold) {
}
enum CalculationMethod {
DotProduct {
private static final Vector3 DOT_PRODUCT_DIRECTION = Vector3.of(0, 1, 0);
private static final Vector3[] DOT_PRODUCT_SAMPLE_POINTS = {
Vector3.of(0, 0, -DERIVATIVE_DIST),
Vector3.of(0, 0, DERIVATIVE_DIST),
Vector3.of(0, -DERIVATIVE_DIST, 0),
Vector3.of(0, DERIVATIVE_DIST, 0),
Vector3.of(-DERIVATIVE_DIST, 0, 0),
Vector3.of(DERIVATIVE_DIST, 0, 0)
};
@Override
public double slant(Sampler3D sampler, double x, double y, double z) {
Vector3.Mutable normalApproximation = Vector3.Mutable.of(0, 0, 0);
for(Vector3 point : DOT_PRODUCT_SAMPLE_POINTS) {
var scalar = -sampler.sample(x+point.getX(), y+point.getY(), z+point.getZ());
normalApproximation.add(point.mutable().multiply(scalar));
}
return DOT_PRODUCT_DIRECTION.dot(normalApproximation.normalize());
}
@Override
public boolean floorToThreshold() {
return false;
}
},
Derivative {
@Override
public double slant(Sampler3D sampler, double x, double y, double z) {
double baseSample = sampler.sample(x, y, z);
double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1)));
}
@Override
public boolean floorToThreshold() {
return true;
}
};
private static final double DERIVATIVE_DIST = 0.55;
public abstract double slant(Sampler3D sampler, double x, double y, double z);
/*
* Controls whether palettes should be applied before or after their respective thresholds.
*
* If true, slant values will map to the palette of the next floor threshold, otherwise they
* will map to the ceiling.
*/
public abstract boolean floorToThreshold();
}
SlantHolder EMPTY = new SlantHolder() {
@Override
public double calculateSlant(Sampler3D sampler, double x, double y, double z) {
throw new UnsupportedOperationException("Empty holder should not calculate slant");
}
@Override
public boolean isAboveDepth(int depth) {
return false;
}
@Override
public boolean isInSlantThreshold(double slant) {
return false;
}
@Override
public PaletteHolder getPalette(double slant) {
throw new UnsupportedOperationException("Empty holder cannot return a palette");
}
};
}
@@ -0,0 +1,38 @@
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
public abstract class SlantHolderImpl implements SlantHolder {
private final SlantHolder.CalculationMethod calculationMethod;
private final int slantDepth;
protected final boolean floorToThreshold;
protected SlantHolderImpl(int slantDepth, CalculationMethod calculationMethod) {
this.floorToThreshold = calculationMethod.floorToThreshold();
this.calculationMethod = calculationMethod;
this.slantDepth = slantDepth;
}
protected abstract double getSlantThreshold();
@Override
public final double calculateSlant(Sampler3D sampler, double x, double y, double z) {
return calculationMethod.slant(sampler, x, y, z);
}
@Override
public final boolean isAboveDepth(int depth) {
return depth <= slantDepth;
}
@Override
public final boolean isInSlantThreshold(double slant) {
return (floorToThreshold ?
slant > getSlantThreshold() :
slant < getSlantThreshold()
);
}
}
@@ -92,9 +92,9 @@ public class NoiseAddon implements AddonInitializer {
noiseRegistry.register(addon.key("PROBABILITY"), ProbabilityNormalizerTemplate::new);
noiseRegistry.register(addon.key("SCALE"), ScaleNormalizerTemplate::new);
noiseRegistry.register(addon.key("POSTERIZATION"), PosterizationNormalizerTemplate::new);
noiseRegistry.register(addon.key("IMAGE"), ImageSamplerTemplate::new);
noiseRegistry.register(addon.key("DOMAIN_WARP"), DomainWarpTemplate::new);
noiseRegistry.register(addon.key("FBM"), BrownianMotionTemplate::new);
@@ -8,6 +8,8 @@
package com.dfsek.terra.addons.noise.config.templates;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.image.BufferedImage;
@@ -19,6 +21,10 @@ import com.dfsek.terra.api.noise.NoiseSampler;
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
public class ImageSamplerTemplate extends SamplerTemplate<ImageSampler> {
private static final Logger logger = LoggerFactory.getLogger(ImageSamplerTemplate.class);
private static boolean used = false;
@Value("image")
private @Meta BufferedImage image;
@@ -30,6 +36,12 @@ public class ImageSamplerTemplate extends SamplerTemplate<ImageSampler> {
@Override
public NoiseSampler get() {
if(!used) {
logger.warn("The IMAGE NoiseSampler implemented by the config-noise-function addon is deprecated. " +
"It is recommended to use the IMAGE NoiseSampler implemented by the config-noise-image " +
"addon instead.");
used = true;
}
return new ImageSampler(image, channel, frequency);
}
}
@@ -0,0 +1,8 @@
version = version("1.0.0")
dependencies {
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
}
@@ -0,0 +1,84 @@
package com.dfsek.terra.addons.image;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.util.function.Supplier;
import com.dfsek.terra.addons.image.config.ColorLoader;
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
import com.dfsek.terra.addons.image.config.noisesampler.ChannelNoiseSamplerTemplate;
import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate;
import com.dfsek.terra.addons.image.config.image.ImageTemplate;
import com.dfsek.terra.addons.image.config.image.StitchedImageTemplate;
import com.dfsek.terra.addons.image.config.colorsampler.ConstantColorSamplerTemplate;
import com.dfsek.terra.addons.image.config.colorsampler.image.SingleImageColorSamplerTemplate;
import com.dfsek.terra.addons.image.config.colorsampler.image.TileImageColorSamplerTemplate;
import com.dfsek.terra.addons.image.config.colorsampler.mutate.RotateColorSamplerTemplate;
import com.dfsek.terra.addons.image.config.colorsampler.mutate.TranslateColorSamplerTemplate;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.operator.DistanceTransform;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.util.reflection.TypeKey;
public class ImageLibraryAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<Image>>> IMAGE_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<ColorSampler>>> COLOR_PICKER_REGISTRY_KEY = new TypeKey<>() {
};
public static final TypeKey<Supplier<ObjectTemplate<NoiseSampler>>> NOISE_SAMPLER_TOKEN = new TypeKey<>() {
};
@Inject
private Platform platform;
@Inject
private BaseAddon addon;
@Override
public void initialize() {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.priority(10)
.then(event -> {
ConfigPack pack = event.getPack();
CheckedRegistry<Supplier<ObjectTemplate<Image>>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY);
imageRegistry.register(addon.key("BITMAP"), () -> new ImageTemplate(pack.getLoader(), pack));
imageRegistry.register(addon.key("STITCHED_BITMAP"), () -> new StitchedImageTemplate(pack.getLoader(), pack));
})
.then(event -> {
event.getPack()
.applyLoader(DistanceTransform.CostFunction.class,
(type, o, loader, depthTracker) -> DistanceTransform.CostFunction.valueOf((String) o))
.applyLoader(DistanceTransform.Normalization.class,
(type, o, loader, depthTracker) -> DistanceTransform.Normalization.valueOf((String) o))
.applyLoader(ColorString.class, new ColorLoader());
CheckedRegistry<Supplier<ObjectTemplate<NoiseSampler>>> noiseRegistry = event.getPack().getOrCreateRegistry(
NOISE_SAMPLER_TOKEN);
noiseRegistry.register(addon.key("DISTANCE_TRANSFORM"), DistanceTransformNoiseSamplerTemplate::new);
noiseRegistry.register(addon.key("CHANNEL"), ChannelNoiseSamplerTemplate::new);
})
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<ColorSampler>>> colorSamplerRegistry = event.getPack().getOrCreateRegistry(
COLOR_PICKER_REGISTRY_KEY);
colorSamplerRegistry.register(addon.key("SINGLE_IMAGE"), SingleImageColorSamplerTemplate::new);
colorSamplerRegistry.register(addon.key("TILED_IMAGE"), TileImageColorSamplerTemplate::new);
colorSamplerRegistry.register(addon.key("COLOR"), ConstantColorSamplerTemplate::new);
colorSamplerRegistry.register(addon.key("ROTATE"), RotateColorSamplerTemplate::new);
colorSamplerRegistry.register(addon.key("TRANSLATE"), TranslateColorSamplerTemplate::new);
});
}
}
@@ -0,0 +1,12 @@
package com.dfsek.terra.addons.image.colorsampler;
@FunctionalInterface
public interface ColorSampler {
/**
* @param x World x coordinate
* @param z World z coordinate
* @return Integer representing a web color
*/
int apply(int x, int z);
}
@@ -0,0 +1,29 @@
package com.dfsek.terra.addons.image.colorsampler.image;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation;
public class SingleImageColorSampler implements ColorSampler {
private final Image image;
private final ColorSampler fallback;
private final ImageTransformation transformation;
public SingleImageColorSampler(Image image, ColorSampler fallback, ImageTransformation transformation) {
this.image = image;
this.fallback = fallback;
this.transformation = transformation;
}
@Override
public int apply(int x, int z) {
var nx = transformation.transformX(image, x);
var nz = transformation.transformZ(image, z);
if(nx < 0 || nz < 0 || nx >= image.getWidth() || nz >= image.getHeight()) return fallback.apply(x, z);
return image.getRGB(nx, nz);
}
}
@@ -0,0 +1,27 @@
package com.dfsek.terra.addons.image.colorsampler.image;
import net.jafama.FastMath;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation;
public class TileImageColorSampler implements ColorSampler {
private final Image image;
private final ImageTransformation transformation;
public TileImageColorSampler(Image image, ImageTransformation transformation) {
this.image = image;
this.transformation = transformation;
}
@Override
public int apply(int x, int z) {
x = transformation.transformX(image, x);
z = transformation.transformZ(image, z);
return image.getRGB(FastMath.floorMod(x, image.getWidth()), FastMath.floorMod(z, image.getHeight()));
}
}
@@ -0,0 +1,30 @@
package com.dfsek.terra.addons.image.colorsampler.image.transform;
import com.dfsek.terra.addons.image.image.Image;
public enum Alignment implements ImageTransformation {
NONE() {
@Override
public int transformX(Image image, int x) {
return x;
}
@Override
public int transformZ(Image image, int z) {
return z;
}
},
CENTER {
@Override
public int transformX(Image image, int x) {
return x + image.getWidth() / 2;
}
@Override
public int transformZ(Image image, int z) {
return z + image.getHeight() / 2;
}
};
}
@@ -0,0 +1,11 @@
package com.dfsek.terra.addons.image.colorsampler.image.transform;
import com.dfsek.terra.addons.image.image.Image;
public interface ImageTransformation {
int transformX(Image image, int x);
int transformZ(Image image, int z);
}
@@ -0,0 +1,61 @@
package com.dfsek.terra.addons.image.colorsampler.mutate;
import net.jafama.FastMath;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
public class RotateColorSampler implements ColorSampler {
private final ColorSampler sampler;
private final double radians;
private final RotationMethod rotationMethod;
public RotateColorSampler(ColorSampler sampler, double degrees) {
this.sampler = sampler;
double normalizedDegrees = degrees % 360.0;
if (normalizedDegrees < 0) normalizedDegrees += 360.0;
if (normalizedDegrees == 0.0)
rotationMethod = RotationMethod.DEG_0;
else if (normalizedDegrees == 90.0)
rotationMethod = RotationMethod.DEG_90;
else if (normalizedDegrees == 180.0)
rotationMethod = RotationMethod.DEG_180;
else if (normalizedDegrees == 270.0)
rotationMethod = RotationMethod.DEG_270;
else
rotationMethod = RotationMethod.RAD_ANY;
this.radians = FastMath.toRadians(degrees);
}
@Override
public int apply(int x, int z) {
int rx = switch(rotationMethod) {
case DEG_0 -> x;
case DEG_90 -> -z;
case DEG_180 -> -x;
case DEG_270 -> z;
case RAD_ANY -> (int) (x * FastMath.cos(radians) - z * FastMath.sin(radians));
};
int rz = switch(rotationMethod) {
case DEG_0 -> z;
case DEG_90 -> x;
case DEG_180 -> -z;
case DEG_270 -> -x;
case RAD_ANY -> (int) (z * FastMath.cos(radians) + x * FastMath.sin(radians));
};
return sampler.apply(rx, rz);
}
private enum RotationMethod {
DEG_0,
DEG_90,
DEG_180,
DEG_270,
RAD_ANY,
}
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.image.colorsampler.mutate;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
public class TranslateColorSampler implements ColorSampler {
private final ColorSampler sampler;
private final int translateX, translateZ;
public TranslateColorSampler(ColorSampler sampler, int translateX, int translateZ) {
this.sampler = sampler;
this.translateX = translateX;
this.translateZ = translateZ;
}
@Override
public int apply(int x, int z) {
return sampler.apply(x - translateX, z - translateZ);
}
}
@@ -0,0 +1,92 @@
package com.dfsek.terra.addons.image.config;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
import com.dfsek.terra.addons.image.util.ColorUtil;
public class ColorLoader implements TypeLoader<ColorString> {
@Override
public ColorString load(@NotNull AnnotatedType annotatedType, @NotNull Object o, @NotNull ConfigLoader configLoader,
DepthTracker depthTracker) throws LoadException {
return new ColorString((String) o);
}
public static class ColorString {
private final int argb;
public ColorString(String string) throws IllegalArgumentException {
this.argb = parse(string);
}
public int getColor() {
return argb;
}
private static int parse(String string) throws IllegalArgumentException {
if (string.length() == 0)
throw new IllegalArgumentException("Empty string cannot be parsed as a valid color");
String[] split = string.split(",");
if (split.length == 1)
return parseHex(string);
else if (split.length == 3)
return parseChannels("255", split[0], split[1], split[2]);
else if (split.length == 4)
return parseChannels(split[0], split[1], split[2], split[3]);
else
throw new IllegalArgumentException("Invalid channels provided, required format RED,GREEN,BLUE or ALPHA,RED,GREEN,BLUE");
}
private static int parseHex(String hex) throws IllegalArgumentException {
if (hex.startsWith("#"))
hex = hex.substring(1);
int alpha = 255;
int red = 0;
int green = 0;
int blue = 0;
try {
if(hex.length() == 8) {
alpha = Integer.parseInt(hex.substring(0, 2), 16);
hex = hex.substring(2);
}
if(hex.length() != 6)
throw new IllegalArgumentException("Invalid color channels, required format AARRGGBB or RRGGBB");
red = Integer.parseInt(hex.substring(0, 2), 16);
green = Integer.parseInt(hex.substring(2, 4), 16);
blue = Integer.parseInt(hex.substring(4, 6), 16);
return ColorUtil.argbValidated(alpha, red, green, blue);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Failed to parse hex color", e);
}
}
private static int parseChannels(String alpha, String red, String green, String blue) throws IllegalArgumentException {
try {
int a = Integer.decode(alpha);
int r = Integer.decode(red);
int g = Integer.decode(green);
int b = Integer.decode(blue);
return ColorUtil.argbValidated(a, r, g, b);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid channel value", e);
}
}
}
}
@@ -0,0 +1,19 @@
package com.dfsek.terra.addons.image.config.colorsampler;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
public class ConstantColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
@Value("color")
private ColorString color;
@Override
public ColorSampler get() {
return ((x, z) -> color.getColor());
}
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.image.config.colorsampler.image;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment;
public abstract class ImageColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
@Value("image")
protected Image image;
@Value("align")
@Default
protected Alignment alignment = Alignment.NONE;
}
@@ -0,0 +1,17 @@
package com.dfsek.terra.addons.image.config.colorsampler.image;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.image.SingleImageColorSampler;
public class SingleImageColorSamplerTemplate extends ImageColorSamplerTemplate {
@Value("outside-sampler")
private ColorSampler fallback;
@Override
public ColorSampler get() {
return new SingleImageColorSampler(image, fallback, alignment);
}
}
@@ -0,0 +1,13 @@
package com.dfsek.terra.addons.image.config.colorsampler.image;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.image.TileImageColorSampler;
public class TileImageColorSamplerTemplate extends ImageColorSamplerTemplate {
@Override
public ColorSampler get() {
return new TileImageColorSampler(image, alignment);
}
}
@@ -0,0 +1,13 @@
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
public abstract class MutateColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
@Value("color-sampler")
protected ColorSampler sampler;
}
@@ -0,0 +1,18 @@
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.mutate.RotateColorSampler;
public class RotateColorSamplerTemplate extends MutateColorSamplerTemplate {
@Value("angle")
private double degrees;
@Override
public ColorSampler get() {
return new RotateColorSampler(sampler, degrees);
}
}
@@ -0,0 +1,21 @@
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.colorsampler.mutate.TranslateColorSampler;
public class TranslateColorSamplerTemplate extends MutateColorSamplerTemplate {
@Value("x")
private int translateX;
@Value("z")
private int translateZ;
@Override
public ColorSampler get() {
return new TranslateColorSampler(sampler, translateX, translateZ);
}
}
@@ -0,0 +1,16 @@
package com.dfsek.terra.addons.image.config.converter;
import com.dfsek.terra.addons.image.converter.ClosestMatchColorConverter;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
public abstract class ClosestColorConverterTemplate<T> implements ColorConverterTemplate<T> {
protected abstract ColorMapping<T> getMapping();
@Override
public ColorConverter<T> get() {
return new ClosestMatchColorConverter<T>(getMapping().get());
}
}
@@ -0,0 +1,9 @@
package com.dfsek.terra.addons.image.config.converter;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.converter.ColorConverter;
public interface ColorConverterTemplate<T> extends ObjectTemplate<ColorConverter<T>> {
}
@@ -0,0 +1,20 @@
package com.dfsek.terra.addons.image.config.converter;
import com.dfsek.terra.addons.image.converter.ColorConverter;
import com.dfsek.terra.addons.image.converter.ExactColorConverter;
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
public abstract class ExactColorConverterTemplate<T> implements ColorConverterTemplate<T> {
protected abstract ColorMapping<T> getMapping();
protected abstract T getFallback();
protected abstract boolean ignoreAlpha();
@Override
public ColorConverter<T> get() {
return new ExactColorConverter<T>(getMapping().get(), getFallback(), ignoreAlpha());
}
}
@@ -0,0 +1,46 @@
package com.dfsek.terra.addons.image.config.image;
import javax.imageio.ImageIO;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import com.dfsek.terra.addons.image.image.BufferedImageWrapper;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
import com.dfsek.terra.api.properties.Properties;
/*
* Cache prevents configs from loading the same image multiple times into memory
*/
record ImageCache(ConcurrentHashMap<String, Image> map) implements Properties {
public static Image load(String path, ConfigPack pack, Loader files) throws IOException {
ImageCache cache;
if(!pack.getContext().has(ImageCache.class)) {
cache = new ImageCache(new ConcurrentHashMap<>());
pack.getContext().put(cache);
} else {
cache = pack.getContext().get(ImageCache.class);
}
if(cache.map.containsKey(path)) {
return cache.map.get(path);
} else {
try {
BufferedImageWrapper image = new BufferedImageWrapper(ImageIO.read(files.get(path)));
cache.map.put(path, image);
return image;
} catch(IllegalArgumentException e) {
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
} catch(IOException e) {
if(e instanceof FileNotFoundException) {
// Rethrow using nicer message
throw new IOException("Unable to load image: No such file or directory: " + path, e);
}
throw new IOException("Unable to load image", e);
}
}
}
}
@@ -0,0 +1,35 @@
package com.dfsek.terra.addons.image.config.image;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import java.io.IOException;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
public class ImageTemplate implements ObjectTemplate<Image> {
@Value("path")
private String path;
private final Loader files;
private final ConfigPack pack;
public ImageTemplate(Loader files, ConfigPack pack) {
this.files = files;
this.pack = pack;
}
@Override
public Image get() {
try {
return ImageCache.load(path, pack, files);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,76 @@
package com.dfsek.terra.addons.image.config.image;
import com.dfsek.tectonic.api.config.template.ValidatedConfigTemplate;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.tectonic.api.exception.ValidationException;
import java.io.IOException;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.image.StitchedImage;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.config.Loader;
public class StitchedImageTemplate implements ObjectTemplate<Image>, ValidatedConfigTemplate {
@Value("path-format")
private String path;
@Value("rows")
private int rows;
@Value("columns")
private int cols;
@Value("zero-indexed")
@Default
private boolean zeroIndexed = false;
private final Loader files;
private final ConfigPack pack;
public StitchedImageTemplate(Loader files, ConfigPack pack) {
this.files = files;
this.pack = pack;
}
@Override
public Image get() {
Image[][] grid = new Image[rows][cols];
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
try {
grid[i][j] = ImageCache.load(getFormattedPath(i, j), pack, files);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
return new StitchedImage(grid, zeroIndexed);
}
private String getFormattedPath(int row, int column) {
if (!zeroIndexed) {
row++;
column++;
}
return path.replaceFirst("\\{row}", String.valueOf(row)).replaceFirst("\\{column}", String.valueOf(column));
}
@Override
public boolean validate() throws ValidationException {
if(!path.contains("{row}"))
throw new ValidationException("Path format does not contain sequence '{row}'");
if(!path.contains("{column}"))
throw new ValidationException("Path format does not contain sequence '{column}'");
if(rows < 1)
throw new ValidationException("Must have at least one row");
if(cols < 1)
throw new ValidationException("Must have at least one column");
return true;
}
}
@@ -0,0 +1,41 @@
package com.dfsek.terra.addons.image.config.noisesampler;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
import com.dfsek.terra.addons.image.noisesampler.ChannelNoiseSampler;
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
import com.dfsek.terra.api.noise.NoiseSampler;
public class ChannelNoiseSamplerTemplate implements ObjectTemplate<NoiseSampler> {
@Value("color-sampler")
private ColorSampler colorSampler;
@Value("channel")
private Channel channel;
/*
* If the channel should be normalized to range [-1, 1] or not
*/
@Value("normalize")
@Default
private boolean normalize = true;
/*
* Whether to multiply color channels by the alpha channel or not. If users
* are expecting pixel transparency to reduce the output value then this should
* be set to true.
*/
@Value("premultiply")
@Default
private boolean premultiply = false;
@Override
public NoiseSampler get() {
return new ChannelNoiseSampler(colorSampler, channel, normalize, premultiply);
}
}
@@ -0,0 +1,74 @@
package com.dfsek.terra.addons.image.config.noisesampler;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.image.image.Image;
import com.dfsek.terra.addons.image.operator.DistanceTransform;
import com.dfsek.terra.addons.image.operator.DistanceTransform.CostFunction;
import com.dfsek.terra.addons.image.operator.DistanceTransform.Normalization;
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
import com.dfsek.terra.api.noise.NoiseSampler;
public class DistanceTransformNoiseSamplerTemplate implements ObjectTemplate<NoiseSampler> {
@Value("image")
private Image image;
/**
* The threshold value applied to the channel specified in the 'channel' parameter that splits
* the image into a binary image. This parameter is only used for cost functions that utilize
* a binary image.
*/
@Value("threshold")
@Default
private int threshold = 127;
/**
* If set to true, distances calculated will be clamped to stay above the largest
* distance calculated on the edges of the image. This ensures output values do not
* appear to be cut off at the image boundaries. It is recommended to leave padding
* around the image if this is in use, such that larger evaluated distances do not
* get cut out by smaller evaluated distances close to borders. Doing so will yield
* better results.
*/
@Value("clamp-to-max-edge")
@Default
private boolean clampToEdge = false;
/**
* The target channel to run distance calculations on.
*/
@Value("channel")
@Default
private Channel channel = Channel.GRAYSCALE;
/**
* The method of image processing applied to the specified image prior to calculating
* distances.
*/
@Value("cost-function")
@Default
private CostFunction costFunction = CostFunction.Channel;
/**
* Inverts the resulting binary image that may be used as a cost function.
*/
@Value("invert-threshold")
@Default
private boolean invertThreshold = false;
/**
* How the final distance calculation should be redistributed.
*/
@Value("normalization")
@Default
private Normalization normalization = Normalization.None;
@Override
public NoiseSampler get() {
return new DistanceTransform.Noise(new DistanceTransform(image, channel, threshold, clampToEdge, costFunction, invertThreshold), normalization);
}
}
@@ -0,0 +1,40 @@
package com.dfsek.terra.addons.image.converter;
import java.util.Map;
import com.dfsek.terra.addons.image.util.ColorUtil;
public class ClosestMatchColorConverter<T> implements ColorConverter<T> {
private final Map<Integer, T> map;
private final Integer[] colors;
public ClosestMatchColorConverter(Map<Integer, T> map) {
this.map = map;
this.colors = map.keySet().toArray(new Integer[0]);
}
@Override
public T apply(int color) {
int closest = 0;
int smallestDistance = Integer.MAX_VALUE;
for(int compare : colors) {
if(color == compare) {
closest = compare;
break;
}
int distance = ColorUtil.distance(color, compare);
if(distance < smallestDistance) {
smallestDistance = distance;
closest = compare;
}
}
return map.get(closest);
}
@Override
public Iterable<T> getEntries() {
return map.values();
}
}
@@ -0,0 +1,8 @@
package com.dfsek.terra.addons.image.converter;
public interface ColorConverter<T> {
T apply(int color);
Iterable<T> getEntries();
}

Some files were not shown because too many files have changed in this diff Show More