mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Compare commits
837
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee1f66fc6b | ||
|
|
c713757300 | ||
|
|
fdf47d8674 | ||
|
|
ebe6bd40d2 | ||
|
|
e41334ac01 | ||
|
|
526c1f2ca8 | ||
|
|
7220152cb9 | ||
|
|
fae60c542b | ||
|
|
e05be64044 | ||
|
|
d22519a7b3 | ||
|
|
3e5a1bd040 | ||
|
|
998b1d8301 | ||
|
|
ccfc1e91cb | ||
|
|
161c22dc7c | ||
|
|
a85b5dceb2 | ||
|
|
51e0c84d01 | ||
|
|
36e36b063d | ||
|
|
732e0ebeb3 | ||
|
|
bb25d37be5 | ||
|
|
843e4c6eb4 | ||
|
|
1e6c59d46d | ||
|
|
04f8ef7a5c | ||
|
|
38bb499205 | ||
|
|
6301858b25 | ||
|
|
f3edffb6ea | ||
|
|
f94eb72853 | ||
|
|
63f664e909 | ||
|
|
a37f193ccb | ||
|
|
6181cceee5 | ||
|
|
8f62b8aa88 | ||
|
|
a1b62cb73a | ||
|
|
e839dddb29 | ||
|
|
7bc0cb2120 | ||
|
|
e9cd2b4512 | ||
|
|
1d1a7aba35 | ||
|
|
87df3cf7d6 | ||
|
|
5866696152 | ||
|
|
20829d6f0e | ||
|
|
ed007155f1 | ||
|
|
3bd9e46370 | ||
|
|
8312f371fc | ||
|
|
4cd5c1a413 | ||
|
|
7b0cede6f8 | ||
|
|
7ec20ad606 | ||
|
|
a099b2e155 | ||
|
|
fff6d3065e | ||
|
|
97e26548f3 | ||
|
|
c430cf43f5 | ||
|
|
7800a1ccd8 | ||
|
|
8417e54537 | ||
|
|
0f5067c85a | ||
|
|
54f2158db7 | ||
|
|
aa48266663 | ||
|
|
d1f9655090 | ||
|
|
e08cb064fe | ||
|
|
d3ca256213 | ||
|
|
7d9a642b77 | ||
|
|
3c6eed82f3 | ||
|
|
790ace00cf | ||
|
|
b8a99a0166 | ||
|
|
45a01d7a12 | ||
|
|
b4e145a738 | ||
|
|
263b9314c2 | ||
|
|
c5848522ca | ||
|
|
0d04275a39 | ||
|
|
4b35bb5b3a | ||
|
|
308f74002f | ||
|
|
0387cc4d18 | ||
|
|
d8c581c3dc | ||
|
|
6cd38eb809 | ||
|
|
3773c46e71 | ||
|
|
3515b6963d | ||
|
|
d088a9b856 | ||
|
|
c113d01fc0 | ||
|
|
2a43173d1a | ||
|
|
1960a896ed | ||
|
|
da6b3c353d | ||
|
|
4476320954 | ||
|
|
4d20c0efc0 | ||
|
|
34d9859ff1 | ||
|
|
d2cf230cb6 | ||
|
|
1e316634b9 | ||
|
|
9e7843c9d5 | ||
|
|
0b47e52206 | ||
|
|
6d9e15cfb5 | ||
|
|
17507c50b4 | ||
|
|
80116a34f8 | ||
|
|
0cc7ae127b | ||
|
|
6b306767a7 | ||
|
|
cec81d44a0 | ||
|
|
ff024d09a3 | ||
|
|
ccff0d4db7 | ||
|
|
de505bbe3c | ||
|
|
d2a173b6e1 | ||
|
|
fd3e17c2bc | ||
|
|
bfe9c360ed | ||
|
|
424524cce4 | ||
|
|
8fed23e865 | ||
|
|
476e5cd79f | ||
|
|
1978658b83 | ||
|
|
45d7ee31e3 | ||
|
|
05ab05c807 | ||
|
|
29c8931f45 | ||
|
|
367b20268d | ||
|
|
08f504c6d6 | ||
|
|
1950f986e8 | ||
|
|
298f165557 | ||
|
|
ee014866e7 | ||
|
|
126e6f6e44 | ||
|
|
175c9b99f4 | ||
|
|
f722c0c72c | ||
|
|
50fe88ed1d | ||
|
|
da19873333 | ||
|
|
cc77dc5261 | ||
|
|
2a18eb5ecd | ||
|
|
3cfb8846e2 | ||
|
|
875ed64de4 | ||
|
|
c332740d31 | ||
|
|
1564656e02 | ||
|
|
a026fd085d | ||
|
|
6e02bdb4b3 | ||
|
|
b877faf1d7 | ||
|
|
467deab05f | ||
|
|
dbd56824f4 | ||
|
|
b88fafd2d7 | ||
|
|
93a326e142 | ||
|
|
7c8ccece9d | ||
|
|
4a9d204be7 | ||
|
|
770ce88060 | ||
|
|
f4b4f4d10a | ||
|
|
851086d248 | ||
|
|
4acedeaad1 | ||
|
|
64ec25e9c9 | ||
|
|
9aa4be30f8 | ||
|
|
acea78948d | ||
|
|
0b98fd3ec6 | ||
|
|
dd66b5105b | ||
|
|
88d201d7d0 | ||
|
|
afd319aeee | ||
|
|
566614fd72 | ||
|
|
dcde458f09 | ||
|
|
466402216a | ||
|
|
abea3bd4b3 | ||
|
|
0981da3950 | ||
|
|
e919ca8862 | ||
|
|
804d44b2b9 | ||
|
|
f28e56abdf | ||
|
|
cf04e87525 | ||
|
|
7f4b71542b | ||
|
|
160f206c16 | ||
|
|
65bb30cbe7 | ||
|
|
8ddf1e96ed | ||
|
|
4cdce63a27 | ||
|
|
c3fde80ea9 | ||
|
|
6805e4b57a | ||
|
|
5b912b5178 | ||
|
|
c97fc9bfba | ||
|
|
6a23f7bf4f | ||
|
|
d1e43f4554 | ||
|
|
7cfcbde368 | ||
|
|
e4aca3a739 | ||
|
|
42fe8f1aff | ||
|
|
cb3fac7b8f | ||
|
|
bc523faa36 | ||
|
|
f20336ac00 | ||
|
|
9932cfea44 | ||
|
|
e42fb452ef | ||
|
|
f24a23f68d | ||
|
|
06834efd1f | ||
|
|
d1bccfe524 | ||
|
|
622c75eefb | ||
|
|
e48c04fafb | ||
|
|
7aa3911897 | ||
|
|
568334b23a | ||
|
|
94c76ed2ca | ||
|
|
f6a16f5584 | ||
|
|
8781b60ee0 | ||
|
|
3940341834 | ||
|
|
c763fa21b0 | ||
|
|
da6d099fdc | ||
|
|
af4f345dc6 | ||
|
|
698e368da9 | ||
|
|
5968865a17 | ||
|
|
07961b4a5a | ||
|
|
65a0447f04 | ||
|
|
814d18d513 | ||
|
|
fdff9f3f03 | ||
|
|
8d63b0b7ee | ||
|
|
47ab8fd5ae | ||
|
|
7d3b25b68f | ||
|
|
ddbda5917f | ||
|
|
2666bd9170 | ||
|
|
a2233c609e | ||
|
|
4274948b50 | ||
|
|
9ff63e6e9f | ||
|
|
28497f72ae | ||
|
|
132179414d | ||
|
|
63c35fd546 | ||
|
|
6e2d33a267 | ||
|
|
9697aaf14d | ||
|
|
b8a348f56d | ||
|
|
a7274ef833 | ||
|
|
fd7962f451 | ||
|
|
d84eb46214 | ||
|
|
c961f3c5cb | ||
|
|
801c0e3947 | ||
|
|
d913536418 | ||
|
|
0ecf7783f6 | ||
|
|
8dbbde4350 | ||
|
|
49c3220312 | ||
|
|
5b492e718d | ||
|
|
ecdc236ba7 | ||
|
|
621cb56d98 | ||
|
|
abd5f98688 | ||
|
|
e4a521e27e | ||
|
|
588c6355e0 | ||
|
|
1f9dbebe66 | ||
|
|
dc3a18c5f9 | ||
|
|
94d01a9c67 | ||
|
|
979f799e9f | ||
|
|
9d6a5ad056 | ||
|
|
9a08507259 | ||
|
|
dba9f2d336 | ||
|
|
0d3e6a2ce1 | ||
|
|
04fc448cf0 | ||
|
|
7357ba25ac | ||
|
|
57d42f49b6 | ||
|
|
5dc441f966 | ||
|
|
13fa32fbea | ||
|
|
64c23f7253 | ||
|
|
6291efd685 | ||
|
|
82a92ee8c5 | ||
|
|
5344a91630 | ||
|
|
6e9da7a0aa | ||
|
|
b78f970e81 | ||
|
|
ae76563090 | ||
|
|
32fe06b0b3 | ||
|
|
7768bf3f8c | ||
|
|
35a315344e | ||
|
|
be5c477960 | ||
|
|
03e1f2a6ec | ||
|
|
81c837f979 | ||
|
|
5dea1cebb2 | ||
|
|
4795422517 | ||
|
|
5e7e5542b1 | ||
|
|
2792705984 | ||
|
|
19a222e33e | ||
|
|
785f305cd2 | ||
|
|
a74a9ff2ba | ||
|
|
767de40a17 | ||
|
|
ae6600f7a5 | ||
|
|
64c8c8b399 | ||
|
|
bfe0be195c | ||
|
|
9c94f59e24 | ||
|
|
56ec4302d1 | ||
|
|
a4a52d4f63 | ||
|
|
8835bf7537 | ||
|
|
a2ba9c1f4b | ||
|
|
0c8be8e9f9 | ||
|
|
33b996f7da | ||
|
|
6ae9441043 | ||
|
|
29f55881c4 | ||
|
|
c31d269d32 | ||
|
|
cc345f396f | ||
|
|
bac2dbb5ad | ||
|
|
214535c9e7 | ||
|
|
a05ca96bc3 | ||
|
|
c0fa34e564 | ||
|
|
eb1a4f91a4 | ||
|
|
e2498f2bf3 | ||
|
|
4e1a240672 | ||
|
|
eda66d2df4 | ||
|
|
0e77c0266c | ||
|
|
8279128508 | ||
|
|
816fb20331 | ||
|
|
dd6c0c122d | ||
|
|
ad0b2de014 | ||
|
|
0334e50120 | ||
|
|
5524e13d9c | ||
|
|
3f9417521f | ||
|
|
2d66c0cccc | ||
|
|
93bfa12f6f | ||
|
|
394871bcee | ||
|
|
a8f5765c4c | ||
|
|
0657af35cd | ||
|
|
e87fd89b80 | ||
|
|
5d425e3f5d | ||
|
|
51dd497dce | ||
|
|
1c4f710ee5 | ||
|
|
3a602c8ec3 | ||
|
|
28c5525946 | ||
|
|
11dc2f6c1a | ||
|
|
cb10f23a6b | ||
|
|
e958049eba | ||
|
|
b8d123941e | ||
|
|
fa158d1eb2 | ||
|
|
b1b585dae3 | ||
|
|
55434edc35 | ||
|
|
e9331ca4f3 | ||
|
|
bb53dfd0b1 | ||
|
|
1b98beaf98 | ||
|
|
777f76e4f0 | ||
|
|
956213f1bd | ||
|
|
c9d3a1e27f | ||
|
|
2a45192720 | ||
|
|
2f4d62edea | ||
|
|
bf43508ea6 | ||
|
|
4b1af15559 | ||
|
|
8d7c8e6b02 | ||
|
|
3bbd1bced4 | ||
|
|
64fda24b03 | ||
|
|
405cccee34 | ||
|
|
dd632f4794 | ||
|
|
7bf51053cb | ||
|
|
ccc94b6bbe | ||
|
|
9949f2c975 | ||
|
|
d3fd6e7e1c | ||
|
|
c8943bdf07 | ||
|
|
eca4c2d566 | ||
|
|
fd5b299140 | ||
|
|
4ae086ce49 | ||
|
|
92f74d3be4 | ||
|
|
7dc688bd3e | ||
|
|
70047c7d3d | ||
|
|
1fd627c94c | ||
|
|
4a6dc38a26 | ||
|
|
882d342431 | ||
|
|
ac974a2bcd | ||
|
|
21215383fb | ||
|
|
6f6972f7b6 | ||
|
|
178fae5509 | ||
|
|
367c94e36d | ||
|
|
704a88ac8f | ||
|
|
a4f096ec50 | ||
|
|
1053072a2c | ||
|
|
b33bc70c4f | ||
|
|
c36dbe39ff | ||
|
|
51e6523a81 | ||
|
|
4f6f5a54e5 | ||
|
|
e6359e9d57 | ||
|
|
5f7f23e2e7 | ||
|
|
6dad81747f | ||
|
|
659cce3aba | ||
|
|
b499414bfa | ||
|
|
3f89e00503 | ||
|
|
e5ce84bc95 | ||
|
|
9a035fc9d5 | ||
|
|
8475a6c69a | ||
|
|
44bc013845 | ||
|
|
0877efffb4 | ||
|
|
072990fd30 | ||
|
|
07a226cbf4 | ||
|
|
1b29b9bcff | ||
|
|
fdb1cf4688 | ||
|
|
c85c4baf4a | ||
|
|
8a6441c63d | ||
|
|
0d817ffd87 | ||
|
|
94cc6490cb | ||
|
|
68586a41aa | ||
|
|
d6bdf70cf1 | ||
|
|
ea3498284b | ||
|
|
beea32e326 | ||
|
|
279a04a8eb | ||
|
|
e63d485d89 | ||
|
|
49a6e02ee3 | ||
|
|
ca61868478 | ||
|
|
f5fe44b835 | ||
|
|
5dbeb8b604 | ||
|
|
60f1ec3d66 | ||
|
|
e6980583f4 | ||
|
|
66017a34c5 | ||
|
|
a189637cfd | ||
|
|
4bd73c9ae1 | ||
|
|
3b8dfa69b2 | ||
|
|
57bb626689 | ||
|
|
df151f2f33 | ||
|
|
481cbc13ed | ||
|
|
ea66f0b2d1 | ||
|
|
507ede9d05 | ||
|
|
447e043650 | ||
|
|
faa9a0f090 | ||
|
|
11e6cedd9b | ||
|
|
562b4662a1 | ||
|
|
084bdbbd25 | ||
|
|
576fb83f6e | ||
|
|
6915962807 | ||
|
|
4c7fc96aec | ||
|
|
2e6a4800e3 | ||
|
|
1f066e533a | ||
|
|
f7d0fb6957 | ||
|
|
5ec8cb805b | ||
|
|
b7680c8178 | ||
|
|
2b4185859a | ||
|
|
8dffb823a4 | ||
|
|
bd66b6313f | ||
|
|
b6ee30c12d | ||
|
|
91658b2dff | ||
|
|
19f61008b8 | ||
|
|
953a24e35d | ||
|
|
2a08f74d46 | ||
|
|
c1372e3b14 | ||
|
|
7bcb77ae03 | ||
|
|
8a8af5f0d9 | ||
|
|
7c5fb85f28 | ||
|
|
b7304857dd | ||
|
|
fcee2057b8 | ||
|
|
49c99f9fa5 | ||
|
|
3b3094e2f2 | ||
|
|
8d1e059489 | ||
|
|
4600012a2e | ||
|
|
313f351428 | ||
|
|
28fb2bf226 | ||
|
|
e98bb1c714 | ||
|
|
2dbe322dd1 | ||
|
|
2afd3b99f8 | ||
|
|
c1d887afd0 | ||
|
|
a832968513 | ||
|
|
0f7f9db8cf | ||
|
|
0a79df7554 | ||
|
|
b266bf1144 | ||
|
|
faa8038177 | ||
|
|
e88dc500cb | ||
|
|
0eab6d3656 | ||
|
|
e18186e29e | ||
|
|
a1063aadbd | ||
|
|
daa33dcb62 | ||
|
|
a0018408fe | ||
|
|
8e7b5fa7e9 | ||
|
|
f8350550da | ||
|
|
b5a8a4271c | ||
|
|
9835a47fdc | ||
|
|
cf463d354f | ||
|
|
9ac513c02d | ||
|
|
4054a9e9fc | ||
|
|
f3220f820c | ||
|
|
10a06ec47a | ||
|
|
6cff270d12 | ||
|
|
82d02678d9 | ||
|
|
c7c48fe7a8 | ||
|
|
60a1ee3176 | ||
|
|
bbc4e8f6c7 | ||
|
|
c93829b157 | ||
|
|
f50c9f25fe | ||
|
|
4518465581 | ||
|
|
14e5230c9e | ||
|
|
3f73256b01 | ||
|
|
512d5cf856 | ||
|
|
f2c8e2ab18 | ||
|
|
164eb13b65 | ||
|
|
28718ed4ee | ||
|
|
876ffc8e4d | ||
|
|
3e8acdf30d | ||
|
|
a7b22dde01 | ||
|
|
5943ae630a | ||
|
|
50022747fe | ||
|
|
10f32b5c05 | ||
|
|
23bda81bb2 | ||
|
|
e2c852bdd9 | ||
|
|
4aaec2e26b | ||
|
|
f6721e5930 | ||
|
|
702764374a | ||
|
|
85e59adf3d | ||
|
|
2aa71b0c72 | ||
|
|
2149056a27 | ||
|
|
98b45cff64 | ||
|
|
3f5e2f0c7b | ||
|
|
d36f6e7e2e | ||
|
|
d71adf169c | ||
|
|
69132ee3d3 | ||
|
|
7fe3549887 | ||
|
|
92e7dfbeb1 | ||
|
|
3fec1f53e7 | ||
|
|
f0a0c0519d | ||
|
|
2f5141eb46 | ||
|
|
0b939ab69d | ||
|
|
e5310a4bf0 | ||
|
|
3881c85f75 | ||
|
|
b2cd4ad7d6 | ||
|
|
4b72588a80 | ||
|
|
f2ab85d7da | ||
|
|
4f7ac75bef | ||
|
|
76654ac3b6 | ||
|
|
b56cb1e293 | ||
|
|
67f9e4fa3e | ||
|
|
a9e0496cc5 | ||
|
|
02af8ae24e | ||
|
|
d47f36726f | ||
|
|
3a93bcc3b6 | ||
|
|
ea756a2cc1 | ||
|
|
14bbe8bf98 | ||
|
|
1470ef7f60 | ||
|
|
989f8084c5 | ||
|
|
622f370fc2 | ||
|
|
245107d65c | ||
|
|
d5d7051098 | ||
|
|
cf5671a3de | ||
|
|
fc69982ff8 | ||
|
|
a661df2680 | ||
|
|
34379152c3 | ||
|
|
7af93bf769 | ||
|
|
4c11af734c | ||
|
|
7cacb9417c | ||
|
|
da586c1ca2 | ||
|
|
78f292e3a0 | ||
|
|
08fe7a8ca0 | ||
|
|
b3017ca6d4 | ||
|
|
ada0c3374f | ||
|
|
bb1a8aeffb | ||
|
|
27e290b18a | ||
|
|
95cba00465 | ||
|
|
ce5e42e3dc | ||
|
|
884536458a | ||
|
|
110726c5f2 | ||
|
|
cc5107e8e9 | ||
|
|
72cb603f16 | ||
|
|
a057ca1140 | ||
|
|
67dad0d1a5 | ||
|
|
40ca330b73 | ||
|
|
eb7e87b0ee | ||
|
|
311c2f82f3 | ||
|
|
5efca3696e | ||
|
|
42fa57f516 | ||
|
|
b28622695d | ||
|
|
05aa0711e6 | ||
|
|
f7ac67e929 | ||
|
|
0af10f3266 | ||
|
|
185abc7c52 | ||
|
|
cb7b4d7707 | ||
|
|
cdbad640b6 | ||
|
|
74dc4dc215 | ||
|
|
af70571d9c | ||
|
|
f77e1df87c | ||
|
|
ef10feee0c | ||
|
|
d2133cb5e2 | ||
|
|
93cc95b510 | ||
|
|
55b1173b07 | ||
|
|
de02700c81 | ||
|
|
2b36c5d080 | ||
|
|
523e9b3a3d | ||
|
|
9d76381f66 | ||
|
|
6c1b80b2a5 | ||
|
|
d5a61f8f56 | ||
|
|
87f1e12831 | ||
|
|
5d8c7522b7 | ||
|
|
d81d843209 | ||
|
|
b1cf9f9fa7 | ||
|
|
954f2753bb | ||
|
|
e95a5a1066 | ||
|
|
5a40f62a94 | ||
|
|
5d17cd57d3 | ||
|
|
17f9e72104 | ||
|
|
f005a6dcd2 | ||
|
|
fa06bc3d72 | ||
|
|
195a3a17ce | ||
|
|
41cf95bc1e | ||
|
|
b7cc798bcb | ||
|
|
5d49a985b0 | ||
|
|
867066a5bb | ||
|
|
6e6dfa06f2 | ||
|
|
04daff56b8 | ||
|
|
bd143d0b43 | ||
|
|
3157ef700f | ||
|
|
b1070002de | ||
|
|
8e06e16d37 | ||
|
|
d9688f3096 | ||
|
|
2eb87de143 | ||
|
|
ccc3ad7c69 | ||
|
|
2ab429f046 | ||
|
|
8ed846b3e3 | ||
|
|
44a0b9a559 | ||
|
|
e3315e0717 | ||
|
|
bc60d450bf | ||
|
|
73e1d5b828 | ||
|
|
4c5335c529 | ||
|
|
be63126164 | ||
|
|
123323b0e5 | ||
|
|
9efa76d039 | ||
|
|
a32fe1db93 | ||
|
|
6feb425580 | ||
|
|
f068c11d5c | ||
|
|
2d31cdbd6d | ||
|
|
ad261e3ae4 | ||
|
|
49d2a37c52 | ||
|
|
71d661a522 | ||
|
|
ec4a536a87 | ||
|
|
19512add34 | ||
|
|
3f499241d7 | ||
|
|
47c26bc0c7 | ||
|
|
d7e29f589f | ||
|
|
404bb22d32 | ||
|
|
c11630ff47 | ||
|
|
914cf3267d | ||
|
|
e7b8d6a0a1 | ||
|
|
89598c575a | ||
|
|
be7cf6611c | ||
|
|
73ab09ef53 | ||
|
|
34088bafca | ||
|
|
c87d15a28d | ||
|
|
360a7a50b7 | ||
|
|
8236aa329f | ||
|
|
352aef0008 | ||
|
|
d4052f7c5c | ||
|
|
4698f8f423 | ||
|
|
796a87f53d | ||
|
|
3778026966 | ||
|
|
940d1f9df6 | ||
|
|
ce9e113d9a | ||
|
|
d61c1d6b6c | ||
|
|
e7440c5703 | ||
|
|
b4c85ed027 | ||
|
|
59ca5112f1 | ||
|
|
de2a7fa0cf | ||
|
|
453ca8ffdb | ||
|
|
cb4573aeb9 | ||
|
|
2c12bd8e05 | ||
|
|
b9558afaa6 | ||
|
|
a4f89dde97 | ||
|
|
d693267be8 | ||
|
|
b0fa89401d | ||
|
|
1a004befca | ||
|
|
5631f912fb | ||
|
|
e979fbc2ad | ||
|
|
d8e6f2b354 | ||
|
|
a463de44f4 | ||
|
|
3ef168d9af | ||
|
|
c6e3709517 | ||
|
|
ab49fe48b7 | ||
|
|
f960294f84 | ||
|
|
e4504910c0 | ||
|
|
33ac800df5 | ||
|
|
55e2ab1c3d | ||
|
|
ae1168091f | ||
|
|
4e4542f23f | ||
|
|
0250ed09b6 | ||
|
|
ce75f3f3fd | ||
|
|
2f0c80826c | ||
|
|
45899355d6 | ||
|
|
a700c4d819 | ||
|
|
47c09e613f | ||
|
|
94a62a9d93 | ||
|
|
e4aaced974 | ||
|
|
a7539e34d8 | ||
|
|
98f1c5cd5a | ||
|
|
2fa16379af | ||
|
|
a217a255d1 | ||
|
|
36e330d36c | ||
|
|
abc4edbf15 | ||
|
|
83080f6bc6 | ||
|
|
44c09adb15 | ||
|
|
1622c53e2c | ||
|
|
3c9a5524cc | ||
|
|
991c33e49f | ||
|
|
b1cd4d04a7 | ||
|
|
5fe6c42ef8 | ||
|
|
c7fbdc6008 | ||
|
|
e67541686c | ||
|
|
5324b1bf4a | ||
|
|
abb96b6834 | ||
|
|
b59777f00b | ||
|
|
4c0697a727 | ||
|
|
8b08a67b75 | ||
|
|
d1de8739df | ||
|
|
22769e76c9 | ||
|
|
08a168094a | ||
|
|
732265ffad | ||
|
|
80643ef336 | ||
|
|
a668f9fb30 | ||
|
|
60000e7e9c | ||
|
|
19db9ef223 | ||
|
|
6483aa3498 | ||
|
|
79ea3cec34 | ||
|
|
7a6c1518fb | ||
|
|
7402b17949 | ||
|
|
893fc9ae96 | ||
|
|
649d0c580a | ||
|
|
4efd21aef2 | ||
|
|
b6a8a45460 | ||
|
|
167ebfc2af | ||
|
|
01b9a33298 | ||
|
|
87c075391b | ||
|
|
b549f9555a | ||
|
|
9d7d671e0b | ||
|
|
b26ee56a70 | ||
|
|
8c67fd63dd | ||
|
|
3764b7d9d6 | ||
|
|
0932e968bd | ||
|
|
9e9b277609 | ||
|
|
6bd36e517e | ||
|
|
3a19fe597a | ||
|
|
78499843d3 | ||
|
|
4f2d0fce4a | ||
|
|
2a1cfe65bc | ||
|
|
7bac913ec3 | ||
|
|
5639aee01e | ||
|
|
7cd36228aa | ||
|
|
d5c46f91bb | ||
|
|
3e13c1a8cb | ||
|
|
fa310f522d | ||
|
|
ba3cd22c7c | ||
|
|
fa22f0e6ea | ||
|
|
4cbd817683 | ||
|
|
a4f76cc3c2 | ||
|
|
eb58c6bb30 | ||
|
|
33dd3825e1 | ||
|
|
80f0a25624 | ||
|
|
78ef64dbb7 | ||
|
|
f41d7a6f43 | ||
|
|
7aa91682b6 | ||
|
|
6e26955fd5 | ||
|
|
67b1d9fc4a | ||
|
|
359d60f55f | ||
|
|
0829fdd82a | ||
|
|
34e8edaa45 | ||
|
|
13e3636ff2 | ||
|
|
00d32ff93a | ||
|
|
921d5c7a18 | ||
|
|
e2d38b13d5 | ||
|
|
6938bc45ef | ||
|
|
4b3c4f853a | ||
|
|
c63db5f701 | ||
|
|
37d0888489 | ||
|
|
add4366765 | ||
|
|
36eddf9710 | ||
|
|
8c10244e45 | ||
|
|
b59836d46f | ||
|
|
82e8967c10 | ||
|
|
387d2d5855 | ||
|
|
14f4b9d332 | ||
|
|
0fe2117572 | ||
|
|
233e61d62f | ||
|
|
8b758ff22e | ||
|
|
bb991d6f15 | ||
|
|
a9f4573d1b | ||
|
|
4f9fdf8bc7 | ||
|
|
09496d8f2a | ||
|
|
3b7fe12fe2 | ||
|
|
d4365151d0 | ||
|
|
626cf0542d | ||
|
|
3eda0605cf | ||
|
|
23c7618560 | ||
|
|
e00ffd6092 | ||
|
|
7b9b688fa4 | ||
|
|
8247cdbcb7 | ||
|
|
5073caa885 | ||
|
|
4cac42c4ef | ||
|
|
3dbe994f44 | ||
|
|
6d214c14c2 | ||
|
|
0ec7c93ad0 | ||
|
|
84b959af89 | ||
|
|
8d0c2bbd55 | ||
|
|
44c9719f41 | ||
|
|
9c74bbeace | ||
|
|
1a14bdfcde | ||
|
|
bc4fffc20e | ||
|
|
3238d09f66 | ||
|
|
19a7803283 | ||
|
|
4ee1b9859f | ||
|
|
eabb9ba84e | ||
|
|
c557d53446 | ||
|
|
b3259b8b37 | ||
|
|
c91fbfd777 | ||
|
|
9897e6bc6c | ||
|
|
92d02a0e84 | ||
|
|
e9fd2edf5d | ||
|
|
46fb47af41 | ||
|
|
fc16743cf3 | ||
|
|
da8eb9e43b | ||
|
|
11c50fd0d9 | ||
|
|
dd26d27880 | ||
|
|
0d969ae57c | ||
|
|
e644192a1c | ||
|
|
0d29ccecac | ||
|
|
c2bf88085a | ||
|
|
e1197bfb6e | ||
|
|
80fcd18c2e | ||
|
|
813143c35b | ||
|
|
244eb0c347 | ||
|
|
24f8b0cfc5 | ||
|
|
873782c24d | ||
|
|
33cd293811 | ||
|
|
fc93a1d48d | ||
|
|
a45326c556 | ||
|
|
60accdc5d5 | ||
|
|
3e48866617 | ||
|
|
868bc02e8e | ||
|
|
5900585aac | ||
|
|
a696e54055 | ||
|
|
4dc208a416 | ||
|
|
0582226502 | ||
|
|
18ae21bcca | ||
|
|
4ac46cd511 | ||
|
|
85b95500f2 | ||
|
|
bb80d6cce7 | ||
|
|
457da5d383 | ||
|
|
9836b0f0f2 | ||
|
|
9027ef0a54 | ||
|
|
9952a8539f | ||
|
|
ceadedaa0f | ||
|
|
0f983e34d1 | ||
|
|
fa2de56df1 | ||
|
|
c27d6a22a9 | ||
|
|
b66c44849b | ||
|
|
f14b413dde | ||
|
|
e0bded6fa7 | ||
|
|
a9e638a4db | ||
|
|
9cfac94b06 | ||
|
|
45d56f3d8a | ||
|
|
ae1a4ec23c | ||
|
|
740b215a99 | ||
|
|
32c30eb98f | ||
|
|
5d5dd03eb8 | ||
|
|
7fc42e8269 | ||
|
|
c784009d45 | ||
|
|
493a315a44 | ||
|
|
fa76a40d45 | ||
|
|
1a5c943591 | ||
|
|
26af67b082 | ||
|
|
4e4ae63d40 | ||
|
|
77c58d477d | ||
|
|
b11536f39a | ||
|
|
018f9005c3 | ||
|
|
2cce47fc9d | ||
|
|
b41cedea4b | ||
|
|
262ed43366 | ||
|
|
78ca40d8a5 | ||
|
|
73f9ba613c | ||
|
|
81ad72d464 | ||
|
|
a74c13032b | ||
|
|
7441ce96da | ||
|
|
c00651d4d0 | ||
|
|
77cce1d217 | ||
|
|
ddacea4d6f | ||
|
|
1e3317a9e2 | ||
|
|
3a539aa588 | ||
|
|
77b7eae96d | ||
|
|
f7920f30f7 | ||
|
|
bd7849a6cb |
No files matched your search
@@ -9,9 +9,9 @@ permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
cancel:
|
||||
# One of playhorizon's small runners, out of GitHub's shared pool: no checkout, no code of the pull request
|
||||
# One of our small runners (horizon-light), out of GitHub's pool: no checkout, no code of the pull request
|
||||
# (docs/ci-runners.md). SELF_HOSTED_CI=off: GitHub's runners.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Cancel the pull request's unfinished runs
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
name: cargo-horizon-mod
|
||||
# `cargo binstall --git https://github.com/horizon-rust/horizon cargo-horizon-mod` (#2120): binstall reads
|
||||
# [package.metadata.binstall] from crates/cargo-horizon-mod/Cargo.toml and downloads the archive this workflow
|
||||
# attaches to the tag `cargo-horizon-mod-v<version>`, whose version must be the manifest's. The targets come from
|
||||
# tools/horizon_mod_binstall.py, the same file the manifest's template is checked against, so a platform added there
|
||||
# is built here and one nobody publishes is caught before it ships. A pull request builds the archives and publishes
|
||||
# nothing.
|
||||
on:
|
||||
push:
|
||||
tags: ['cargo-horizon-mod-v*']
|
||||
pull_request:
|
||||
paths:
|
||||
- 'crates/cargo-horizon-mod/**'
|
||||
- 'tools/horizon_mod_binstall.py'
|
||||
- '.github/workflows/cargo-horizon-mod.yml'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
plan:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
platforms: ${{ steps.platforms.outputs.platforms }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: platforms
|
||||
run: echo "platforms=$(python3 -c 'import json, tools.horizon_mod_binstall as m; print(json.dumps(m.PLATFORMS))')" >> "$GITHUB_OUTPUT"
|
||||
build:
|
||||
needs: plan
|
||||
name: build ${{ matrix.target }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJSON(needs.plan.outputs.platforms) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- name: The aarch64 Linux linker
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
|
||||
- name: Build
|
||||
run: cargo build --release --locked -p cargo-horizon-mod --target ${{ matrix.target }}
|
||||
- name: It starts
|
||||
if: matrix.run
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
binary="target/${{ matrix.target }}/release/cargo-horizon-mod"
|
||||
[ -f "$binary.exe" ] && binary="$binary.exe"
|
||||
out=$("$binary" 2>&1 || true)
|
||||
case "$out" in
|
||||
*"usage: cargo horizon-mod"*) ;;
|
||||
*) echo "$out"; echo "::error::$binary does not start"; exit 1 ;;
|
||||
esac
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
binary="target/${{ matrix.target }}/release/cargo-horizon-mod"
|
||||
[ -f "$binary.exe" ] && binary="$binary.exe"
|
||||
mkdir -p dist
|
||||
tar -czf "dist/$(basename "$binary" | sed 's/\.exe$//')-${{ matrix.target }}.tar.gz" -C "$(dirname "$binary")" "$(basename "$binary")"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cargo-horizon-mod-${{ matrix.target }}
|
||||
path: dist/*.tar.gz
|
||||
if-no-files-found: error
|
||||
retention-days: 3
|
||||
publish:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: cargo-horizon-mod-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: The tag and the manifest name the same release and archives
|
||||
run: python3 tools/horizon_mod_binstall.py dist "$GITHUB_REF_NAME"
|
||||
- name: Checksums
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd dist
|
||||
for file in *.tar.gz; do sha256sum "$file" | cut -d' ' -f1 > "$file.sha256"; done
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
notes="\`cargo binstall --git https://github.com/horizon-rust/horizon cargo-horizon-mod\`"
|
||||
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
||||
gh release upload "$GITHUB_REF_NAME" dist/* --clobber
|
||||
else
|
||||
gh release create "$GITHUB_REF_NAME" dist/* --latest=false --title "cargo-horizon-mod ${GITHUB_REF_NAME#cargo-horizon-mod-v}" --notes "$notes"
|
||||
fi
|
||||
@@ -27,13 +27,18 @@ jobs:
|
||||
# One job, not one per check: each job takes a runner from the organisation's shared pool, and with every pull request
|
||||
# and every merge to main starting this workflow, three jobs a run left the pool's queue tens of minutes deep.
|
||||
#
|
||||
# Where: GitHub's runners. With the repository variable SELF_HOSTED_CI=all (for when that pool is full: their 4 CPUs
|
||||
# are about one GitHub runner, docs/ci-runners.md), main's pushes and this repository's own pull requests run on
|
||||
# playhorizon's checks runners (label playhorizon-ci); a fork's pull request never does (and the runners refuse it
|
||||
# themselves). There the build folder persists (CARGO_TARGET_DIR, outside the checkout), so the apt packages,
|
||||
# Python and the Rust cache steps are GitHub's only.
|
||||
# Where (docs/ci-runners.md): our checks runners (label horizon-checks: the desktop's, an isolated user, so a fork's
|
||||
# pull request runs there too) take the pull requests whose number ends in 0 or 1, about what their three runners
|
||||
# can do; GitHub's runners take the others and main's pushes (which also save the Rust cache GitHub's pull request
|
||||
# runs read), so both pools work at once. The repository variable SELF_HOSTED_CI moves them: `all` every job to
|
||||
# ours, `light` (the desktop paused) or `off` every job to GitHub's. On ours the build folder persists
|
||||
# (CARGO_TARGET_DIR, outside the checkout), so the apt packages, Python and the Rust cache steps are GitHub's only.
|
||||
verify:
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI == 'all' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && fromJSON('["self-hosted", "linux", "playhorizon-ci"]') || 'ubuntu-latest' }}
|
||||
runs-on: >-
|
||||
${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && (vars.SELF_HOSTED_CI == 'all' ||
|
||||
github.event_name == 'pull_request' && (endsWith(format('{0}', github.event.pull_request.number), '0') ||
|
||||
endsWith(format('{0}', github.event.pull_request.number), '1'))) &&
|
||||
fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -63,6 +68,16 @@ jobs:
|
||||
else
|
||||
python tools/check_public_tree.py --history
|
||||
fi
|
||||
# Our runners as GitHub's: some Python tests compile Rust with a bare `rustc` (GitHub's image has a default
|
||||
# toolchain, our runners' own rustup homes may not), and the GPU tests find no adapter there, so they return
|
||||
# early. Ours have Mesa's software Vulkan for parity; a backend Linux lacks keeps the tests from it, so a pull
|
||||
# request's verify doesn't depend on which pool took it (#8627: a GPU test that flakes on lavapipe).
|
||||
- name: Our runners as GitHub's (a default Rust toolchain, no GPU)
|
||||
if: runner.environment != 'github-hosted'
|
||||
run: |
|
||||
rustup toolchain install stable --profile minimal --component rustfmt --component clippy
|
||||
rustup default stable
|
||||
echo "WGPU_BACKEND=dx12" >> "$GITHUB_ENV"
|
||||
- name: Test offline research tooling
|
||||
run: python -m unittest discover -s tests -v
|
||||
- name: Install Rust toolchain and the ALSA and udev headers the client links against
|
||||
@@ -104,7 +119,11 @@ jobs:
|
||||
# changes it or a crate it builds is checked here, and so is every push to main: two pull requests can each pass
|
||||
# alone and break it together (#2059 and the registry ratings did, 2026-10-26).
|
||||
launcher:
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI == 'all' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && fromJSON('["self-hosted", "linux", "playhorizon-ci"]') || 'ubuntu-latest' }}
|
||||
runs-on: >-
|
||||
${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && (vars.SELF_HOSTED_CI == 'all' ||
|
||||
github.event_name == 'pull_request' && (endsWith(format('{0}', github.event.pull_request.number), '0') ||
|
||||
endsWith(format('{0}', github.event.pull_request.number), '1'))) &&
|
||||
fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -145,8 +164,8 @@ jobs:
|
||||
# a harness, an oracle, a probe or the runner, or carries the run-parity label (run-parity.yml, when it is added).
|
||||
parity-scope:
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
# A git diff of this repository's own branch: one of playhorizon's small runners (docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
# A git diff of this repository's own branch: one of our small runners (horizon-light, docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
outputs:
|
||||
run: ${{ steps.scope.outputs.run }}
|
||||
steps:
|
||||
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
# the skipped runs of ordinary comments must not cancel a waiting one.
|
||||
concurrency:
|
||||
group: claim-${{ github.event.issue.number }}
|
||||
# One of playhorizon's small runners, out of GitHub's shared pool: main's script only (docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
# One of our small runners (horizon-light), out of GitHub's pool: main's script only (docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -56,8 +56,8 @@ jobs:
|
||||
run: python3 tools/ci/claim_command.py command
|
||||
release:
|
||||
if: github.event_name != 'issue_comment' && github.repository == 'horizon-rust/horizon'
|
||||
# One of playhorizon's small runners, out of GitHub's shared pool: main's script only (docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
# One of our small runners (horizon-light), out of GitHub's pool: main's script only (docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -17,7 +17,8 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
# Our checks runners (label horizon-checks, docs/ci-runners.md); SELF_HOSTED_CI=light or off: GitHub's.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
@@ -25,7 +26,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy
|
||||
# Ours keep their build folder between jobs.
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
if: runner.environment == 'github-hosted'
|
||||
with:
|
||||
workspaces: crates/horizon-mod-registry
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
@@ -21,7 +21,8 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
# One of our small runners (label horizon-light, docs/ci-runners.md): main's script, Mojang's public manifest.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -253,7 +253,8 @@ jobs:
|
||||
# Tells the thread that asked for this build how it ended. A conflict `pick` already explained there.
|
||||
needs: [pick, build, sign, publish]
|
||||
if: always() && inputs.issue != '' && needs.pick.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
# One comment, no checkout and no secret: one of our small runners (label horizon-light, docs/ci-runners.md).
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
@@ -19,9 +19,9 @@ jobs:
|
||||
concurrency:
|
||||
group: overlap-note-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
# One of playhorizon's small runners, out of GitHub's shared pool: it runs main's script only (above), so a fork's
|
||||
# One of our small runners (horizon-light), out of GitHub's pool: it runs main's script only (above), so a fork's
|
||||
# pull request runs none of its code there (docs/ci-runners.md). SELF_HOSTED_CI=off: GitHub's runners.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -22,17 +22,24 @@ env:
|
||||
|
||||
jobs:
|
||||
bench:
|
||||
runs-on: ubuntu-latest
|
||||
# Our checks runners (label horizon-checks, docs/ci-runners.md): the branch and the base on the same machine, as
|
||||
# the comparison needs. SELF_HOSTED_CI=light or off: GitHub's.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Rust and the ALSA and udev headers
|
||||
env:
|
||||
HOSTED: ${{ runner.environment == 'github-hosted' }}
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev
|
||||
if [ "$HOSTED" = true ]; then
|
||||
sudo apt-get update && sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev
|
||||
fi
|
||||
rustup toolchain install stable --profile minimal
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
if: runner.environment == 'github-hosted'
|
||||
with:
|
||||
shared-key: pack-bench
|
||||
- name: Bench the branch
|
||||
@@ -46,11 +53,15 @@ jobs:
|
||||
SCALE: ${{ inputs.scale || '1' }}
|
||||
run: |
|
||||
git fetch https://github.com/horizon-rust/horizon.git "$BASE"
|
||||
# Our runners keep the folder beside the checkout between jobs.
|
||||
git worktree remove --force ../base 2> /dev/null || rm -rf ../base
|
||||
git worktree prune
|
||||
git worktree add ../base FETCH_HEAD
|
||||
cp crates/horizon-client-render/examples/pack_load_bench.rs ../base/crates/horizon-client-render/examples/
|
||||
# A bench row that times a function the base lacks keeps it from building there: then
|
||||
# the branch's own rows compare the two ways.
|
||||
(cd ../base && CARGO_TARGET_DIR="$GITHUB_WORKSPACE/target" cargo run --profile fast -p horizon-client-render \
|
||||
target=${CARGO_TARGET_DIR:-$GITHUB_WORKSPACE/target}
|
||||
(cd ../base && CARGO_TARGET_DIR="$target" cargo run --profile fast -p horizon-client-render \
|
||||
--example pack_load_bench -- --scale "$SCALE") > base.md \
|
||||
|| echo "The bench does not build on the base." > base.md
|
||||
- name: Summary
|
||||
|
||||
@@ -16,9 +16,9 @@ jobs:
|
||||
label:
|
||||
# Title-only edits change nothing: skip them before a runner starts.
|
||||
if: github.event.action != 'edited' || github.event.changes.body != null
|
||||
# One of playhorizon's small runners, out of GitHub's shared pool: it runs main's script only (above), so a fork's
|
||||
# One of our small runners (horizon-light), out of GitHub's pool: it runs main's script only (above), so a fork's
|
||||
# pull request runs none of its code there (docs/ci-runners.md). SELF_HOSTED_CI=off: GitHub's runners.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "playhorizon-light"]') || 'ubuntu-latest' }}
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -17,7 +17,8 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
# Our checks runners (label horizon-checks, docs/ci-runners.md); SELF_HOSTED_CI=light or off: GitHub's.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
@@ -25,7 +26,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy
|
||||
# Ours keep their build folder between jobs.
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
if: runner.environment == 'github-hosted'
|
||||
with:
|
||||
workspaces: crates/horizon-telemetry-server
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
@@ -21,7 +21,8 @@ jobs:
|
||||
startsWith(github.event.comment.body, '/reopen') ||
|
||||
startsWith(github.event.comment.body, '/label') ||
|
||||
startsWith(github.event.comment.body, '/unlabel'))
|
||||
runs-on: ubuntu-latest
|
||||
# One of our small runners (label horizon-light, docs/ci-runners.md): main's script only, as above.
|
||||
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
Generated
+14
@@ -1429,6 +1429,7 @@ dependencies = [
|
||||
"horizon-world",
|
||||
"log",
|
||||
"miniz_oxide 0.8.9",
|
||||
"mio",
|
||||
"naga",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
@@ -1445,6 +1446,7 @@ dependencies = [
|
||||
"ureq",
|
||||
"wgpu",
|
||||
"winit",
|
||||
"xxhash-rust",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -2315,6 +2317,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiversion_no_op"
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -14,6 +14,9 @@ Apache-2.0; both licenses are in that folder (`LICENSE.MIT`, `LICENSE.APACHE`).
|
||||
fixes to its GLSL frontend that shader packs need, each applied in the commit that needed it. It is licensed under MIT or Apache-2.0; both licenses
|
||||
are in that folder (`LICENSE.MIT`, `LICENSE.APACHE`).
|
||||
|
||||
The GLSL frontend also resets the common type for each comma-separated declarator so one
|
||||
variable's array specifier does not change later variables.
|
||||
|
||||
## libmimalloc-sys and mimalloc
|
||||
|
||||
`third_party/libmimalloc-sys` is [libmimalloc-sys](https://github.com/purpleprotocol/mimalloc_rust)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Particle culling review measurements — 2026-11-02
|
||||
|
||||
Ordinary-scene captures and measurements for PR #7054. The scene contains 24 mobs, about 9,400–10,100 rain particles, and about 50–100 campfire smoke particles. Two opposite-order passes were run with 2 s warmup and 8 s measurement per condition. Host: Intel Core i7-3770S, Intel HD Graphics 4000, Vulkan/Mesa, 675×727, render distance 4, VSync off, 260 FPS cap.
|
||||
|
||||
These are means of the two samples. CPU is the harness's average CPU time per frame; p99 is frame time. The normal scene is not expected to show large gains because most particles are visible.
|
||||
|
||||
| Condition | FPS | CPU ms/frame | Frame p99 ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Main, culling Off | 88.39 | 1.930 | 23.85 |
|
||||
| Particle branch, culling Off | 88.55 | 1.828 | 24.88 |
|
||||
| Main, Entity Culling Synchronous | 89.12 | 1.887 | 23.07 |
|
||||
| Particle branch, Entity Culling Synchronous, Particle Culling Off | 89.06 | 1.897 | 25.18 |
|
||||
| Particle Culling Synchronous | 88.87 | 1.774 | 24.94 |
|
||||
| Particle Culling Asynchronous* | 89.00 | 1.754 | 24.90 |
|
||||
|
||||
*The asynchronous samples predate the final switch to a dedicated particle worker. They are retained only as historical context and are not a measurement of the final worker implementation. No FPS improvement is claimed from these ordinary-scene runs. The Off path delegates directly to `render`; `disabled_culled_render_matches_the_regular_particle_path` checks that it produces the same vertices.
|
||||
|
||||
The FPS and CPU differences are within run-to-run variability for this GPU-limited scene. The first four conditions are the reviewer's requested Main-versus-branch default and existing synchronous-mode comparisons. Particle Sync/Async rows are branch-internal mode comparisons.
|
||||
|
||||
## Captures
|
||||
|
||||
The screenshots show the same ordinary test world and FPS overlay:
|
||||
|
||||
- `main-off.png`
|
||||
- `particle-off.png`
|
||||
- `particle-sync.png`
|
||||
- `particle-async.png`
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 342 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 343 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 341 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 341 KiB |
@@ -6,6 +6,14 @@ license.workspace = true
|
||||
rust-version = "1.86"
|
||||
description = "cargo horizon-mod new/build/run/check: make Horizon content mods (docs/mods.md)"
|
||||
|
||||
# `cargo binstall --git https://github.com/horizon-rust/horizon cargo-horizon-mod` (#2120) reads this: the archive
|
||||
# `cargo-horizon-mod`'s workflow attaches to the `cargo-horizon-mod-v<version>` release for the runner's target,
|
||||
# holding the binary at its root. tools/horizon_mod_binstall.py expands it for the workflow's targets.
|
||||
[package.metadata.binstall]
|
||||
pkg-url = "https://github.com/horizon-rust/horizon/releases/download/cargo-horizon-mod-v{ version }/{ name }-{ target }.tar.gz"
|
||||
bin-dir = "{ bin }{ binary-ext }"
|
||||
pkg-fmt = "tgz"
|
||||
|
||||
[dependencies]
|
||||
horizon-mods = { path = "../horizon-mods", default-features = false, features = ["index"] }
|
||||
serde_json = "1.0.151"
|
||||
|
||||
@@ -100,6 +100,66 @@ impl ResourceSource for Bundled {
|
||||
"assets/horizon/lang/ur_pk.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/ur_pk.json")
|
||||
}
|
||||
"assets/horizon/lang/fa_ir.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/fa_ir.json")
|
||||
}
|
||||
"assets/horizon/lang/he_il.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/he_il.json")
|
||||
}
|
||||
"assets/horizon/lang/hu_hu.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/hu_hu.json")
|
||||
}
|
||||
"assets/horizon/lang/ro_ro.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/ro_ro.json")
|
||||
}
|
||||
"assets/horizon/lang/da_dk.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/da_dk.json")
|
||||
}
|
||||
"assets/horizon/lang/fi_fi.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/fi_fi.json")
|
||||
}
|
||||
"assets/horizon/lang/no_no.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/no_no.json")
|
||||
}
|
||||
"assets/horizon/lang/bg_bg.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/bg_bg.json")
|
||||
}
|
||||
"assets/horizon/lang/sk_sk.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/sk_sk.json")
|
||||
}
|
||||
"assets/horizon/lang/lt_lt.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/lt_lt.json")
|
||||
}
|
||||
"assets/horizon/lang/it_it.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/it_it.json")
|
||||
}
|
||||
"assets/horizon/lang/nl_nl.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/nl_nl.json")
|
||||
}
|
||||
"assets/horizon/lang/vi_vn.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/vi_vn.json")
|
||||
}
|
||||
"assets/horizon/lang/th_th.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/th_th.json")
|
||||
}
|
||||
"assets/horizon/lang/ja_jp.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/ja_jp.json")
|
||||
}
|
||||
"assets/horizon/lang/ar_sa.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/ar_sa.json")
|
||||
}
|
||||
"assets/horizon/lang/pt_br.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/pt_br.json")
|
||||
}
|
||||
"assets/horizon/lang/cs_cz.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/cs_cz.json")
|
||||
}
|
||||
"assets/horizon/lang/sv_se.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/sv_se.json")
|
||||
}
|
||||
"assets/horizon/lang/el_gr.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/el_gr.json")
|
||||
}
|
||||
"assets/horizon/lang/de_de.json" | "assets/horizon/lang/de_at.json" => {
|
||||
include_str!("../../horizon-client/assets/horizon/lang/de_de.json")
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ ab_glyph = "0.2.32"
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
|
||||
flate2 = { version = "1.1.10", default-features = false, features = ["zlib-rs", "runtime_detection"] }
|
||||
# The network threads of a server's players: epoll, kqueue or Windows' AFD (lan/io_pool.rs).
|
||||
mio = { version = "1.2.4", features = ["os-poll", "net"] }
|
||||
# Shared chunk frames are found by one keyed 128-bit hash of their body (lan/conn.rs SharedFrames).
|
||||
xxhash-rust = { version = "0.8.19", features = ["xxh3"] }
|
||||
horizon-theme = { path = "../horizon-theme" }
|
||||
# Opt-in data collection: the game keeps, the launcher sends (docs/telemetry.md); `live` is
|
||||
# the anonymous "I'm playing" ping the game sends itself (`heartbeat.rs`).
|
||||
|
||||
@@ -0,0 +1,826 @@
|
||||
//! Draws a scene `horizon-ctl render-capture DIR` wrote, offscreen, from its camera, with the
|
||||
//! real `Renderer`, and prints GPU and CPU milliseconds per frame and Horizon's own passes'
|
||||
//! GPU times as JSON (docs/graphics.md, "Render capture and replay").
|
||||
//! `cargo run --release -p horizon-client-render --example scene_replay -- DIR [--frames 300]
|
||||
//! [--warmup 60] [--yaw-offsets 0,90,180,270] [--texture-filtering 0|1|2] [--anisotropy-bit N]
|
||||
//! [--mipmaps N] [--lod-distance CHUNKS] [--render-distance CHUNKS] [--size WxH]
|
||||
//! [--section-draws flat|indirect] [--section-cull on|off] [--section-sides on|off]
|
||||
//! [--gpu-culling on|off] [--root RESOURCES] [--screenshot PNG]
|
||||
//! [--world SAVE --leaf-culling MODE] [--memory on]`
|
||||
//! `--screenshot` saves the first view's last frame, after the timed frames.
|
||||
//! `--world` meshes the captured sections again from that save's region files
|
||||
//! (blocks, biomes and saved light; `ao`, `biomeBlendRadius` and `cutoutLeaves`
|
||||
//! from `./options.txt`) with `--leaf-culling` (default `off`), so a mesher
|
||||
//! change can be measured on the captured view. A section whose 3x3 columns are
|
||||
//! not all saved keeps its captured mesh.
|
||||
//! `--memory on` adds the renderer's largest GPU resources and, on macOS, the process's
|
||||
//! physical footprint by category (`footprint`), taken after the timed frames.
|
||||
use horizon_client_render::{
|
||||
assets::{Assets, RenderAssets},
|
||||
biome::Biomes,
|
||||
blocks::BlockFacts,
|
||||
chunk::{BIOME_STRATEGY, Chunk, ColumnLayout, Light, block_strategy},
|
||||
fast_hash::{FxHashMap, FxHashSet},
|
||||
gfx::{
|
||||
PreparedTextures, Renderer, ScreenshotRequest,
|
||||
capture::{self, AtlasCheck, Scene},
|
||||
panorama::Faces,
|
||||
},
|
||||
graphics::LeafCullingMode,
|
||||
hud::HudBatch,
|
||||
lod::TileKey,
|
||||
mesher::{MeshScratch, MeshTables, Mesher, SectionMesh, packed},
|
||||
models,
|
||||
};
|
||||
use horizon_core::{
|
||||
biome::{BiomeZoom, obfuscate_seed},
|
||||
paletted::PalettedContainer,
|
||||
};
|
||||
use horizon_world::{
|
||||
chunk::BlockState,
|
||||
nbt::{Limits, NamedTag, Tag},
|
||||
region::RegionFile,
|
||||
registry::BlockRegistry,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
event::WindowEvent,
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
|
||||
// The game's allocator, so that `--memory`'s footprint is the game's: the system heap kept
|
||||
// gigabytes of the meshes `--world` frees dirty.
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: horizon_threads::Allocator = horizon_threads::ALLOCATOR;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Options {
|
||||
dir: PathBuf,
|
||||
frames: usize,
|
||||
warmup: usize,
|
||||
yaw_offsets: Vec<f32>,
|
||||
root: Option<PathBuf>,
|
||||
texture_filtering: Option<i32>,
|
||||
anisotropy_bit: Option<i32>,
|
||||
mipmaps: Option<u32>,
|
||||
lod_distance: Option<u32>,
|
||||
render_distance: Option<i32>,
|
||||
size: Option<[u32; 2]>,
|
||||
flat: Option<bool>,
|
||||
cull: Option<bool>,
|
||||
sides: Option<bool>,
|
||||
gpu_culling: Option<bool>,
|
||||
screenshot: Option<PathBuf>,
|
||||
world: Option<PathBuf>,
|
||||
leaf_culling: LeafCullingMode,
|
||||
memory: bool,
|
||||
}
|
||||
|
||||
fn on_off(value: &str) -> Result<bool, String> {
|
||||
match value {
|
||||
"on" | "flat" => Ok(true),
|
||||
"off" | "indirect" => Ok(false),
|
||||
other => Err(format!("expected on or off, got {other}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn options() -> Result<Options, String> {
|
||||
let mut o = Options {
|
||||
frames: 300,
|
||||
warmup: 60,
|
||||
yaw_offsets: vec![0.0],
|
||||
..Default::default()
|
||||
};
|
||||
let mut args = std::env::args().skip(1);
|
||||
let mut dir = None;
|
||||
while let Some(arg) = args.next() {
|
||||
if !arg.starts_with("--") {
|
||||
dir = Some(PathBuf::from(arg));
|
||||
continue;
|
||||
}
|
||||
let value = args.next().ok_or(format!("{arg} needs a value"))?;
|
||||
let number = |v: &str| v.parse::<i64>().map_err(|e| format!("{arg}: {e}"));
|
||||
match arg.as_str() {
|
||||
"--frames" => o.frames = number(&value)?.max(1) as usize,
|
||||
"--warmup" => o.warmup = number(&value)?.max(0) as usize,
|
||||
"--yaw-offsets" => {
|
||||
o.yaw_offsets = value
|
||||
.split(',')
|
||||
.map(|v| v.trim().parse::<f32>().map_err(|e| format!("{arg}: {e}")))
|
||||
.collect::<Result<_, _>>()?
|
||||
}
|
||||
"--root" => o.root = Some(value.into()),
|
||||
"--texture-filtering" => o.texture_filtering = Some(number(&value)? as i32),
|
||||
"--anisotropy-bit" => o.anisotropy_bit = Some(number(&value)? as i32),
|
||||
"--mipmaps" => o.mipmaps = Some(number(&value)?.max(0) as u32),
|
||||
"--lod-distance" => o.lod_distance = Some(number(&value)?.max(0) as u32),
|
||||
"--render-distance" => o.render_distance = Some(number(&value)?.max(0) as i32),
|
||||
"--size" => {
|
||||
let (w, h) = value.split_once('x').ok_or("--size takes WxH")?;
|
||||
o.size = Some([number(w)? as u32, number(h)? as u32]);
|
||||
}
|
||||
"--section-draws" => o.flat = Some(on_off(&value)?),
|
||||
"--section-cull" => o.cull = Some(on_off(&value)?),
|
||||
"--section-sides" => o.sides = Some(on_off(&value)?),
|
||||
"--gpu-culling" => o.gpu_culling = Some(on_off(&value)?),
|
||||
"--screenshot" => o.screenshot = Some(value.into()),
|
||||
"--world" => o.world = Some(value.into()),
|
||||
"--memory" => o.memory = on_off(&value)?,
|
||||
"--leaf-culling" => {
|
||||
o.leaf_culling = LeafCullingMode::ALL
|
||||
.into_iter()
|
||||
.find(|mode| mode.name() == value)
|
||||
.ok_or(format!("no leaf culling mode {value}"))?;
|
||||
}
|
||||
other => return Err(format!("unknown option {other}")),
|
||||
}
|
||||
}
|
||||
o.dir = dir.ok_or("the capture directory is required")?;
|
||||
Ok(o)
|
||||
}
|
||||
|
||||
/// Milliseconds: average, median, 95th percentile and slowest, over `samples`.
|
||||
fn summary(samples: &[f32]) -> Value {
|
||||
if samples.is_empty() {
|
||||
return Value::Null;
|
||||
}
|
||||
let mut sorted = samples.to_vec();
|
||||
sorted.sort_by(f32::total_cmp);
|
||||
let at = |q: f32| sorted[((sorted.len() - 1) as f32 * q).round() as usize];
|
||||
json!({
|
||||
"avg": samples.iter().sum::<f32>() / samples.len() as f32,
|
||||
"min": sorted[0],
|
||||
"p10": at(0.1),
|
||||
"p50": at(0.5),
|
||||
"p95": at(0.95),
|
||||
"max": sorted[sorted.len() - 1],
|
||||
"samples": samples.len(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ms(d: Duration) -> f32 {
|
||||
d.as_secs_f32() * 1000.0
|
||||
}
|
||||
|
||||
fn replay(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let o = options()?;
|
||||
let scene = Scene::read(&o.dir)?;
|
||||
let settings = &scene.settings;
|
||||
let root = o.root.clone().unwrap_or_else(|| settings.resources.clone());
|
||||
let loading = Instant::now();
|
||||
let assets = Assets::load(&root)?;
|
||||
let render = assets.reload(&root)?;
|
||||
if AtlasCheck::of(&render) != settings.atlas {
|
||||
return Err(format!(
|
||||
"the block sprites at {} are not the ones the capture was made with (resource packs \
|
||||
or mods?): pass --root with the game's resource root",
|
||||
root.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let one = [255u8; 4];
|
||||
let zero = [0u8; 4];
|
||||
let prepared = PreparedTextures::new(
|
||||
&render.pixels,
|
||||
render.sprite_size,
|
||||
render.texture_layers,
|
||||
&render.sprite_mips,
|
||||
(&zero[..], 1),
|
||||
)
|
||||
.with_pbr(
|
||||
&render.pbr_normals,
|
||||
&render.pbr_specular,
|
||||
render.pbr_size,
|
||||
render.texture_layers,
|
||||
);
|
||||
let mut renderer = pollster::block_on(Renderer::new(
|
||||
window,
|
||||
true,
|
||||
&render.pixels,
|
||||
render.sprite_size,
|
||||
render.sprite_minimum_size,
|
||||
render.texture_layers,
|
||||
&render.sprite_mips,
|
||||
&render.animations,
|
||||
(&one[..], 1, 1),
|
||||
(&zero[..], 1, 1),
|
||||
(&zero[..], 1, 1),
|
||||
(&zero[..], 1),
|
||||
Arc::new(Faces::colour([0, 0, 0, 255])),
|
||||
Some(prepared),
|
||||
None,
|
||||
settings.graphics_api(),
|
||||
settings.translucent_batching,
|
||||
false,
|
||||
))?;
|
||||
let state = &scene.renderer;
|
||||
let [width, height] = o.size.unwrap_or(state.size);
|
||||
renderer.resize(width, height);
|
||||
renderer.gamma = state.gamma;
|
||||
renderer.fullbright = state.fullbright;
|
||||
renderer.improved_transparency = state.improved_transparency;
|
||||
renderer.animated_textures = state.animated_textures;
|
||||
let filtering = o.texture_filtering.unwrap_or(settings.texture_filtering);
|
||||
let anisotropy = o.anisotropy_bit.unwrap_or(settings.anisotropy_bit);
|
||||
renderer.set_texture_filtering(filtering, anisotropy);
|
||||
renderer.set_mipmap_levels(o.mipmaps.unwrap_or(settings.mipmap_levels));
|
||||
renderer.set_world_post(
|
||||
settings.resolution_scale,
|
||||
settings.fxaa,
|
||||
settings.taa,
|
||||
settings.upscale,
|
||||
);
|
||||
let mut notes = Vec::new();
|
||||
let mut note = |what: &str, result: horizon_client_render::Result<()>| {
|
||||
if let Err(e) = result {
|
||||
notes.push(format!("{what}: {e}"));
|
||||
}
|
||||
};
|
||||
note(
|
||||
"section-draws",
|
||||
renderer.set_flat_draws(o.flat.unwrap_or(state.flat_draws)),
|
||||
);
|
||||
note(
|
||||
"section-cull",
|
||||
renderer.set_section_cull(o.cull.unwrap_or(state.section_cull)),
|
||||
);
|
||||
note(
|
||||
"gpu-culling",
|
||||
renderer.set_gpu_culling(o.gpu_culling.unwrap_or(state.gpu_culling)),
|
||||
);
|
||||
renderer.set_section_sides(o.sides.unwrap_or(state.section_sides));
|
||||
renderer.set_pass_timing(true);
|
||||
let layout = ColumnLayout::of_level(state.min_y, state.sections as i32 * 16);
|
||||
if layout != ColumnLayout::VANILLA {
|
||||
renderer.clear_sections(layout);
|
||||
}
|
||||
|
||||
// Near sections first: with no far tile resident yet, none fades in.
|
||||
let camera = scene.frame.camera;
|
||||
let column = |x: f64| (x.floor() as i32) >> 4;
|
||||
let (cx, cz) = (column(camera[0]), column(camera[2]));
|
||||
let near = |x: i32, z: i32| {
|
||||
o.render_distance
|
||||
.is_none_or(|r| (x - cx).pow(2) + (z - cz).pow(2) <= r * r)
|
||||
};
|
||||
let (mut remeshed, mut world_report) = (FxHashMap::default(), Value::Null);
|
||||
if let Some(world) = &o.world {
|
||||
let keys: Vec<[i32; 3]> = section_keys(&o.dir)?
|
||||
.into_iter()
|
||||
.filter(|&[x, _, z]| near(x, z))
|
||||
.collect();
|
||||
let started = Instant::now();
|
||||
remeshed = remesh(world, &root, &assets, &render, o.leaf_culling, &keys)?;
|
||||
world_report = json!({
|
||||
"save": world.display().to_string(),
|
||||
"leaf_culling": o.leaf_culling.name(),
|
||||
"remeshed": remeshed.len(),
|
||||
"kept": keys.len() - remeshed.len(),
|
||||
"remesh_ms": ms(started.elapsed()),
|
||||
});
|
||||
}
|
||||
let leaf_layers = leaf_layers(&render);
|
||||
let (mut sections, mut quads, mut leaf_quads) = (0, 0usize, 0usize);
|
||||
let mut packed_quads = 0usize;
|
||||
capture::read_meshes(&o.dir, "sections", |[x, y, z], captured| {
|
||||
if near(x, z) {
|
||||
let mesh = remeshed.remove(&[x, y, z]).unwrap_or(captured);
|
||||
quads += mesh.solid.len() / 4;
|
||||
packed_quads += packed::packed_quads(&mesh.packed, mesh.solid.len() / 4);
|
||||
leaf_quads += mesh
|
||||
.solid
|
||||
.iter()
|
||||
.filter(|v| leaf_layers.contains(&v.layer))
|
||||
.count()
|
||||
/ 4;
|
||||
renderer.upload((x, y, z), &mesh);
|
||||
sections += 1;
|
||||
}
|
||||
})?;
|
||||
let mut tiles: Vec<(TileKey, SectionMesh)> = Vec::new();
|
||||
capture::read_meshes(&o.dir, "tiles", |[x, z, level], mesh| {
|
||||
let key = TileKey {
|
||||
x,
|
||||
z,
|
||||
level: level as u8,
|
||||
};
|
||||
let [ox, _, oz] = key.origin();
|
||||
let half = f64::from(key.size()) / 2.0;
|
||||
let (dx, dz) = (
|
||||
f64::from(ox) + half - camera[0],
|
||||
f64::from(oz) + half - camera[2],
|
||||
);
|
||||
let near = o
|
||||
.lod_distance
|
||||
.is_none_or(|d| (dx * dx + dz * dz).sqrt() <= f64::from(d) * 16.0);
|
||||
if near {
|
||||
tiles.push((key, mesh));
|
||||
}
|
||||
})?;
|
||||
let tile_count = tiles.len();
|
||||
if !tiles.is_empty() {
|
||||
let cutout: FxHashSet<(i32, i32)> = scene.lod_cutout.iter().copied().collect();
|
||||
renderer.queue_lods(tiles, Vec::new(), Some(cutout));
|
||||
renderer.upload_lods(Duration::from_secs(3600));
|
||||
}
|
||||
let loaded_ms = ms(loading.elapsed());
|
||||
|
||||
let hud = HudBatch::new([width, height]);
|
||||
let mut views = Vec::new();
|
||||
for &offset in &o.yaw_offsets {
|
||||
let mut frame = scene.frame(&hud);
|
||||
frame.yaw += offset;
|
||||
frame.eye_rotation[0] += offset;
|
||||
if let Some(r) = o.render_distance {
|
||||
frame.distance = (r * 16) as f32;
|
||||
frame.detail_distance = frame.distance;
|
||||
}
|
||||
let draw = |renderer: &mut Renderer| -> horizon_client_render::Result<Duration> {
|
||||
if frame.cave_culling {
|
||||
renderer.start_occlusion(frame.camera, frame.distance);
|
||||
}
|
||||
let started = Instant::now();
|
||||
renderer.render(&frame)?;
|
||||
Ok(started.elapsed())
|
||||
};
|
||||
for _ in 0..o.warmup {
|
||||
draw(&mut renderer)?;
|
||||
}
|
||||
renderer.gpu_times.clear();
|
||||
let (mut call, mut encode, mut gpu) = (Vec::new(), Vec::new(), Vec::new());
|
||||
for _ in 0..o.frames {
|
||||
call.push(ms(draw(&mut renderer)?));
|
||||
encode.push(ms(renderer.last_encode));
|
||||
gpu.append(&mut renderer.gpu_times);
|
||||
}
|
||||
// Timestamps arrive a few frames late: a few more of the same frame bring them in.
|
||||
for _ in 0..16 {
|
||||
if gpu.len() >= o.frames {
|
||||
break;
|
||||
}
|
||||
draw(&mut renderer)?;
|
||||
gpu.append(&mut renderer.gpu_times);
|
||||
}
|
||||
gpu.truncate(o.frames);
|
||||
if let Some(path) = o.screenshot.as_ref().filter(|_| views.is_empty()) {
|
||||
renderer.screenshot_result = None;
|
||||
renderer.screenshot = Some(ScreenshotRequest {
|
||||
path: path.clone(),
|
||||
size: None,
|
||||
scaled: false,
|
||||
});
|
||||
for _ in 0..32 {
|
||||
draw(&mut renderer)?;
|
||||
if renderer.screenshot_result.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match renderer.screenshot_result.take() {
|
||||
Some(Ok(_)) => {}
|
||||
Some(Err(e)) => notes.push(format!("screenshot: {e}")),
|
||||
None => notes.push("screenshot: not saved".into()),
|
||||
}
|
||||
}
|
||||
views.push(json!({
|
||||
"yaw_offset": offset,
|
||||
"gpu_ms": summary(&gpu),
|
||||
"render_call_ms": summary(&call),
|
||||
"cpu_encode_ms": summary(&encode),
|
||||
"gpu_passes_median_ms": renderer
|
||||
.own_pass_times()
|
||||
.into_iter()
|
||||
.map(|(name, ms)| (name, json!(ms)))
|
||||
.collect::<serde_json::Map<_, _>>(),
|
||||
"drawn_sections": renderer.drawn_sections,
|
||||
"section_draws": renderer.section_draws,
|
||||
"section_draw_calls": renderer.section_draw_calls,
|
||||
// Occlusion culling's last result: boxes hidden, boxes tested, share of solid
|
||||
// indices hidden.
|
||||
"culled": renderer.culled_sections(),
|
||||
}));
|
||||
}
|
||||
let section_draws = if renderer.flat_draws() {
|
||||
"flat"
|
||||
} else {
|
||||
"indirect"
|
||||
};
|
||||
let memory = o.memory.then(|| memory_report(&renderer));
|
||||
let report = json!({
|
||||
"capture": o.dir.display().to_string(),
|
||||
"adapter": renderer.info.name,
|
||||
"captured_on": state.adapter,
|
||||
"size": [width, height],
|
||||
"sections": sections,
|
||||
"solid_quads": quads,
|
||||
"leaf_quads": leaf_quads,
|
||||
// Solid quads uploaded as one record each (`mesher::packed`).
|
||||
"packed_quads": packed_quads,
|
||||
"world": world_report,
|
||||
"tiles": tile_count,
|
||||
"load_ms": loaded_ms,
|
||||
"frames": o.frames,
|
||||
"warmup": o.warmup,
|
||||
"settings": {
|
||||
"texture_filtering": filtering,
|
||||
"anisotropy_bit": anisotropy,
|
||||
"mipmaps": o.mipmaps.unwrap_or(settings.mipmap_levels),
|
||||
"lod_distance": o.lod_distance,
|
||||
"render_distance": o.render_distance,
|
||||
"section_draws": section_draws,
|
||||
"section_cull": renderer.section_cull(),
|
||||
"section_sides": renderer.section_sides(),
|
||||
"gpu_culling": renderer.gpu_culling(),
|
||||
"shader_pack_left_out": state.shader_pack,
|
||||
},
|
||||
"notes": notes,
|
||||
"views": views,
|
||||
"memory": memory,
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mb(bytes: u64) -> f64 {
|
||||
(bytes as f64 / 1048576.0 * 10.0).round() / 10.0
|
||||
}
|
||||
|
||||
/// `--memory`: the renderer's census in MiB and, on macOS, `footprint`'s summary of this
|
||||
/// process (GPU buffers and textures count as IOAccelerator memory there).
|
||||
fn memory_report(renderer: &Renderer) -> Value {
|
||||
let gpu: serde_json::Map<String, Value> = renderer
|
||||
.gpu_census()
|
||||
.into_iter()
|
||||
.map(|(name, bytes)| (format!("{name}_mb"), json!(mb(bytes))))
|
||||
.collect();
|
||||
let footprint = std::process::Command::new("/usr/bin/footprint")
|
||||
.args(["-f", "bytes", "-p", &std::process::id().to_string()])
|
||||
.output()
|
||||
.ok()
|
||||
.map(|out| String::from_utf8_lossy(&out.stdout).into_owned());
|
||||
let footprint = footprint.map(|text| {
|
||||
let mut total = None;
|
||||
let mut categories = Vec::new();
|
||||
for line in text.lines() {
|
||||
if let Some(at) = line.find("Footprint: ") {
|
||||
total = line[at + 11..]
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.and_then(|v| v.parse::<u64>().ok());
|
||||
}
|
||||
// " <dirty> B <clean> B <reclaimable> B <regions> <category>"
|
||||
let words: Vec<&str> = line.split_whitespace().collect();
|
||||
if words.len() >= 8
|
||||
&& words[1] == "B"
|
||||
&& let Ok(dirty) = words[0].parse::<u64>()
|
||||
&& dirty >= 1 << 20
|
||||
{
|
||||
categories.push(json!([words[7..].join(" "), mb(dirty)]));
|
||||
}
|
||||
}
|
||||
json!({ "total_mb": total.map(mb), "dirty_by_category_mb": categories })
|
||||
});
|
||||
json!({ "gpu": gpu, "footprint": footprint })
|
||||
}
|
||||
|
||||
struct App;
|
||||
impl ApplicationHandler for App {
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(Window::default_attributes().with_visible(false))
|
||||
.unwrap(),
|
||||
);
|
||||
if let Err(error) = replay(window) {
|
||||
eprintln!("scene_replay: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
event_loop.exit();
|
||||
}
|
||||
fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
EventLoop::new().unwrap().run_app(&mut App).unwrap();
|
||||
}
|
||||
|
||||
/// The captured sections' keys, from the index alone.
|
||||
fn section_keys(dir: &Path) -> Result<Vec<[i32; 3]>, Box<dyn std::error::Error>> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Key {
|
||||
key: [i32; 3],
|
||||
}
|
||||
let records: Vec<Key> = serde_json::from_reader(std::io::BufReader::new(std::fs::File::open(
|
||||
dir.join("sections.json"),
|
||||
)?))?;
|
||||
Ok(records.into_iter().map(|r| r.key).collect())
|
||||
}
|
||||
|
||||
/// Sprite layers of every leaves block, as terrain vertices carry them (with or
|
||||
/// without `OPAQUE_LAYER`).
|
||||
fn leaf_layers(render: &RenderAssets) -> FxHashSet<u16> {
|
||||
render
|
||||
.sprite_layers
|
||||
.iter()
|
||||
.filter(|(name, _)| name.ends_with("_leaves"))
|
||||
.flat_map(|(_, &layer)| {
|
||||
[
|
||||
layer as u16,
|
||||
layer as u16 | horizon_client_render::mesher::OPAQUE_LAYER,
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `--world`: the sections `keys` meshed again from `save`'s overworld region
|
||||
/// files, on every core, as the game's workers mesh them.
|
||||
fn remesh(
|
||||
save: &Path,
|
||||
root: &Path,
|
||||
assets: &Assets,
|
||||
render: &RenderAssets,
|
||||
leaf_culling: LeafCullingMode,
|
||||
keys: &[[i32; 3]],
|
||||
) -> Result<FxHashMap<[i32; 3], SectionMesh>, Box<dyn std::error::Error>> {
|
||||
let facts = BlockFacts::load(root, assets.registry)?;
|
||||
let visibility = models::load_visibility(root, assets.registry)?;
|
||||
let tables = MeshTables::new(assets, render, &facts, visibility)?;
|
||||
let biomes = Biomes::load(root)?;
|
||||
let seed = world_seed(save).unwrap_or_else(|| {
|
||||
eprintln!(
|
||||
"scene_replay: no seed in {}, biome blending uses 0",
|
||||
save.display()
|
||||
);
|
||||
0
|
||||
});
|
||||
let options = std::fs::read_to_string("options.txt").unwrap_or_default();
|
||||
let option = |key: &str| {
|
||||
options
|
||||
.lines()
|
||||
.find_map(|l| l.strip_prefix(key)?.strip_prefix(':').map(str::to_owned))
|
||||
};
|
||||
let smooth_lighting = option("ao").is_none_or(|v| v == "true");
|
||||
let cutout_leaves = option("cutoutLeaves").is_none_or(|v| v == "true");
|
||||
let blend_radius = option("biomeBlendRadius")
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(2);
|
||||
|
||||
let mut columns: Vec<(i32, i32)> = keys
|
||||
.iter()
|
||||
.flat_map(|&[x, _, z]| {
|
||||
(-1..=1).flat_map(move |dz| (-1..=1).map(move |dx| (x + dx, z + dz)))
|
||||
})
|
||||
.collect();
|
||||
columns.sort_unstable();
|
||||
columns.dedup();
|
||||
let threads = std::thread::available_parallelism().map_or(4, |n| n.get());
|
||||
let region_dir = save.join("dimensions/minecraft/overworld/region");
|
||||
let next = AtomicUsize::new(0);
|
||||
let chunks: FxHashMap<(i32, i32), Chunk> = std::thread::scope(|scope| {
|
||||
let workers: Vec<_> = (0..threads)
|
||||
.map(|_| {
|
||||
scope.spawn(|| {
|
||||
let mut files: FxHashMap<(i32, i32), Option<RegionFile>> = FxHashMap::default();
|
||||
let mut out = Vec::new();
|
||||
loop {
|
||||
let i = next.fetch_add(1, Ordering::Relaxed);
|
||||
let Some(&(x, z)) = columns.get(i) else {
|
||||
break;
|
||||
};
|
||||
let file = files.entry((x >> 5, z >> 5)).or_insert_with(|| {
|
||||
RegionFile::open(region_dir.join(format!(
|
||||
"r.{}.{}.mca",
|
||||
x >> 5,
|
||||
z >> 5
|
||||
)))
|
||||
.ok()
|
||||
});
|
||||
let root = file
|
||||
.as_mut()
|
||||
.and_then(|f| {
|
||||
f.read(horizon_core::position::ChunkPos::new(x, z))
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
.and_then(|record| record.nbt(Limits::default()).ok());
|
||||
if let Some(chunk) =
|
||||
root.and_then(|root| decode(&root, assets.registry, &biomes))
|
||||
{
|
||||
out.push(((x, z), chunk));
|
||||
}
|
||||
}
|
||||
out
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
workers
|
||||
.into_iter()
|
||||
.flat_map(|w| w.join().unwrap())
|
||||
.collect()
|
||||
});
|
||||
|
||||
let meshable: Vec<[i32; 3]> = keys
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&[x, _, z]| {
|
||||
(-1..=1).all(|dz| (-1..=1).all(|dx| chunks.contains_key(&(x + dx, z + dz))))
|
||||
})
|
||||
.collect();
|
||||
let next = AtomicUsize::new(0);
|
||||
let meshes = std::thread::scope(|scope| {
|
||||
let workers: Vec<_> = (0..threads)
|
||||
.map(|_| {
|
||||
scope.spawn(|| {
|
||||
let mut mesher = Mesher {
|
||||
nether_shading: false,
|
||||
shader_pack_lighting: false,
|
||||
separate_ao: false,
|
||||
assets,
|
||||
render,
|
||||
tables: &tables,
|
||||
biomes: &biomes,
|
||||
visibility: tables.visibility.clone(),
|
||||
zoom: BiomeZoom::new(obfuscate_seed(seed)),
|
||||
blend_radius,
|
||||
smooth_lighting,
|
||||
cutout_leaves,
|
||||
translucent_layers: &[],
|
||||
leaf_culling,
|
||||
};
|
||||
let mut scratch = MeshScratch::default();
|
||||
let mut out = Vec::new();
|
||||
loop {
|
||||
let i = next.fetch_add(1, Ordering::Relaxed);
|
||||
let Some(&[x, y, z]) = meshable.get(i) else {
|
||||
break;
|
||||
};
|
||||
let region: [[Option<&Chunk>; 3]; 3] = std::array::from_fn(|dz| {
|
||||
std::array::from_fn(|dx| {
|
||||
chunks.get(&(x + dx as i32 - 1, z + dz as i32 - 1))
|
||||
})
|
||||
});
|
||||
let mut mesh =
|
||||
mesher.mesh_reusing(®ion, y, SectionMesh::default(), &mut scratch);
|
||||
mesh.sort_solid();
|
||||
out.push(([x, y, z], mesh));
|
||||
}
|
||||
out
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
workers
|
||||
.into_iter()
|
||||
.flat_map(|w| w.join().unwrap())
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
});
|
||||
Ok(meshes)
|
||||
}
|
||||
|
||||
/// The world seed: `data/minecraft/world_gen_settings.dat`, or level.dat's
|
||||
/// `Data.WorldGenSettings` in older saves.
|
||||
fn world_seed(save: &Path) -> Option<i64> {
|
||||
let read = |path: PathBuf| {
|
||||
horizon_world::datafix::files::read_dat(&path)
|
||||
.ok()
|
||||
.flatten()
|
||||
};
|
||||
let seed = |tag: Option<&Tag>| match tag?.get("seed")? {
|
||||
Tag::Long(seed) => Some(*seed),
|
||||
_ => None,
|
||||
};
|
||||
seed(
|
||||
read(save.join("data/minecraft/world_gen_settings.dat"))
|
||||
.as_ref()
|
||||
.and_then(|t| t.get("data")),
|
||||
)
|
||||
.or_else(|| {
|
||||
seed(
|
||||
read(save.join("level.dat"))?
|
||||
.get("Data")?
|
||||
.get("WorldGenSettings"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// A saved full chunk's blocks, biomes and light, as the client's own save
|
||||
/// loader reads them (`save::decode_lit`, in horizon-client).
|
||||
fn decode(root: &NamedTag, registry: &BlockRegistry, biomes: &Biomes) -> Option<Chunk> {
|
||||
let tag = &root.tag;
|
||||
let text = |t: Option<&Tag>| match t {
|
||||
Some(Tag::String(s)) => Some(s.to_string_lossy().to_string()),
|
||||
_ => None,
|
||||
};
|
||||
if text(tag.get("Status")).is_some_and(|s| s != "minecraft:full" && s != "full") {
|
||||
return None;
|
||||
}
|
||||
if tag.get("isLightOn") != Some(&Tag::Byte(1)) {
|
||||
return None;
|
||||
}
|
||||
let (Some(Tag::Int(x)), Some(Tag::Int(z))) = (tag.get("xPos"), tag.get("zPos")) else {
|
||||
return None;
|
||||
};
|
||||
let mut chunk = Chunk::with_layout(*x, *z, ColumnLayout::VANILLA);
|
||||
let air = registry.default_state("minecraft:air")?.0;
|
||||
let Some(Tag::List(sections)) = tag.get("sections") else {
|
||||
return Some(chunk);
|
||||
};
|
||||
let longs = |t: Option<&Tag>| match t {
|
||||
Some(Tag::LongArray(data)) => Some(data.iter().map(|&w| w as u64).collect::<Vec<_>>()),
|
||||
_ => None,
|
||||
};
|
||||
let mut layers = vec![(None, None); chunk.sections.len()];
|
||||
for section in sections {
|
||||
let Some(Tag::Byte(y)) = section.get("Y") else {
|
||||
continue;
|
||||
};
|
||||
let Some(si) = chunk.section_index(i32::from(*y) * 16) else {
|
||||
continue;
|
||||
};
|
||||
let target = Arc::make_mut(&mut chunk.sections[si]);
|
||||
if let Some(states) = section.get("block_states")
|
||||
&& let Some(Tag::List(palette)) = states.get("palette")
|
||||
{
|
||||
let palette: Vec<u32> = palette
|
||||
.iter()
|
||||
.map(|p| {
|
||||
BlockState::from_nbt(p)
|
||||
.ok()
|
||||
.and_then(|s| {
|
||||
registry.id(&s).or_else(|| {
|
||||
let default = registry.default_state(&s.name)?;
|
||||
let mut full = registry.state(default)?.clone();
|
||||
for (k, v) in &s.properties {
|
||||
if full.properties.contains_key(k) {
|
||||
full.properties.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
registry.id(&full).or(Some(default))
|
||||
})
|
||||
})
|
||||
.map_or(air, |id| id.0)
|
||||
})
|
||||
.collect();
|
||||
let data = longs(states.get("data"));
|
||||
if let Ok(blocks) =
|
||||
PalettedContainer::from_packed(block_strategy(), palette, data.as_deref())
|
||||
{
|
||||
target.blocks = blocks;
|
||||
}
|
||||
}
|
||||
if let Some(b) = section.get("biomes")
|
||||
&& let Some(Tag::List(palette)) = b.get("palette")
|
||||
{
|
||||
let palette: Vec<u32> = palette
|
||||
.iter()
|
||||
.map(|p| match p {
|
||||
Tag::String(s) => u32::from(biomes.id(&s.to_string_lossy())),
|
||||
_ => 0,
|
||||
})
|
||||
.collect();
|
||||
let data = longs(b.get("data"));
|
||||
if let Ok(container) =
|
||||
PalettedContainer::from_packed(BIOME_STRATEGY, palette, data.as_deref())
|
||||
{
|
||||
target.biomes = container;
|
||||
}
|
||||
}
|
||||
let layer = |name: &str| match section.get(name) {
|
||||
Some(Tag::ByteArray(a)) if a.len() == 2048 => Some(a.clone()),
|
||||
_ => None,
|
||||
};
|
||||
layers[si] = (layer("SkyLight"), layer("BlockLight"));
|
||||
}
|
||||
// `save::load_light`: a section without saved sky light takes the light
|
||||
// of the blocks above it, from the top of the column down.
|
||||
let mut above = [15u8; 256];
|
||||
for (si, (sky, block)) in layers.into_iter().enumerate().rev() {
|
||||
let mut data = Box::new([0u8; 4096]);
|
||||
for (i, v) in data.iter_mut().enumerate() {
|
||||
let nibble =
|
||||
|layer: &Option<Vec<u8>>| layer.as_ref().map(|l| l[i >> 1] >> ((i & 1) << 2) & 15);
|
||||
let s = nibble(&sky).unwrap_or(above[i & 255]);
|
||||
let b = nibble(&block).unwrap_or(0);
|
||||
*v = s << 4 | b;
|
||||
}
|
||||
if sky.is_some() {
|
||||
for (i, a) in above.iter_mut().enumerate() {
|
||||
*a = data[i] >> 4;
|
||||
}
|
||||
}
|
||||
let section = Arc::make_mut(&mut chunk.sections[si]);
|
||||
section.light = Light::Full(data);
|
||||
section.compact();
|
||||
}
|
||||
chunk.lit = true;
|
||||
chunk.light_loaded = true;
|
||||
Some(chunk)
|
||||
}
|
||||
@@ -207,6 +207,20 @@ impl Assets {
|
||||
pub fn reload(&self, root: &Path) -> Result<RenderAssets> {
|
||||
RenderAssets::load(root, self)
|
||||
}
|
||||
/// [`Assets::reload`] with the model library already parsed.
|
||||
///
|
||||
/// Parsing it reads every model file under `assets/`, which needs no
|
||||
/// block registry, so a caller that has the files to spare can start
|
||||
/// it before `Assets::load` finishes and hand the result in here. On
|
||||
/// a cold cache that is most of a second of reading moved off the
|
||||
/// critical path.
|
||||
pub fn reload_with_models(
|
||||
&self,
|
||||
root: &Path,
|
||||
parsed: crate::model_library::ParsedModels,
|
||||
) -> Result<RenderAssets> {
|
||||
RenderAssets::load_with_parsed(root, self, true, Some(parsed))
|
||||
}
|
||||
/// [`Assets::reload`] for a server, which draws nothing: the sprites'
|
||||
/// layers, without the block or item models or the textures' pixels.
|
||||
pub fn reload_for_server(&self, root: &Path) -> Result<RenderAssets> {
|
||||
@@ -218,6 +232,16 @@ impl RenderAssets {
|
||||
Self::load_with(root, assets, true)
|
||||
}
|
||||
fn load_with(root: &Path, assets: &Assets, draws: bool) -> Result<Self> {
|
||||
Self::load_with_parsed(root, assets, draws, None)
|
||||
}
|
||||
/// `parsed`: the model library when a caller parsed it ahead of this
|
||||
/// (see [`Assets::reload_with_models`]); otherwise it is parsed here.
|
||||
fn load_with_parsed(
|
||||
root: &Path,
|
||||
assets: &Assets,
|
||||
draws: bool,
|
||||
parsed: Option<crate::model_library::ParsedModels>,
|
||||
) -> Result<Self> {
|
||||
let registry = assets.registry;
|
||||
let mut sprites = crate::sprites::Sprites::with_disk_cache(root);
|
||||
for name in MATERIALS {
|
||||
@@ -225,17 +249,24 @@ impl RenderAssets {
|
||||
}
|
||||
// A server draws no block and no item: its block models (about 24 MB
|
||||
// baked) and item model library (7 MB) stay empty.
|
||||
let parsed = if draws {
|
||||
crate::model_library::ParsedModels::load(root)
|
||||
} else {
|
||||
crate::model_library::ParsedModels::default()
|
||||
let parsed = match parsed {
|
||||
Some(parsed) => parsed,
|
||||
None if draws => {
|
||||
let _step = crate::spans::startup::step("core.models_parse");
|
||||
crate::model_library::ParsedModels::load(root)
|
||||
}
|
||||
None => crate::model_library::ParsedModels::default(),
|
||||
};
|
||||
let mut models = if draws {
|
||||
let _step = crate::spans::startup::step("core.models_bake");
|
||||
crate::models::prepare_with_models(root, registry, &mut sprites, &parsed)?
|
||||
} else {
|
||||
crate::models::PreparedModels::default()
|
||||
};
|
||||
let model_library = parsed.into_item_library();
|
||||
let model_library = {
|
||||
let _step = crate::spans::startup::step("core.item_library");
|
||||
parsed.into_item_library()
|
||||
};
|
||||
// Fluids are drawn by the fluid renderer, not block models; breaking
|
||||
// progress uses the destroy-stage overlays.
|
||||
for name in [
|
||||
@@ -251,6 +282,7 @@ impl RenderAssets {
|
||||
sprites.insert(root, &format!("minecraft:block/destroy_stage_{stage}"))?;
|
||||
}
|
||||
let fusion = if draws {
|
||||
let _step = crate::spans::startup::step("core.fusion");
|
||||
crate::fusion::FusionTextures::load(root, registry, &mut sprites, &mut models.catalog)
|
||||
} else {
|
||||
Default::default()
|
||||
|
||||
@@ -50,8 +50,9 @@ struct Out {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
@location(2) spherical: f32,
|
||||
@location(3) cylindrical: f32,
|
||||
// The distances fog reads: .x spherical, .y cylindrical. One vec2 rather than two floats, so that
|
||||
// the fragment stage stays within 14 user-defined inputs plus front_facing (GL's limit of 15, #8195).
|
||||
@location(2) fog_distance: vec2<f32>,
|
||||
// entity.vsh's overlayColor: the OverlayTexture texel (see overlay_texel).
|
||||
@location(5) overlay_color: vec4<f32>,
|
||||
@location(4) @interpolate(flat) overlay: u32,
|
||||
@@ -157,11 +158,11 @@ fn entity_vertex(position: vec3<f32>, uv: vec2<f32>,
|
||||
out.back_color = back;
|
||||
out.overlay_color = overlay_texel(overlay);
|
||||
// Sky geometry (clouds, celestial bodies) is exempt from render-distance fog.
|
||||
out.spherical = select(length(position), 0.0, overlay >= 2u && overlay <= 4u);
|
||||
out.cylindrical = select(max(length(position.xz), abs(position.y)), 0.0, overlay >= 2u && overlay <= 4u);
|
||||
out.fog_distance.x = select(length(position), 0.0, overlay >= 2u && overlay <= 4u);
|
||||
out.fog_distance.y = select(max(length(position.xz), abs(position.y)), 0.0, overlay >= 2u && overlay <= 4u);
|
||||
out.overlay = overlay;
|
||||
out.glint_uv = vec2(0.0);
|
||||
if overlay == 4u { out.spherical = length(position); }
|
||||
if overlay == 4u { out.fog_distance.x = length(position); }
|
||||
return out;
|
||||
}
|
||||
@vertex fn vs_entity(@location(0) position: vec3<f32>, @location(1) uv: vec2<f32>,
|
||||
@@ -293,7 +294,7 @@ fn entity_color(in: Out, front: bool) -> vec4<f32> {
|
||||
color = vec4(c, 1.0);
|
||||
} else if in.overlay == 4u {
|
||||
// clouds.fsh: alpha fades out towards the cloud fog end.
|
||||
return vec4(color.rgb, color.a * (1.0 - linear_fog(in.spherical, 0.0, globals.fog_ends.y)));
|
||||
return vec4(color.rgb, color.a * (1.0 - linear_fog(in.fog_distance.x, 0.0, globals.fog_ends.y)));
|
||||
}
|
||||
if color.a < 0.1 || (in.cull != 0u && !front) { discard; }
|
||||
// entity.fsh calculateFinalColor: the overlay (hurt red, TNT white) over the
|
||||
@@ -312,8 +313,8 @@ fn entity_color(in: Out, front: bool) -> vec4<f32> {
|
||||
textureSample(glint_armor_tex, glint_sampler, armor_glint_uv(in.glint_uv)),
|
||||
);
|
||||
}
|
||||
let f = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w));
|
||||
let f = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w));
|
||||
return vec4(mix(color.rgb, globals.fog_color.rgb, f * globals.fog_color.a), color.a);
|
||||
}
|
||||
|
||||
@@ -327,8 +328,8 @@ fn entity_color(in: Out, front: bool) -> vec4<f32> {
|
||||
fn shadow_color(in: Out) -> vec4<f32> {
|
||||
var color = textureSample(atlas, atlas_sampler, in.uv) * in.color;
|
||||
color *= in.light_color;
|
||||
let f = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w));
|
||||
let f = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w));
|
||||
return vec4(mix(color.rgb, globals.fog_color.rgb, f * globals.fog_color.a), color.a);
|
||||
}
|
||||
|
||||
@@ -347,8 +348,8 @@ fn shadow_color(in: Out) -> vec4<f32> {
|
||||
let texel = textureSample(atlas, atlas_sampler, uv);
|
||||
if texel.a < 0.1 { discard; }
|
||||
// Fog hides a light source and the light on a surface as it hides everything else.
|
||||
let fogged = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w)) * globals.fog_color.a;
|
||||
let fogged = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w)) * globals.fog_color.a;
|
||||
if in.tier == 0u {
|
||||
return vec4(0.0, 0.0, 0.0, hdr_scene_alpha(mix(in.scene, globals.hdr_light.y, fogged)));
|
||||
}
|
||||
@@ -379,7 +380,7 @@ fn hdr_mark(tier: u32, y: f32) -> f32 {
|
||||
// HDR lighting on clouds, drawn again after them with only alpha written: they're lit by the
|
||||
// sky, so they take its value, which their blending would otherwise cover.
|
||||
@fragment fn fs_hdr_cloud_mark(in: Out) -> @location(0) vec4<f32> {
|
||||
let a = in.color.a * (1.0 - linear_fog(in.spherical, 0.0, globals.fog_ends.y));
|
||||
let a = in.color.a * (1.0 - linear_fog(in.fog_distance.x, 0.0, globals.fog_ends.y));
|
||||
if a < 0.1 { discard; }
|
||||
return vec4(0.0, 0.0, 0.0, hdr_scene_alpha(globals.hdr_light.y));
|
||||
}
|
||||
@@ -388,8 +389,8 @@ fn celestial_color(in: Out) -> vec4<f32> {
|
||||
// RenderPipelines.DRAGON_RAYS / rendertype_lightning.fsh: untextured
|
||||
// vertex colour, with fog fading both RGB and alpha before additive blending.
|
||||
if in.overlay == 37u {
|
||||
let fog = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w));
|
||||
let fog = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w));
|
||||
return in.color * (1.0 - fog);
|
||||
}
|
||||
return textureSample(atlas, atlas_sampler, in.uv) * in.color * in.light_color;
|
||||
@@ -428,8 +429,8 @@ fn energy_swirl_base(in: Out) -> vec4<f32> {
|
||||
return vec4(color.rgb * (128.0 / 255.0), color.a);
|
||||
}
|
||||
fn energy_swirl_fog(in: Out) -> f32 {
|
||||
let f = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w));
|
||||
let f = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w));
|
||||
return f * globals.fog_color.a;
|
||||
}
|
||||
@fragment fn fs_energy_swirl(in: Out) -> @location(0) vec4<f32> {
|
||||
@@ -572,7 +573,7 @@ fn weather_color(in: Out) -> vec4<f32> {
|
||||
var color = textureLoad(atlas, texel, 0) * in.color;
|
||||
if color.a < 0.1 { discard; }
|
||||
color *= in.light_color;
|
||||
let f = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w));
|
||||
let f = max(linear_fog(in.fog_distance.x, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.fog_distance.y, globals.fog.z, globals.fog.w));
|
||||
return vec4(mix(color.rgb, globals.fog_color.rgb, f * globals.fog_color.a), color.a);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ pub struct Environment {
|
||||
tracks: HashMap<String, Track>,
|
||||
base: HashMap<String, [f32; 4]>,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Sky {
|
||||
pub sky_color: [f32; 3],
|
||||
pub fog_color: [f32; 3],
|
||||
|
||||
@@ -21,7 +21,7 @@ pub enum FogType {
|
||||
}
|
||||
|
||||
/// The fog uniform (`FogData`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FogData {
|
||||
pub color: [f32; 4],
|
||||
pub environmental_start: f32,
|
||||
|
||||
@@ -104,8 +104,11 @@ struct FontSet {
|
||||
providers: Vec<Provider>,
|
||||
/// Every char drawn looks up here, so the hasher is Fx, not SipHash.
|
||||
glyphs: crate::fast_hash::FxHashMap<char, Glyph>,
|
||||
/// `glyphsByWidth` for obfuscated text (built on first use).
|
||||
by_width: Option<HashMap<u32, Vec<char>>>,
|
||||
/// `SelectedGlyphs.nonFishy` of the characters whose first glyph has a fishy advance (the
|
||||
/// others use `glyphs`): what obfuscated text draws.
|
||||
non_fishy: crate::fast_hash::FxHashMap<char, Glyph>,
|
||||
/// `glyphsByWidth` for obfuscated text (built on first use), by `ceil(advance)`.
|
||||
by_width: Option<HashMap<i32, Vec<char>>>,
|
||||
}
|
||||
/// The atlas region glyphs are rasterized into, filled in rows.
|
||||
struct Region {
|
||||
@@ -258,10 +261,11 @@ fn used_width(pixels: &[u8], stride: u32, (cx, cy, cw, ch): (u32, u32, u32, u32)
|
||||
})
|
||||
.map_or(0, |x| x + 1)
|
||||
}
|
||||
/// `FontSet.hasFishyAdvance`: a provider glyph whose advance is under 0 or
|
||||
/// over 32 is not used. `FontSet.computeGlyphInfo` moves on to the next
|
||||
/// provider that has the character, so a pack's absurd advance cannot widen
|
||||
/// a line; Horizon keeps one advance per glyph and tests it here.
|
||||
/// `FontSet.hasFishyAdvance`: an advance under 0 or over 32. Such a glyph is still the one drawn
|
||||
/// (`SelectedGlyphs.any` is the first provider's); only `SelectedGlyphs.nonFishy`, what obfuscated
|
||||
/// text draws, moves on to the next provider that has the character. Packs rely on it: a `space`
|
||||
/// provider's kerning runs from -512 to 512, and a tall bitmap's glyphs pass 32. Horizon keeps one
|
||||
/// advance per glyph and tests it here.
|
||||
fn fishy_advance(advance: f32) -> bool {
|
||||
!(0.0..=32.0).contains(&advance)
|
||||
}
|
||||
@@ -674,6 +678,7 @@ impl Font {
|
||||
FontSet {
|
||||
providers,
|
||||
glyphs: Default::default(),
|
||||
non_fishy: Default::default(),
|
||||
by_width: None,
|
||||
}
|
||||
}
|
||||
@@ -682,6 +687,7 @@ impl Font {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
for (_, set) in &mut cache.sets {
|
||||
set.glyphs.clear();
|
||||
set.non_fishy.clear();
|
||||
set.by_width = None;
|
||||
}
|
||||
cache.region.cursor = (0, 0);
|
||||
@@ -735,7 +741,7 @@ impl Font {
|
||||
return *g;
|
||||
}
|
||||
let was_full = std::mem::replace(&mut region.full, false);
|
||||
let found = self.resolve(set, region, c);
|
||||
let found = self.resolve(set, region, c, false);
|
||||
// No room: the missing glyph for now, not remembered, so the
|
||||
// character is tried again once `begin_frame` cleared the region.
|
||||
let overflowed = std::mem::replace(&mut region.full, was_full);
|
||||
@@ -747,13 +753,46 @@ impl Font {
|
||||
set.glyphs.insert(c, glyph);
|
||||
glyph
|
||||
}
|
||||
fn resolve(&self, set: &mut FontSet, region: &mut Region, c: char) -> Option<Glyph> {
|
||||
/// `FontSet.getRandomGlyph`'s `SelectedGlyphs.nonFishy`: the glyph of the first provider whose
|
||||
/// advance is not fishy, else the missing glyph. A character whose first glyph is fine has
|
||||
/// the same one for both.
|
||||
fn glyph_non_fishy(&self, id: &str, c: char) -> Glyph {
|
||||
let any = self.glyph_of(id, c);
|
||||
if !fishy_advance(any.advance) {
|
||||
return any;
|
||||
}
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let Cache { sets, region, .. } = &mut *cache;
|
||||
let Some((_, set)) = sets.iter_mut().find(|(k, _)| k == id) else {
|
||||
return self.missing;
|
||||
};
|
||||
if let Some(g) = set.non_fishy.get(&c) {
|
||||
return *g;
|
||||
}
|
||||
let was_full = std::mem::replace(&mut region.full, false);
|
||||
let found = self.resolve(set, region, c, true);
|
||||
let overflowed = std::mem::replace(&mut region.full, was_full);
|
||||
region.full |= overflowed;
|
||||
if overflowed {
|
||||
return self.missing;
|
||||
}
|
||||
let glyph = found.unwrap_or(self.missing);
|
||||
set.non_fishy.insert(c, glyph);
|
||||
glyph
|
||||
}
|
||||
fn resolve(
|
||||
&self,
|
||||
set: &mut FontSet,
|
||||
region: &mut Region,
|
||||
c: char,
|
||||
non_fishy: bool,
|
||||
) -> Option<Glyph> {
|
||||
let (uniform, japanese) = (self.uniform, self.japanese);
|
||||
for provider in &mut set.providers {
|
||||
match provider {
|
||||
Provider::Space(advances) => {
|
||||
if let Some(a) = advances.get(&c)
|
||||
&& !fishy_advance(*a)
|
||||
&& !(non_fishy && fishy_advance(*a))
|
||||
{
|
||||
return Some(Glyph {
|
||||
rect: None,
|
||||
@@ -768,10 +807,12 @@ impl Font {
|
||||
}
|
||||
Provider::Bitmap(filter, glyphs) if filter.passes(uniform, japanese) => {
|
||||
match glyphs.get(&c) {
|
||||
Some(BitmapGlyph::Atlas(g)) if !fishy_advance(g.advance) => {
|
||||
Some(BitmapGlyph::Atlas(g)) if !(non_fishy && fishy_advance(g.advance)) => {
|
||||
return Some(*g);
|
||||
}
|
||||
Some(BitmapGlyph::Sheet(cell)) if !fishy_advance(cell.glyph.advance) => {
|
||||
Some(BitmapGlyph::Sheet(cell))
|
||||
if !(non_fishy && fishy_advance(cell.glyph.advance)) =>
|
||||
{
|
||||
let (stride, pixels) = (&cell.sheet.0, &cell.sheet.1);
|
||||
let mut rgba = Vec::with_capacity((cell.w * cell.h * 4) as usize);
|
||||
for y in cell.y..cell.y + cell.h {
|
||||
@@ -791,14 +832,14 @@ impl Font {
|
||||
Provider::Unihex(filter, def, glyphs) if filter.passes(uniform, japanese) => {
|
||||
let glyphs = glyphs.get_or_insert_with(|| load_hex(&self.root, def));
|
||||
if let Some(h) = glyphs.get(&u32::from(c)).copied()
|
||||
&& !fishy_advance(hex_advance(&h))
|
||||
&& !(non_fishy && fishy_advance(hex_advance(&h)))
|
||||
{
|
||||
return self.rasterize_hex(region, h);
|
||||
}
|
||||
}
|
||||
Provider::TrueType(filter, ttf) if filter.passes(uniform, japanese) => {
|
||||
if let Some(id) = ttf.ids.get(&c)
|
||||
&& !fishy_advance(ttf.advance(*id))
|
||||
&& !(non_fishy && fishy_advance(ttf.advance(*id)))
|
||||
{
|
||||
let (glyph, bitmap) = ttf.glyph(*id);
|
||||
return match bitmap {
|
||||
@@ -851,7 +892,7 @@ impl Font {
|
||||
pub fn random_glyph(&self, style: &Style, advance: f32) -> Glyph {
|
||||
use horizon_core::random::RandomSource;
|
||||
let id = style.font.as_deref().unwrap_or(DEFAULT);
|
||||
let width = advance.ceil() as u32;
|
||||
let width = advance.ceil() as i32;
|
||||
let pick = {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let Cache { sets, random, .. } = &mut *cache;
|
||||
@@ -869,9 +910,9 @@ impl Font {
|
||||
chars.get(i as usize).copied()
|
||||
})
|
||||
};
|
||||
pick.map_or(self.missing, |c| self.glyph_of(id, c))
|
||||
pick.map_or(self.missing, |c| self.glyph_non_fishy(id, c))
|
||||
}
|
||||
fn glyphs_by_width(&self, set: &mut FontSet) -> HashMap<u32, Vec<char>> {
|
||||
fn glyphs_by_width(&self, set: &mut FontSet) -> HashMap<i32, Vec<char>> {
|
||||
let (uniform, japanese) = (self.uniform, self.japanese);
|
||||
let mut chars = std::collections::BTreeSet::new();
|
||||
let mut in_atlas = false;
|
||||
@@ -893,33 +934,31 @@ impl Font {
|
||||
// Nothing in the atlas: the glyphs below U+0250, rasterized as used.
|
||||
chars.extend((0..0x250u32).filter_map(char::from_u32));
|
||||
}
|
||||
let mut out: HashMap<u32, Vec<char>> = HashMap::new();
|
||||
let mut out: HashMap<i32, Vec<char>> = HashMap::new();
|
||||
for c in chars {
|
||||
if let Some(advance) = self.provider_advance(set, c) {
|
||||
out.entry(advance.ceil() as u32).or_default().push(c);
|
||||
out.entry(advance.ceil() as i32).or_default().push(c);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
/// A character's advance in `set` (its first provider that has it),
|
||||
/// without rasterizing it.
|
||||
/// A character's advance in `set` (its first provider that has it, fishy or not, as the
|
||||
/// by-width table of `FontSet.reload` takes it), without rasterizing it.
|
||||
fn provider_advance(&self, set: &mut FontSet, c: char) -> Option<f32> {
|
||||
let (uniform, japanese) = (self.uniform, self.japanese);
|
||||
for provider in &mut set.providers {
|
||||
match provider {
|
||||
Provider::Space(advances) => {
|
||||
if let Some(a) = advances.get(&c)
|
||||
&& !fishy_advance(*a)
|
||||
{
|
||||
if let Some(a) = advances.get(&c) {
|
||||
return Some(*a);
|
||||
}
|
||||
}
|
||||
Provider::Bitmap(filter, glyphs) if filter.passes(uniform, japanese) => {
|
||||
match glyphs.get(&c) {
|
||||
Some(BitmapGlyph::Atlas(g)) if !fishy_advance(g.advance) => {
|
||||
Some(BitmapGlyph::Atlas(g)) => {
|
||||
return Some(g.advance);
|
||||
}
|
||||
Some(BitmapGlyph::Sheet(cell)) if !fishy_advance(cell.glyph.advance) => {
|
||||
Some(BitmapGlyph::Sheet(cell)) => {
|
||||
return Some(cell.glyph.advance);
|
||||
}
|
||||
_ => {}
|
||||
@@ -927,16 +966,12 @@ impl Font {
|
||||
}
|
||||
Provider::Unihex(filter, def, glyphs) if filter.passes(uniform, japanese) => {
|
||||
let glyphs = glyphs.get_or_insert_with(|| load_hex(&self.root, def));
|
||||
if let Some(h) = glyphs.get(&u32::from(c))
|
||||
&& !fishy_advance(hex_advance(h))
|
||||
{
|
||||
if let Some(h) = glyphs.get(&u32::from(c)) {
|
||||
return Some(hex_advance(h));
|
||||
}
|
||||
}
|
||||
Provider::TrueType(filter, ttf) if filter.passes(uniform, japanese) => {
|
||||
if let Some(id) = ttf.ids.get(&c)
|
||||
&& !fishy_advance(ttf.advance(*id))
|
||||
{
|
||||
if let Some(id) = ttf.ids.get(&c) {
|
||||
return Some(ttf.advance(*id));
|
||||
}
|
||||
}
|
||||
@@ -1406,12 +1441,13 @@ mod tests {
|
||||
assert!(!font.take_patches().is_empty());
|
||||
}
|
||||
|
||||
/// `FontSet.computeGlyphInfo`: a provider glyph with an advance outside
|
||||
/// 0..32 is skipped, so the next provider that has the character is used.
|
||||
/// `FontSet.computeGlyphInfo`: `SelectedGlyphs.any` is the first provider's glyph, fishy
|
||||
/// advance or not, and is what is drawn and measured; `nonFishy`, what obfuscated text draws,
|
||||
/// is the first provider whose advance is within 0..32, else the missing glyph.
|
||||
#[test]
|
||||
fn a_fishy_advance_falls_through_to_the_next_provider() {
|
||||
// 64 opaque columns: the first provider's 'a' would advance 65, past
|
||||
// the 32 vanilla allows, so the narrow provider's 'a' is the one used.
|
||||
fn a_fishy_glyph_is_drawn_and_only_obfuscated_text_falls_through() {
|
||||
// 64 opaque columns: the first provider's 'a' advances 65, past the 32 vanilla
|
||||
// calls fishy, and the narrow provider's 'a' advances 3.
|
||||
let wide = png(64, 8, |_, _| true);
|
||||
let narrow = png(16, 8, |x, _| x < 2);
|
||||
let def = br#"{"providers":[{"type":"bitmap","file":"minecraft:font/wide.png","ascent":7,"chars":["a"]},{"type":"bitmap","file":"minecraft:font/narrow.png","ascent":7,"chars":["a"]}]}"#;
|
||||
@@ -1425,11 +1461,80 @@ mod tests {
|
||||
);
|
||||
let font = load(&root);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
assert_eq!(font.glyph('a').advance, 3.0, "the narrow provider wins");
|
||||
assert_eq!(font.advance('a', &Style::default()), 3.0);
|
||||
// Obfuscated text picks a glyph by its own advance: nothing is 65 wide.
|
||||
let obfuscated = font.random_glyph(&Style::default(), 65.0);
|
||||
assert_eq!(obfuscated.advance, 6.0, "the missing glyph");
|
||||
let drawn = font.glyph('a');
|
||||
assert_eq!(drawn.advance, 65.0, "the first provider's glyph is drawn");
|
||||
assert!(drawn.rect.is_some());
|
||||
assert_eq!(font.advance('a', &Style::default()), 65.0);
|
||||
// The by-width table files 'a' under its first glyph's 65; the glyph it then draws is
|
||||
// the narrow provider's (`nonFishy`).
|
||||
assert_eq!(font.random_glyph(&Style::default(), 65.0).advance, 3.0);
|
||||
// Nothing else is 3 wide in the table, and the missing glyph is 6.
|
||||
assert_eq!(font.random_glyph(&Style::default(), 3.0).advance, 6.0);
|
||||
}
|
||||
|
||||
/// A `space` provider's shift glyphs (kerning in server packs: -512 to 512) are drawn and
|
||||
/// measured as they are, and `nonFishy` has nothing else to draw for them.
|
||||
#[test]
|
||||
fn space_glyphs_with_any_advance_are_kept() {
|
||||
let shifts: Vec<i32> = (0..10)
|
||||
.map(|i| -(1 << i))
|
||||
.chain((0..10).map(|i| 1 << i))
|
||||
.collect();
|
||||
let advances: Vec<String> = shifts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| format!("\"\\ue{:03x}\": {a}", i))
|
||||
.collect();
|
||||
let def = format!(
|
||||
r#"{{"providers":[{{"type":"space","advances":{{{}}}}}]}}"#,
|
||||
advances.join(",")
|
||||
);
|
||||
let root = root(
|
||||
"space-shifts",
|
||||
&[("assets/minecraft/font/default.json", def.as_bytes())],
|
||||
);
|
||||
let font = load(&root);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
for (i, a) in shifts.iter().enumerate() {
|
||||
let c = char::from_u32(0xe000 + i as u32).unwrap();
|
||||
assert_eq!(font.glyph(c).advance, *a as f32, "{c:?}");
|
||||
assert_eq!(font.advance(c, &Style::default()), *a as f32);
|
||||
}
|
||||
// Obfuscated text asks for a glyph of a given width: a fishy space has none to give.
|
||||
let c = char::from_u32(0xe000 + 19).unwrap();
|
||||
assert_eq!(font.glyph(c).advance, 512.0);
|
||||
assert_eq!(
|
||||
font.glyph_non_fishy(DEFAULT, c).advance,
|
||||
6.0,
|
||||
"the missing glyph"
|
||||
);
|
||||
assert_eq!(font.random_glyph(&Style::default(), 512.0).advance, 6.0);
|
||||
}
|
||||
|
||||
/// Nexo's GUI glyphs: a bitmap `height` of 287 with no advance, so every glyph wider than 31
|
||||
/// pixels has a fishy advance, and is still drawn.
|
||||
#[test]
|
||||
fn a_wide_bitmap_glyph_is_drawn() {
|
||||
let sheet = png(40, 287, |_, _| true);
|
||||
let def = br#"{"providers":[{"type":"bitmap","file":"minecraft:font/gui.png","height":287,"ascent":65,"chars":["a"]}]}"#;
|
||||
let root = root(
|
||||
"wide-bitmap",
|
||||
&[
|
||||
("assets/minecraft/font/default.json", def),
|
||||
("assets/minecraft/textures/font/gui.png", &sheet),
|
||||
],
|
||||
);
|
||||
let font = load(&root);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let g = font.glyph('a');
|
||||
assert_eq!(g.advance, 41.0, "the glyph's 40 columns and one");
|
||||
assert!(g.rect.is_some(), "drawn, not the missing glyph");
|
||||
assert!(fishy_advance(g.advance));
|
||||
assert_eq!(
|
||||
font.glyph_non_fishy(DEFAULT, 'a').advance,
|
||||
6.0,
|
||||
"excluded from what obfuscated text draws"
|
||||
);
|
||||
}
|
||||
|
||||
/// StringDecomposer.iterateFormatted: a `§` that ends a part stops that
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -877,6 +877,8 @@ mod tests {
|
||||
start,
|
||||
translucent_at,
|
||||
solid,
|
||||
solid_slots: u64::from(solid) * 4,
|
||||
packed: false,
|
||||
sides: crate::mesher::Sides::unsorted(solid),
|
||||
translucent,
|
||||
sort: super::super::translucency::SortState::new(Vec::new()),
|
||||
@@ -930,11 +932,12 @@ mod tests {
|
||||
.iter()
|
||||
.map(|s| (s.page, s.start, s.translucent_at))
|
||||
.collect();
|
||||
// The sections with solid quads have records, on page 0.
|
||||
// The sections with solid quads have records, on page 0, each with
|
||||
// its cell's box.
|
||||
let mut records = gpu_culling::Records::default();
|
||||
for (i, s) in sections.iter_mut().enumerate() {
|
||||
if s.solid > 0 {
|
||||
s.gpu_id = Some(records.insert((i as i32, 0, 0), s.page, s.start, s.solid));
|
||||
s.gpu_id = Some(records.insert(s.record((i as i32, 0, 0), None)));
|
||||
}
|
||||
}
|
||||
let recorded = records.live() as u32;
|
||||
@@ -957,9 +960,8 @@ mod tests {
|
||||
let word =
|
||||
|(page, at): (u32, u64)| read(&device, &queue, arena.buffer(page), at * stride);
|
||||
for (i, s) in sections.iter().enumerate() {
|
||||
// Its record names its solid quads where they are now.
|
||||
let record = (s.solid > 0)
|
||||
.then(|| gpu_culling::Record::new((i as i32, 0, 0), s.page, s.start, s.solid));
|
||||
// Its record names its solid quads where they are now, its box kept.
|
||||
let record = (s.solid > 0).then(|| s.record((i as i32, 0, 0), None));
|
||||
let held = s.gpu_id.and_then(|id| records.at(id));
|
||||
assert_eq!(held, record.as_ref(), "split {split}, section {i}");
|
||||
let Some((page, start, len)) = s.vertex_ranges(split).0 else {
|
||||
@@ -1014,7 +1016,7 @@ mod tests {
|
||||
label: None,
|
||||
entries: &[],
|
||||
});
|
||||
let pipeline = |layout: &wgpu::PipelineLayout, label: &str, _: &str, _: &str, _, _| {
|
||||
let pipeline = |layout: &wgpu::PipelineLayout, label: &str, _: &str, _: &str, _, _, _| {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(layout),
|
||||
@@ -1071,6 +1073,8 @@ mod tests {
|
||||
start: 0,
|
||||
translucent_at: (0, at),
|
||||
solid: 64,
|
||||
solid_slots: 64 * 4,
|
||||
packed: false,
|
||||
sides: crate::mesher::Sides::unsorted(64),
|
||||
translucent: 128,
|
||||
sort: super::super::translucency::SortState::new(Vec::new()),
|
||||
@@ -1295,6 +1299,8 @@ mod tests {
|
||||
start: 0,
|
||||
translucent_at: if indices { (7, 7) } else { at },
|
||||
solid,
|
||||
solid_slots: u64::from(solid) * 4,
|
||||
packed: false,
|
||||
sides: crate::mesher::Sides::unsorted(solid),
|
||||
translucent,
|
||||
sort: super::super::translucency::SortState::new(Vec::new()),
|
||||
|
||||
@@ -0,0 +1,574 @@
|
||||
//! `horizon-ctl render-capture`: what the renderer needs to draw the current frame again
|
||||
//! without the game, for `examples/scene_replay.rs` (docs/graphics.md, "Render capture and
|
||||
//! replay").
|
||||
//!
|
||||
//! The renderer keeps no CPU copy of the meshes it uploads, so while capturing is on
|
||||
//! (`Renderer::keep_capture_copies`) it keeps one of every section and far tile mesh as it is
|
||||
//! handed over, at the mesher-to-renderer seam. A capture writes those of the sections and
|
||||
//! tiles it holds, the frame's camera and inputs, and the settings that change GPU work. Only
|
||||
//! the build that wrote a capture reads it.
|
||||
//!
|
||||
//! Files: `scene.json` (`Scene`), `sections.json` and `tiles.json` (one `MeshRecord` per mesh,
|
||||
//! in order), `sections.bin` and `tiles.bin` (each mesh's solid then translucent
|
||||
//! `TerrainVertex`es), `celestial.bin` (the sun and moon `Vertex`es).
|
||||
use super::{Frame, Renderer, SectionKey};
|
||||
use crate::{
|
||||
Result,
|
||||
environment::Sky,
|
||||
fast_hash::{FxHashMap, FxHashSet},
|
||||
fog::FogData,
|
||||
graphics::GraphicsApi,
|
||||
hud::HudBatch,
|
||||
lod::TileKey,
|
||||
mesher::{SectionMesh, Sides, TerrainVertex, Vertex},
|
||||
};
|
||||
use glam::{DVec3, Mat4, Vec3};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{BufReader, BufWriter, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// CPU copies of the meshes uploaded since capturing was turned on.
|
||||
#[derive(Default)]
|
||||
pub(super) struct Copies {
|
||||
sections: FxHashMap<SectionKey, SectionMesh>,
|
||||
tiles: FxHashMap<TileKey, SectionMesh>,
|
||||
}
|
||||
|
||||
impl Copies {
|
||||
pub(super) fn section(&mut self, key: SectionKey, mesh: &SectionMesh) {
|
||||
self.sections.insert(key, copy(mesh));
|
||||
}
|
||||
/// Columns unloaded (`Renderer::remove_columns`).
|
||||
pub(super) fn remove_columns(&mut self, columns: &[(i32, i32)]) {
|
||||
if columns.is_empty() {
|
||||
return;
|
||||
}
|
||||
let gone: FxHashSet<(i32, i32)> = columns.iter().copied().collect();
|
||||
self.sections
|
||||
.retain(|&(x, _, z), _| !gone.contains(&(x, z)));
|
||||
}
|
||||
pub(super) fn tile(&mut self, key: TileKey, mesh: &SectionMesh) {
|
||||
self.tiles.insert(key, copy(mesh));
|
||||
}
|
||||
pub(super) fn forget_tile(&mut self, key: TileKey) {
|
||||
self.tiles.remove(&key);
|
||||
}
|
||||
pub(super) fn clear_tiles(&mut self) {
|
||||
self.tiles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn copy(mesh: &SectionMesh) -> SectionMesh {
|
||||
SectionMesh {
|
||||
solid: mesh.solid.clone(),
|
||||
sides: mesh.sides,
|
||||
translucent: mesh.translucent.clone(),
|
||||
packed: mesh.packed.clone(),
|
||||
solid_tints: Vec::new(),
|
||||
visibility: mesh.visibility,
|
||||
block_entities: mesh.block_entities.clone(),
|
||||
lights: mesh.lights.clone(),
|
||||
sprite_layers: mesh.sprite_layers.clone(),
|
||||
bounds: mesh.bounds,
|
||||
keep: mesh.keep,
|
||||
}
|
||||
}
|
||||
|
||||
/// A capture asked for: where to write it, and the game's settings the renderer does not hold.
|
||||
pub struct Request {
|
||||
pub dir: PathBuf,
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
/// The game's settings that change GPU work, as the game applied them to the renderer.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
/// `GraphicsApi`'s variant name.
|
||||
pub graphics_api: String,
|
||||
pub translucent_batching: bool,
|
||||
/// `textureFiltering` and `maxAnisotropyBit` (`Renderer::set_texture_filtering`).
|
||||
pub texture_filtering: i32,
|
||||
pub anisotropy_bit: i32,
|
||||
pub mipmap_levels: u32,
|
||||
/// `Renderer::set_world_post`'s arguments.
|
||||
pub resolution_scale: f32,
|
||||
pub fxaa: bool,
|
||||
pub taa: bool,
|
||||
pub upscale: f32,
|
||||
/// Chunks.
|
||||
pub render_distance: i32,
|
||||
pub lod_distance: u32,
|
||||
/// The game's prepared resources, where the replay loads the block sprites from.
|
||||
pub resources: PathBuf,
|
||||
/// The block sprites the meshes were made against.
|
||||
pub atlas: AtlasCheck,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn graphics_api(&self) -> GraphicsApi {
|
||||
GraphicsApi::ALL
|
||||
.into_iter()
|
||||
.find(|api| format!("{api:?}") == self.graphics_api)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Which block sprite layers the meshes' vertices name: the replay's must be the same.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AtlasCheck {
|
||||
pub sprite_size: u32,
|
||||
pub texture_layers: u32,
|
||||
/// FNV-1a of every sprite's name and layer.
|
||||
pub layers: u64,
|
||||
}
|
||||
|
||||
impl AtlasCheck {
|
||||
pub fn of(render: &crate::assets::RenderAssets) -> Self {
|
||||
let mut hash = 0xcbf2_9ce4_8422_2325u64;
|
||||
for (name, layer) in &render.sprite_layers {
|
||||
for byte in name.bytes().chain(layer.to_le_bytes()) {
|
||||
hash = (hash ^ u64::from(byte)).wrapping_mul(0x0000_0100_0000_01b3);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
sprite_size: render.sprite_size,
|
||||
texture_layers: render.texture_layers,
|
||||
layers: hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The renderer's own state that changes GPU work.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RendererState {
|
||||
pub adapter: String,
|
||||
pub backend: String,
|
||||
/// The frame's size in pixels.
|
||||
pub size: [u32; 2],
|
||||
/// The column layout: lowest block y and sections.
|
||||
pub min_y: i32,
|
||||
pub sections: usize,
|
||||
pub flat_draws: bool,
|
||||
pub section_cull: bool,
|
||||
pub section_sides: bool,
|
||||
pub gpu_culling: bool,
|
||||
pub gamma: f32,
|
||||
pub fullbright: bool,
|
||||
pub improved_transparency: bool,
|
||||
pub animated_textures: bool,
|
||||
/// A shader pack was running; the replay draws without it.
|
||||
pub shader_pack: bool,
|
||||
}
|
||||
|
||||
/// The cloud inputs of the frame (`clouds::Input`), with the clouds texture.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Clouds {
|
||||
pub texture: (u32, u32, Vec<bool>),
|
||||
pub uv: [f32; 2],
|
||||
pub camera: [f64; 3],
|
||||
pub time: i64,
|
||||
pub partial_tick: f64,
|
||||
pub range: i32,
|
||||
pub fast: bool,
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
/// The frame's camera and the inputs of its globals, sky, fog and lightmap.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct FrameInputs {
|
||||
pub camera: [f64; 3],
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub fov_degrees: f32,
|
||||
pub eye: [f64; 3],
|
||||
pub eye_rotation: [f32; 2],
|
||||
pub first_person: bool,
|
||||
pub bob: [f32; 16],
|
||||
pub spin: [f32; 16],
|
||||
pub sky: Sky,
|
||||
pub distance: f32,
|
||||
pub detail_distance: f32,
|
||||
pub depth_far: f32,
|
||||
pub underwater: bool,
|
||||
pub water_plane: Option<f32>,
|
||||
pub cave_culling: bool,
|
||||
pub sky_blocked: bool,
|
||||
pub skybox: bool,
|
||||
pub end_sky: bool,
|
||||
pub end_sky_sprite: Option<[f32; 4]>,
|
||||
pub environment_fog: Option<(f32, f32)>,
|
||||
pub water_fog: [f32; 3],
|
||||
pub fog: Option<FogData>,
|
||||
pub dark_disc: bool,
|
||||
pub nether_lighting: bool,
|
||||
pub glint_alpha: f32,
|
||||
pub glint_ticks: f32,
|
||||
pub hand_fov_degrees: f32,
|
||||
pub stars: Option<([f32; 16], f32)>,
|
||||
pub clouds: Option<Clouds>,
|
||||
pub game_time: f32,
|
||||
pub shader_game_time: f32,
|
||||
pub end_portal_sprites: [[f32; 4]; 2],
|
||||
pub crumbling_sprite: [f32; 4],
|
||||
pub ticks: u64,
|
||||
pub darkness: (f32, f32),
|
||||
pub night_vision: f32,
|
||||
pub block_flicker: f32,
|
||||
pub boss_darken: f32,
|
||||
}
|
||||
|
||||
/// `scene.json`.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Scene {
|
||||
pub settings: Settings,
|
||||
pub renderer: RendererState,
|
||||
pub frame: FrameInputs,
|
||||
pub sections: usize,
|
||||
pub tiles: usize,
|
||||
/// The chunk columns far tiles leave out for full-detail sections.
|
||||
pub lod_cutout: Vec<(i32, i32)>,
|
||||
/// `celestial.bin`.
|
||||
#[serde(skip)]
|
||||
pub celestial: Vec<Vertex>,
|
||||
}
|
||||
|
||||
/// One mesh of `sections.json` or `tiles.json`; its vertices follow the previous mesh's in
|
||||
/// the `.bin` file. `key` is (x, section y, z) for a section, (x, z, level) for a tile.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct MeshRecord {
|
||||
key: [i32; 3],
|
||||
solid: usize,
|
||||
translucent: usize,
|
||||
sides: Sides,
|
||||
visibility: u64,
|
||||
block_entities: Vec<([i32; 3], u32)>,
|
||||
lights: Vec<([i32; 3], u32)>,
|
||||
sprite_layers: Vec<u16>,
|
||||
bounds: Option<[[f32; 3]; 2]>,
|
||||
keep: bool,
|
||||
}
|
||||
|
||||
/// Writes `name.json` and `name.bin`: (meshes, vertex bytes).
|
||||
fn write_meshes<'m>(
|
||||
dir: &Path,
|
||||
name: &str,
|
||||
meshes: impl Iterator<Item = ([i32; 3], &'m SectionMesh)>,
|
||||
) -> Result<(usize, u64)> {
|
||||
let mut blob = BufWriter::new(File::create(dir.join(format!("{name}.bin")))?);
|
||||
let mut records = Vec::new();
|
||||
let mut bytes = 0u64;
|
||||
for (key, mesh) in meshes {
|
||||
for part in [&mesh.solid, &mesh.translucent] {
|
||||
let raw: &[u8] = bytemuck::cast_slice(part.as_slice());
|
||||
blob.write_all(raw)?;
|
||||
bytes += raw.len() as u64;
|
||||
}
|
||||
records.push(MeshRecord {
|
||||
key,
|
||||
solid: mesh.solid.len(),
|
||||
translucent: mesh.translucent.len(),
|
||||
sides: mesh.sides,
|
||||
visibility: mesh.visibility,
|
||||
block_entities: mesh.block_entities.clone(),
|
||||
lights: mesh.lights.clone(),
|
||||
sprite_layers: mesh.sprite_layers.clone(),
|
||||
bounds: mesh
|
||||
.bounds
|
||||
.map(|[min, max]| [min.to_array(), max.to_array()]),
|
||||
keep: mesh.keep,
|
||||
});
|
||||
}
|
||||
blob.flush()?;
|
||||
let mut list = BufWriter::new(File::create(dir.join(format!("{name}.json")))?);
|
||||
serde_json::to_writer(&mut list, &records)?;
|
||||
list.flush()?;
|
||||
Ok((records.len(), bytes))
|
||||
}
|
||||
|
||||
/// Reads `name.json` and `name.bin`, handing each mesh to `each` as it is read.
|
||||
pub fn read_meshes(
|
||||
dir: &Path,
|
||||
name: &str,
|
||||
mut each: impl FnMut([i32; 3], SectionMesh),
|
||||
) -> Result<()> {
|
||||
let records: Vec<MeshRecord> = serde_json::from_reader(BufReader::new(File::open(
|
||||
dir.join(format!("{name}.json")),
|
||||
)?))?;
|
||||
let mut blob = BufReader::new(File::open(dir.join(format!("{name}.bin")))?);
|
||||
for record in records {
|
||||
let mut solid = vec![<TerrainVertex as bytemuck::Zeroable>::zeroed(); record.solid];
|
||||
blob.read_exact(bytemuck::cast_slice_mut::<TerrainVertex, u8>(
|
||||
solid.as_mut_slice(),
|
||||
))?;
|
||||
let mut translucent =
|
||||
vec![<TerrainVertex as bytemuck::Zeroable>::zeroed(); record.translucent];
|
||||
blob.read_exact(bytemuck::cast_slice_mut::<TerrainVertex, u8>(
|
||||
translucent.as_mut_slice(),
|
||||
))?;
|
||||
each(
|
||||
record.key,
|
||||
SectionMesh {
|
||||
solid,
|
||||
sides: record.sides,
|
||||
translucent,
|
||||
visibility: record.visibility,
|
||||
block_entities: record.block_entities,
|
||||
lights: record.lights,
|
||||
sprite_layers: record.sprite_layers,
|
||||
bounds: record
|
||||
.bounds
|
||||
.map(|[min, max]| [Vec3::from_array(min), Vec3::from_array(max)]),
|
||||
keep: record.keep,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Keeps CPU copies of the meshes uploaded from now on, for `render-capture`, or drops
|
||||
/// them. They start empty: the caller meshes everything again for a complete capture.
|
||||
pub fn keep_capture_copies(&mut self, on: bool) {
|
||||
if on != self.capture_copies.is_some() {
|
||||
self.capture_copies = on.then(Copies::default);
|
||||
}
|
||||
}
|
||||
pub fn keeps_capture_copies(&self) -> bool {
|
||||
self.capture_copies.is_some()
|
||||
}
|
||||
|
||||
/// Writes the scene `frame` draws to `request.dir` (`render` calls it for `capture`).
|
||||
pub(super) fn write_capture(
|
||||
&self,
|
||||
request: &Request,
|
||||
frame: &Frame,
|
||||
) -> Result<serde_json::Value> {
|
||||
let copies = self
|
||||
.capture_copies
|
||||
.as_ref()
|
||||
.ok_or("no mesh copies kept: render-capture on first")?;
|
||||
let dir = &request.dir;
|
||||
fs::create_dir_all(dir)?;
|
||||
let (sections, section_bytes) = write_meshes(
|
||||
dir,
|
||||
"sections",
|
||||
copies
|
||||
.sections
|
||||
.iter()
|
||||
.map(|(&(x, y, z), mesh)| ([x, y, z], mesh)),
|
||||
)?;
|
||||
let standing = self.standing_lods();
|
||||
let (tiles, tile_bytes) = write_meshes(
|
||||
dir,
|
||||
"tiles",
|
||||
copies
|
||||
.tiles
|
||||
.iter()
|
||||
.filter(|(key, _)| standing.contains(*key))
|
||||
.map(|(key, mesh)| ([key.x, key.z, i32::from(key.level)], mesh)),
|
||||
)?;
|
||||
fs::write(
|
||||
dir.join("celestial.bin"),
|
||||
bytemuck::cast_slice::<Vertex, u8>(frame.celestial),
|
||||
)?;
|
||||
let scene = Scene {
|
||||
settings: request.settings.clone(),
|
||||
renderer: RendererState {
|
||||
adapter: self.info.name.clone(),
|
||||
backend: format!("{:?}", self.info.backend),
|
||||
size: [self.config.width, self.config.height],
|
||||
min_y: self.layout.min_y(),
|
||||
sections: self.layout.sections(),
|
||||
flat_draws: self.flat_draws(),
|
||||
section_cull: self.section_cull(),
|
||||
section_sides: self.section_sides(),
|
||||
gpu_culling: self.gpu_culling(),
|
||||
gamma: self.gamma,
|
||||
fullbright: self.fullbright,
|
||||
improved_transparency: self.improved_transparency,
|
||||
animated_textures: self.animated_textures,
|
||||
shader_pack: self.pack_post.is_some() || self.native.is_some(),
|
||||
},
|
||||
frame: FrameInputs {
|
||||
camera: frame.camera.to_array(),
|
||||
yaw: frame.yaw,
|
||||
pitch: frame.pitch,
|
||||
fov_degrees: frame.fov_degrees,
|
||||
eye: frame.eye.to_array(),
|
||||
eye_rotation: frame.eye_rotation,
|
||||
first_person: frame.first_person,
|
||||
bob: frame.bob.to_cols_array(),
|
||||
spin: frame.spin.to_cols_array(),
|
||||
sky: frame.sky,
|
||||
distance: frame.distance,
|
||||
detail_distance: frame.detail_distance,
|
||||
depth_far: frame.depth_far,
|
||||
underwater: frame.underwater,
|
||||
water_plane: frame.water_plane,
|
||||
cave_culling: frame.cave_culling,
|
||||
sky_blocked: frame.sky_blocked,
|
||||
skybox: frame.skybox,
|
||||
end_sky: frame.end_sky,
|
||||
end_sky_sprite: frame.end_sky_sprite,
|
||||
environment_fog: frame.environment_fog,
|
||||
water_fog: frame.water_fog,
|
||||
fog: frame.fog,
|
||||
dark_disc: frame.dark_disc,
|
||||
nether_lighting: frame.nether_lighting,
|
||||
glint_alpha: frame.glint_alpha,
|
||||
glint_ticks: frame.glint_ticks,
|
||||
hand_fov_degrees: frame.hand_fov_degrees,
|
||||
stars: frame
|
||||
.stars
|
||||
.map(|(rotation, brightness)| (rotation.to_cols_array(), brightness)),
|
||||
clouds: frame.clouds.as_ref().map(|c| Clouds {
|
||||
texture: c.texture.clone(),
|
||||
uv: c.uv,
|
||||
camera: c.camera.to_array(),
|
||||
time: c.time,
|
||||
partial_tick: c.partial_tick,
|
||||
range: c.range,
|
||||
fast: c.fast,
|
||||
color: c.color,
|
||||
}),
|
||||
game_time: frame.game_time,
|
||||
shader_game_time: frame.shader_game_time,
|
||||
end_portal_sprites: frame.end_portal_sprites,
|
||||
crumbling_sprite: frame.crumbling_sprite,
|
||||
ticks: frame.ticks,
|
||||
darkness: frame.darkness,
|
||||
night_vision: frame.night_vision,
|
||||
block_flicker: frame.block_flicker,
|
||||
boss_darken: frame.boss_darken,
|
||||
},
|
||||
sections,
|
||||
tiles,
|
||||
lod_cutout: self.lod_cutout.iter().copied().collect(),
|
||||
celestial: Vec::new(),
|
||||
};
|
||||
let mut out = BufWriter::new(File::create(dir.join("scene.json"))?);
|
||||
serde_json::to_writer_pretty(&mut out, &scene)?;
|
||||
out.flush()?;
|
||||
Ok(serde_json::json!({
|
||||
"ok": true,
|
||||
"dir": dir.display().to_string(),
|
||||
"sections": sections,
|
||||
"tiles": tiles,
|
||||
"vertex_bytes": section_bytes + tile_bytes,
|
||||
"shader_pack_left_out": scene.renderer.shader_pack,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
/// Reads `scene.json` and `celestial.bin` (the meshes: `read_meshes`).
|
||||
pub fn read(dir: &Path) -> Result<Self> {
|
||||
let mut scene: Scene =
|
||||
serde_json::from_reader(BufReader::new(File::open(dir.join("scene.json"))?))?;
|
||||
let bytes = fs::read(dir.join("celestial.bin"))?;
|
||||
let size = std::mem::size_of::<Vertex>();
|
||||
let mut celestial = vec![<Vertex as bytemuck::Zeroable>::zeroed(); bytes.len() / size];
|
||||
let len = celestial.len() * size;
|
||||
bytemuck::cast_slice_mut::<Vertex, u8>(celestial.as_mut_slice())
|
||||
.copy_from_slice(&bytes[..len]);
|
||||
scene.celestial = celestial;
|
||||
Ok(scene)
|
||||
}
|
||||
|
||||
/// The captured frame: its camera, sky, fog, clouds, sun, moon and stars, without
|
||||
/// entities, particles, weather, the hand, the outline or the HUD.
|
||||
pub fn frame<'a>(&'a self, hud: &'a HudBatch) -> Frame<'a> {
|
||||
let f = &self.frame;
|
||||
Frame {
|
||||
weather: &[],
|
||||
weather_time: crate::weather::Time::new(0, 0.0),
|
||||
camera: DVec3::from_array(f.camera),
|
||||
yaw: f.yaw,
|
||||
pitch: f.pitch,
|
||||
fov_degrees: f.fov_degrees,
|
||||
eye: DVec3::from_array(f.eye),
|
||||
eye_rotation: f.eye_rotation,
|
||||
first_person: f.first_person,
|
||||
bob: Mat4::from_cols_array(&f.bob),
|
||||
spin: Mat4::from_cols_array(&f.spin),
|
||||
sky: f.sky,
|
||||
shadow_scene_revision: (0, 0),
|
||||
shadow_cache: false,
|
||||
distance: f.distance,
|
||||
detail_distance: f.detail_distance,
|
||||
depth_far: f.depth_far,
|
||||
underwater: f.underwater,
|
||||
water_plane: f.water_plane,
|
||||
cave_culling: f.cave_culling,
|
||||
title: false,
|
||||
sky_blocked: f.sky_blocked,
|
||||
skybox: f.skybox,
|
||||
end_sky: f.end_sky,
|
||||
end_sky_sprite: f.end_sky_sprite,
|
||||
environment_fog: f.environment_fog,
|
||||
water_fog: f.water_fog,
|
||||
fog: f.fog,
|
||||
dark_disc: f.dark_disc,
|
||||
outline: Vec::new(),
|
||||
outline_after_terrain: false,
|
||||
outline_high_contrast: false,
|
||||
debug_lines: &[],
|
||||
debug_lines_on_top: &[],
|
||||
debug_crosshair: &[],
|
||||
hud,
|
||||
glint_alpha: f.glint_alpha,
|
||||
glint_ticks: f.glint_ticks,
|
||||
nether_lighting: f.nether_lighting,
|
||||
entities: &[],
|
||||
static_block_entities: &[],
|
||||
visible_static_block_entity_sections: &[],
|
||||
static_block_entity_stats: Default::default(),
|
||||
entity_motion: &[],
|
||||
entity_spans: &[],
|
||||
skin: None,
|
||||
glowing: &[],
|
||||
dynamic_lights: &[],
|
||||
colored_lights: &[],
|
||||
emissive_layers: &[],
|
||||
clouds: f.clouds.as_ref().map(|c| crate::clouds::Input {
|
||||
texture: &c.texture,
|
||||
uv: c.uv,
|
||||
camera: DVec3::from_array(c.camera),
|
||||
time: c.time,
|
||||
partial_tick: c.partial_tick,
|
||||
range: c.range,
|
||||
fast: c.fast,
|
||||
color: c.color,
|
||||
}),
|
||||
lightning: Default::default(),
|
||||
additive: &[],
|
||||
see_through: &[],
|
||||
shadow_only: &[],
|
||||
celestial: &self.celestial,
|
||||
stars: f
|
||||
.stars
|
||||
.map(|(rotation, brightness)| (Mat4::from_cols_array(&rotation), brightness)),
|
||||
hand: &[],
|
||||
hand_fov_degrees: f.hand_fov_degrees,
|
||||
screen: &[],
|
||||
pack: Default::default(),
|
||||
border: None,
|
||||
game_time: f.game_time,
|
||||
shader_game_time: f.shader_game_time,
|
||||
end_portal_sprites: f.end_portal_sprites,
|
||||
crumbling_sprite: f.crumbling_sprite,
|
||||
ticks: f.ticks,
|
||||
darkness: f.darkness,
|
||||
night_vision: f.night_vision,
|
||||
block_flicker: f.block_flicker,
|
||||
boss_darken: f.boss_darken,
|
||||
chunk_fade: 0.0,
|
||||
post_effects: crate::post_chain::Request::none(),
|
||||
menu_blur: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
//! A resource pack's `core/rendertype_lines` shader on the block outline (`RenderTypes.LINES`,
|
||||
//! and `secondaryBlockOutline` under a high contrast one): an outline its fragment shader colours
|
||||
//! or animates. Without one the outline draws natively (`vs_line` in terrain.wgsl).
|
||||
|
||||
use glam::Mat4;
|
||||
use horizon_shaders::{CoreProgram, Glue};
|
||||
|
||||
/// The outline's edges (`pack_lines::layout`: start, end, ARGB colour and width per instance) as
|
||||
/// vanilla's lines: four vertices an edge, the start twice and the end twice, the side
|
||||
/// `gl_VertexIndex % 2`, as `vs_line` draws them.
|
||||
pub(crate) const GLUE: Glue = Glue {
|
||||
inputs: &[
|
||||
("Position", "vec3"),
|
||||
("Color", "vec4"),
|
||||
("Normal", "vec3"),
|
||||
("LineWidth", "float"),
|
||||
],
|
||||
prelude: "layout(location = 0) in vec3 hz_start;\n\
|
||||
layout(location = 1) in vec3 hz_end;\n\
|
||||
layout(location = 2) in vec4 hz_argb;\n\
|
||||
layout(location = 3) in float hz_width;",
|
||||
// ARGB in a little-endian u32 reads as B, G, R, A. ShapeRenderer's normal: the direction.
|
||||
setup: "Position = hz_start; if (int(gl_VertexIndex) >= 2) Position = hz_end; \
|
||||
Color = hz_argb.zyxw; Normal = normalize(hz_end - hz_start); LineWidth = hz_width;",
|
||||
// 26.3's ProjMat already has wgpu's reversed 0 to 1 depth (Projection: zZeroToOne).
|
||||
finish: "",
|
||||
vertex_id: "int(gl_VertexIndex)",
|
||||
};
|
||||
|
||||
/// Each uniform block's offset in the one buffer (`core_shader::BLOCKS` order).
|
||||
const BLOCK_OFFSETS: [u64; 4] = [0, 256, 512, 768];
|
||||
const BLOCK_SIZES: [u64; 4] = [48, 64, 160, 48];
|
||||
|
||||
pub(super) struct CoreLines {
|
||||
lines: wgpu::RenderPipeline,
|
||||
secondary: wgpu::RenderPipeline,
|
||||
group: wgpu::BindGroup,
|
||||
uniform: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl CoreLines {
|
||||
pub(super) fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
program: &CoreProgram,
|
||||
) -> Option<Self> {
|
||||
// A pack's shader is untrusted: the backend failing to compile it is an internal error.
|
||||
let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let internal = device.push_error_scope(wgpu::ErrorFilter::Internal);
|
||||
let module = |label, module: &naga::Module| {
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(label),
|
||||
source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module.clone())),
|
||||
})
|
||||
};
|
||||
let vertex = module("Resource pack lines (vertex)", &program.vertex);
|
||||
let fragment = module("Resource pack lines (fragment)", &program.fragment);
|
||||
// Vanilla's LINES pipeline has no samplers: a pack that reads one is not used. The
|
||||
// blocks' sizes are checked here too, not at the draw: a pack's own larger block fails
|
||||
// in this scope.
|
||||
let entries: Vec<wgpu::BindGroupLayoutEntry> = (0..4)
|
||||
.map(|binding| wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(BLOCK_SIZES[binding as usize]),
|
||||
},
|
||||
count: None,
|
||||
})
|
||||
.collect();
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Resource pack lines"),
|
||||
entries: &entries,
|
||||
});
|
||||
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Resource pack lines uniforms"),
|
||||
size: 1024,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let resources: Vec<wgpu::BindGroupEntry> = (0..4)
|
||||
.map(|i| wgpu::BindGroupEntry {
|
||||
binding: i as u32,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &uniform,
|
||||
offset: BLOCK_OFFSETS[i],
|
||||
size: wgpu::BufferSize::new(BLOCK_SIZES[i]),
|
||||
}),
|
||||
})
|
||||
.collect();
|
||||
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Resource pack lines"),
|
||||
layout: &layout,
|
||||
entries: &resources,
|
||||
});
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Resource pack lines"),
|
||||
bind_group_layouts: &[Some(&layout)],
|
||||
..Default::default()
|
||||
});
|
||||
// The built-in outline's state: a strip per edge, reversed-Z depth test, translucent.
|
||||
let pipeline = |label, depth_write| {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &vertex,
|
||||
entry_point: Some("main"),
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[Some(super::pack_lines::layout())],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: Some(depth_write),
|
||||
depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &fragment,
|
||||
entry_point: Some("main"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
let lines = pipeline("Resource pack block outline", true);
|
||||
let secondary = pipeline("Resource pack block outline secondary", false);
|
||||
let internal = pollster::block_on(internal.pop());
|
||||
if let Some(e) = internal.or(pollster::block_on(validation.pop())) {
|
||||
log::warn!("the resource pack's rendertype_lines shader is not used: {e}");
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
lines,
|
||||
secondary,
|
||||
group,
|
||||
uniform,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn uniform(&self) -> &wgpu::Buffer {
|
||||
&self.uniform
|
||||
}
|
||||
|
||||
/// Draw `edges` (instances of the outline buffer bound at slot 0) with the pack's shader:
|
||||
/// the secondary stroke without depth writes. The caller sets its own group 0 again after.
|
||||
pub(super) fn draw(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
secondary: bool,
|
||||
edges: std::ops::Range<u32>,
|
||||
) {
|
||||
pass.set_pipeline(if secondary {
|
||||
&self.secondary
|
||||
} else {
|
||||
&self.lines
|
||||
});
|
||||
pass.set_bind_group(0, &self.group, &[]);
|
||||
pass.draw(0..4, edges);
|
||||
}
|
||||
}
|
||||
|
||||
/// The std140 blocks: `Globals` (`post_chain::Globals::bytes`), `Projection` (`jitter`, the lens
|
||||
/// and the view bob `pose`, as vanilla's projection matrix carries it), `DynamicTransforms` (the
|
||||
/// camera's rotation with the PERSPECTIVE `layering` scale, colour modulator white) and `Fog`
|
||||
/// (`fog`: environmental start and end, render distance start and end, sky end, clouds end).
|
||||
/// `ProjMat · ModelViewMat` is `vs_line`'s `view_proj` with its layering scale.
|
||||
pub(super) fn uniforms(
|
||||
globals: [u8; 48],
|
||||
view: &super::View,
|
||||
pose: Mat4,
|
||||
jitter: Mat4,
|
||||
layering: f32,
|
||||
fog_color: [f32; 4],
|
||||
fog: [f32; 6],
|
||||
) -> Vec<u8> {
|
||||
let projection = jitter * view.projection * pose;
|
||||
let model_view = view.view * Mat4::from_scale(glam::Vec3::splat(layering));
|
||||
let mut data = vec![0f32; 256];
|
||||
data[64..80].copy_from_slice(&projection.to_cols_array());
|
||||
let transforms = 128;
|
||||
data[transforms..transforms + 16].copy_from_slice(&model_view.to_cols_array());
|
||||
data[transforms + 16..transforms + 32].copy_from_slice(&Mat4::IDENTITY.to_cols_array());
|
||||
data[transforms + 32..transforms + 36].fill(1.0);
|
||||
data[192..196].copy_from_slice(&fog_color);
|
||||
data[196..202].copy_from_slice(&fog);
|
||||
let mut bytes: Vec<u8> = bytemuck::cast_slice(&data).to_vec();
|
||||
bytes[..48].copy_from_slice(&globals);
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use glam::{Vec3, Vec4};
|
||||
|
||||
const INCLUDES: [(&str, &str); 3] = [
|
||||
(
|
||||
"minecraft:shaders/include/globals.glsl",
|
||||
"layout(std140) uniform Globals {\n ivec3 CameraBlockPos;\n float GlintAlpha;\n vec3 CameraOffset;\n float GameTime;\n vec2 ScreenSize;\n int MenuBlurRadius;\n int UseRgss;\n};\n",
|
||||
),
|
||||
(
|
||||
"minecraft:shaders/include/dynamictransforms.glsl",
|
||||
"layout(std140) uniform DynamicTransforms {\n mat4 ModelViewMat;\n mat4 TextureMat;\n vec4 ColorModulator;\n vec3 ModelOffset;\n};\n",
|
||||
),
|
||||
(
|
||||
"minecraft:shaders/include/projection.glsl",
|
||||
"layout(std140) uniform Projection {\n mat4 ProjMat;\n};\n",
|
||||
),
|
||||
];
|
||||
|
||||
fn include(id: &str) -> Option<String> {
|
||||
INCLUDES
|
||||
.iter()
|
||||
.find(|(path, _)| *path == id)
|
||||
.map(|(_, text)| (*text).to_owned())
|
||||
}
|
||||
|
||||
/// 26.3's `rendertype_lines.vsh` in shape: a stroke `LineWidth` pixels wide across the
|
||||
/// screen-space direction of `Normal`, its side from `gl_VertexIndex % 2`.
|
||||
const LINES_VSH: &str = "#version 330\n#extension GL_ARB_separate_shader_objects : require\n\
|
||||
#include <minecraft:globals.glsl>\n#include <minecraft:dynamictransforms.glsl>\n#include <minecraft:projection.glsl>\n\
|
||||
layout(location = 0) in vec3 Position;\nlayout(location = 1) in vec4 Color;\n\
|
||||
layout(location = 2) in vec3 Normal;\nlayout(location = 3) in float LineWidth;\n\
|
||||
layout(location = 2) out vec4 vertexColor;\n\
|
||||
const mat4 SHRINK = mat4(0.99, 0.0, 0.0, 0.0, 0.0, 0.99, 0.0, 0.0, 0.0, 0.0, 0.99, 0.0, 0.0, 0.0, 0.0, 1.0);\n\
|
||||
void main() {\n vec4 from = ProjMat * SHRINK * ModelViewMat * vec4(Position, 1.0);\n\
|
||||
vec4 to = ProjMat * SHRINK * ModelViewMat * vec4(Position + Normal, 1.0);\n\
|
||||
vec2 along = normalize((to.xy / to.w - from.xy / from.w) * ScreenSize);\n\
|
||||
vec2 side = vec2(-along.y, along.x) * LineWidth / ScreenSize;\n\
|
||||
if (gl_VertexIndex % 2 == 0) {\n side = -side;\n }\n\
|
||||
gl_Position = vec4(from.xy / from.w + side, from.z / from.w, 1.0) * from.w;\n\
|
||||
vertexColor = Color;\n}\n";
|
||||
|
||||
/// An RGB outline pack's fragment shader in shape: the colour from `GameTime`.
|
||||
const RGB_FSH: &str = "#version 330\n#extension GL_ARB_separate_shader_objects : require\n\
|
||||
#include <minecraft:globals.glsl>\n\
|
||||
layout(location = 2) in vec4 vertexColor;\nlayout(location = 0) out vec4 fragColor;\n\
|
||||
void main() {\n fragColor = vec4(GameTime, 1.0 - GameTime, vertexColor.b, 1.0);\n}\n";
|
||||
|
||||
/// ProjMat · ModelViewMat (with the vertex shader's shrink between them) is `vs_line`'s
|
||||
/// view_proj on the layered, shrunk position: the pack's outline sits where the built-in one
|
||||
/// does, view bob and TAA jitter included.
|
||||
#[test]
|
||||
fn the_pack_matrices_put_the_outline_where_vs_line_does() {
|
||||
let view = super::super::View::screen(37.0, -21.0, 70.0, 16.0 / 9.0, 256.0);
|
||||
let pose =
|
||||
Mat4::from_rotation_z(0.02) * Mat4::from_translation(Vec3::new(0.01, -0.03, 0.0));
|
||||
let jitter = Mat4::from_translation(Vec3::new(0.0004, -0.0002, 0.0));
|
||||
let layering = 1.0 - 1.0 / 4096.0;
|
||||
let bytes = uniforms([0; 48], &view, pose, jitter, layering, [0.0; 4], [0.0; 6]);
|
||||
let data: Vec<f32> = bytes
|
||||
.as_chunks()
|
||||
.0
|
||||
.iter()
|
||||
.map(|b| f32::from_le_bytes(*b))
|
||||
.collect();
|
||||
let projection = Mat4::from_cols_slice(&data[64..80]);
|
||||
let model_view = Mat4::from_cols_slice(&data[128..144]);
|
||||
assert_eq!(&data[160..164], &[1.0; 4], "ColorModulator");
|
||||
let shrink = 1.0 - 1.0 / 256.0;
|
||||
for p in [Vec3::new(1.5, -0.2, 3.0), Vec3::new(-4.0, 2.0, -0.5)] {
|
||||
let pack =
|
||||
projection * Mat4::from_scale(Vec3::splat(shrink)) * model_view * p.extend(1.0);
|
||||
let native = jitter * view.view_proj(pose) * (p * layering * shrink).extend(1.0);
|
||||
let error: Vec4 = (pack - native).abs();
|
||||
assert!(
|
||||
error.max_element() < 1e-5 * native.w.abs(),
|
||||
"{pack} {native}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn device() -> Option<(wgpu::Device, wgpu::Queue)> {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let adapter = pollster::block_on(instance.request_adapter(&Default::default())).ok()?;
|
||||
pollster::block_on(adapter.request_device(&Default::default())).ok()
|
||||
}
|
||||
|
||||
/// One edge across the middle of a 64 by 64 target, 4 pixels wide, blue (ARGB `ff0000ff`),
|
||||
/// at `GameTime` 0.25, drawn with `program`: the RGBA rows read back.
|
||||
fn draw_edge(device: &wgpu::Device, queue: &wgpu::Queue, program: &CoreProgram) -> Vec<u8> {
|
||||
const SIZE: u32 = 64;
|
||||
let lines = CoreLines::new(device, wgpu::TextureFormat::Rgba8Unorm, program)
|
||||
.expect("the pack's pipelines");
|
||||
let target = |format, usage| {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size: wgpu::Extent3d {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | usage,
|
||||
view_formats: &[],
|
||||
})
|
||||
};
|
||||
let color = target(
|
||||
wgpu::TextureFormat::Rgba8Unorm,
|
||||
wgpu::TextureUsages::COPY_SRC,
|
||||
);
|
||||
let depth = target(
|
||||
wgpu::TextureFormat::Depth32Float,
|
||||
wgpu::TextureUsages::empty(),
|
||||
);
|
||||
let view = super::super::View {
|
||||
view: Mat4::IDENTITY,
|
||||
projection: super::super::world_projection(70.0, 1.0, 64.0),
|
||||
aspect: 1.0,
|
||||
tan_half_fov: 35f32.to_radians().tan(),
|
||||
};
|
||||
let globals = crate::post_chain::Globals {
|
||||
game_time: 0.25,
|
||||
..Default::default()
|
||||
};
|
||||
let data = uniforms(
|
||||
globals.bytes([SIZE, SIZE], 0),
|
||||
&view,
|
||||
Mat4::IDENTITY,
|
||||
Mat4::IDENTITY,
|
||||
1.0,
|
||||
[0.0; 4],
|
||||
[0.0; 6],
|
||||
);
|
||||
queue.write_buffer(lines.uniform(), 0, &data);
|
||||
let mut edge = [-1.0, 0.0, -2.0, 1.0, 0.0, -2.0, 0.0, 4.0f32];
|
||||
edge[6] = f32::from_bits(0xff00_00ff);
|
||||
let edges = wgpu::util::DeviceExt::create_buffer_init(
|
||||
device,
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&edge),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
},
|
||||
);
|
||||
let read = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: u64::from(SIZE * SIZE * 4),
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let mut encoder = device.create_command_encoder(&Default::default());
|
||||
{
|
||||
let color_view = color.create_view(&Default::default());
|
||||
let depth_view = depth.create_view(&Default::default());
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &color_view,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(0.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
pass.set_vertex_buffer(0, edges.slice(..));
|
||||
lines.draw(&mut pass, false, 0..1);
|
||||
}
|
||||
encoder.copy_texture_to_buffer(
|
||||
color.as_image_copy(),
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &read,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(SIZE * 4),
|
||||
rows_per_image: Some(SIZE),
|
||||
},
|
||||
},
|
||||
color.size(),
|
||||
);
|
||||
queue.submit([encoder.finish()]);
|
||||
read.slice(..)
|
||||
.map_async(wgpu::MapMode::Read, |r| r.unwrap());
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
read.slice(..).get_mapped_range().unwrap().to_vec()
|
||||
}
|
||||
|
||||
/// The stroke's middle takes the pack's colour (`GameTime` red, its inverse green, the
|
||||
/// vertex colour's blue); a pixel far from it stays clear.
|
||||
fn assert_pack_coloured(pixels: &[u8]) {
|
||||
let at = |x: usize, y: usize| &pixels[(y * 64 + x) * 4..(y * 64 + x) * 4 + 4];
|
||||
for y in [31, 32] {
|
||||
let pixel = at(32, y);
|
||||
let want = [64, 191, 255, 255];
|
||||
assert!(
|
||||
pixel.iter().zip(want).all(|(&p, w)| p.abs_diff(w) <= 1),
|
||||
"row {y}: {pixel:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(at(32, 4), [0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pack_fragment_shader_colours_the_outline() {
|
||||
let Some((device, queue)) = device() else {
|
||||
eprintln!("no GPU: skipping");
|
||||
return;
|
||||
};
|
||||
let program = horizon_shaders::translate_core(LINES_VSH, RGB_FSH, &[], &include, &GLUE)
|
||||
.expect("the shaders translate");
|
||||
assert_pack_coloured(&draw_edge(&device, &queue, &program));
|
||||
}
|
||||
|
||||
/// A pack's own include with a bigger block than vanilla's (an older `DynamicTransforms`)
|
||||
/// is not used, and the outline draws natively, rather than failing at the draw.
|
||||
#[test]
|
||||
fn a_pack_block_bigger_than_vanillas_falls_back() {
|
||||
let Some((device, _)) = device() else {
|
||||
return;
|
||||
};
|
||||
let bigger = |id: &str| {
|
||||
include(id)
|
||||
.map(|text| text.replace("vec3 ModelOffset;", "vec3 ModelOffset;\n vec4 More;"))
|
||||
};
|
||||
let program = horizon_shaders::translate_core(LINES_VSH, RGB_FSH, &[], &bigger, &GLUE)
|
||||
.expect("the shaders translate");
|
||||
assert!(CoreLines::new(&device, wgpu::TextureFormat::Rgba8Unorm, &program).is_none());
|
||||
}
|
||||
|
||||
/// Vanilla's own `rendertype_lines.vsh` (from the prepared resources) under a pack's
|
||||
/// fragment shader.
|
||||
#[test]
|
||||
fn vanillas_lines_vertex_shader_takes_a_packs_fragment_shader() {
|
||||
let root = std::env::var_os("HORIZON_RESOURCES")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../.local/horizon-client")
|
||||
.join(horizon_core::VANILLA_VERSION)
|
||||
});
|
||||
let Ok(vertex) = std::fs::read_to_string(
|
||||
root.join("assets/minecraft/shaders/core/rendertype_lines.vsh"),
|
||||
) else {
|
||||
eprintln!(
|
||||
"skipping resource-backed lines shader check: {}",
|
||||
root.display()
|
||||
);
|
||||
return;
|
||||
};
|
||||
let include = |id: &str| {
|
||||
let (namespace, path) = id.split_once(':')?;
|
||||
std::fs::read_to_string(root.join(format!("assets/{namespace}/{path}"))).ok()
|
||||
};
|
||||
let program = horizon_shaders::translate_core(&vertex, RGB_FSH, &[], &include, &GLUE)
|
||||
.expect("vanilla's vertex shader translates");
|
||||
// A pack that replaces only the vertex shader runs vanilla's fragment shader.
|
||||
let fragment = include("minecraft:shaders/core/rendertype_lines.fsh").unwrap();
|
||||
let vanilla = horizon_shaders::translate_core(&vertex, &fragment, &[], &include, &GLUE)
|
||||
.expect("vanilla's fragment shader translates");
|
||||
let Some((device, queue)) = device() else {
|
||||
eprintln!("no GPU: skipping the draw");
|
||||
return;
|
||||
};
|
||||
assert_pack_coloured(&draw_edge(&device, &queue, &program));
|
||||
assert!(CoreLines::new(&device, wgpu::TextureFormat::Rgba8Unorm, &vanilla).is_some());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
//! the results only sorts sections between the phases: a stale result draws
|
||||
//! a hidden section in phase 1, or tests a section that is in sight.
|
||||
//!
|
||||
//! Far terrain tiles are tested the same way, with their meshes' own
|
||||
//! bounds for a box (grown by a block too, which keeps their faces off the
|
||||
//! box's), and resident slots of their own (`TILE`).
|
||||
//!
|
||||
//! Every solid draw is an entry of one of the two lists, so the CPU records
|
||||
//! as many draws as without culling: drivers that unroll an indirect
|
||||
//! multi-draw on the CPU (Mesa's on Intel Gen9) pay per entry. Phase 2's
|
||||
@@ -39,6 +43,11 @@ use std::sync::{
|
||||
};
|
||||
|
||||
const WORDS: u64 = 5 * 4;
|
||||
/// A tested section's box, around its origin: its cell grown by a block.
|
||||
pub const SECTION_BOX: [[f32; 3]; 2] = [[-1.0; 3], [17.0; 3]];
|
||||
/// The bit that marks a far tile's slot (`Renderer::tile_cull_slot`): tiles
|
||||
/// count their slots apart from the sections' resident ones.
|
||||
pub const TILE: u32 = 1 << 31;
|
||||
/// Phase 1's sections are each tested every `RETEST` frames.
|
||||
const RETEST: u32 = 8;
|
||||
/// Culling runs while the last result read back hid at least this share of
|
||||
@@ -63,6 +72,17 @@ const FREE: u8 = 0;
|
||||
const PENDING: u8 = 1;
|
||||
const MAPPED: u8 = 2;
|
||||
|
||||
/// One test, as `cull.wgsl` reads it: visible index and phase 2 draws
|
||||
/// (`place`), then its box's corners around the origin.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Test {
|
||||
data: [u32; 4],
|
||||
low: [f32; 4],
|
||||
high: [f32; 4],
|
||||
}
|
||||
const TEST_BYTES: u64 = std::mem::size_of::<Test>() as u64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Params {
|
||||
@@ -131,8 +151,10 @@ pub struct Cull {
|
||||
phase2_capacity: u64,
|
||||
tests: wgpu::Buffer,
|
||||
tests_capacity: u64,
|
||||
/// This frame's tests: visible index, first phase 2 draw, phase 2 draws.
|
||||
test_data: Vec<[u32; 4]>,
|
||||
/// This frame's tests: visible index, then its phase 2 draws in each
|
||||
/// solid pass (`place`): the first of the first pass's, both counts (the
|
||||
/// second's in the high half), the first of the second pass's; and its box.
|
||||
test_data: Vec<Test>,
|
||||
test_slots: Vec<u32>,
|
||||
test_indices: Vec<u32>,
|
||||
total: u64,
|
||||
@@ -142,8 +164,10 @@ pub struct Cull {
|
||||
readbacks: Vec<Readback>,
|
||||
next: usize,
|
||||
current: Option<usize>,
|
||||
/// Per resident slot: whether the last result read back saw it.
|
||||
/// Per resident slot: whether the last result read back saw it; and per
|
||||
/// far tile's slot (`TILE`).
|
||||
seen: Vec<bool>,
|
||||
seen_tiles: Vec<bool>,
|
||||
stamp: u32,
|
||||
/// Of the boxes the last test read back drew, how many it did not see
|
||||
/// and how many it drew, and the share of solid indices hidden.
|
||||
@@ -325,7 +349,7 @@ impl Cull {
|
||||
),
|
||||
phase2: buffer(device, "Section draws, phase 2", WORDS, PHASE2_USAGE),
|
||||
phase2_capacity: 1,
|
||||
tests: buffer(device, "Section tests", 16, TESTS_USAGE),
|
||||
tests: buffer(device, "Section tests", TEST_BYTES, TESTS_USAGE),
|
||||
tests_capacity: 1,
|
||||
test_data: Vec::new(),
|
||||
test_slots: Vec::new(),
|
||||
@@ -347,6 +371,7 @@ impl Cull {
|
||||
next: 0,
|
||||
current: None,
|
||||
seen: Vec::new(),
|
||||
seen_tiles: Vec::new(),
|
||||
stamp: 0,
|
||||
hidden: ([0; 2], 1.0),
|
||||
always: false,
|
||||
@@ -378,11 +403,15 @@ impl Cull {
|
||||
let share = indices as f32 / r.total.max(1) as f32;
|
||||
self.hidden = ([hidden, results.len()], share);
|
||||
for (&slot, &result) in r.slots.iter().zip(&results) {
|
||||
let slot = slot as usize;
|
||||
if slot >= self.seen.len() {
|
||||
self.seen.resize(slot + 1, false);
|
||||
let (seen, slot) = if slot & TILE != 0 {
|
||||
(&mut self.seen_tiles, (slot & !TILE) as usize)
|
||||
} else {
|
||||
(&mut self.seen, slot as usize)
|
||||
};
|
||||
if slot >= seen.len() {
|
||||
seen.resize(slot + 1, false);
|
||||
}
|
||||
self.seen[slot] = result == r.stamp;
|
||||
seen[slot] = result == r.stamp;
|
||||
}
|
||||
}
|
||||
r.buffer.unmap();
|
||||
@@ -397,30 +426,61 @@ impl Cull {
|
||||
!self.probe || self.stamp.is_multiple_of(PROBE)
|
||||
}
|
||||
|
||||
/// Whether phase 1 draws the section in resident `slot`.
|
||||
/// Whether phase 1 draws the section in resident `slot` (or the far tile
|
||||
/// in its `TILE` slot).
|
||||
pub fn first(&self, slot: u32) -> bool {
|
||||
if slot & TILE != 0 {
|
||||
return self
|
||||
.seen_tiles
|
||||
.get((slot & !TILE) as usize)
|
||||
.copied()
|
||||
.unwrap_or(false);
|
||||
}
|
||||
self.seen.get(slot as usize).copied().unwrap_or(false)
|
||||
}
|
||||
|
||||
/// A section phase 1 draws, `index` in the visible list, with `indices`
|
||||
/// solid indices: tested in turn.
|
||||
pub fn drawn(&mut self, index: usize, slot: u32, indices: u32) {
|
||||
/// solid indices and the box `bounds` around its origin: tested in turn.
|
||||
pub fn drawn(&mut self, index: usize, slot: u32, indices: u32, bounds: [[f32; 3]; 2]) {
|
||||
self.total += u64::from(indices);
|
||||
if indices > 0 && (self.probe || slot.wrapping_add(self.stamp).is_multiple_of(RETEST)) {
|
||||
self.push([index as u32, 0, 0, 0], slot, indices);
|
||||
self.push(index, bounds, slot, indices);
|
||||
}
|
||||
}
|
||||
|
||||
/// A section left to phase 2: its `draws` from phase 2's `first`.
|
||||
pub fn deferred(&mut self, index: usize, slot: u32, first: usize, draws: usize, indices: u32) {
|
||||
/// A section left to phase 2, with `draws` draws there: its test, whose
|
||||
/// draws `place` gives once phase 2's list is in its final order.
|
||||
pub fn deferred(
|
||||
&mut self,
|
||||
index: usize,
|
||||
slot: u32,
|
||||
draws: usize,
|
||||
indices: u32,
|
||||
bounds: [[f32; 3]; 2],
|
||||
) -> Option<u32> {
|
||||
self.total += u64::from(indices);
|
||||
if draws > 0 {
|
||||
self.push([index as u32, first as u32, draws as u32, 0], slot, indices);
|
||||
}
|
||||
(draws > 0).then(|| {
|
||||
self.push(index, bounds, slot, indices);
|
||||
self.test_data.len() as u32 - 1
|
||||
})
|
||||
}
|
||||
|
||||
fn push(&mut self, test: [u32; 4], slot: u32, indices: u32) {
|
||||
self.test_data.push(test);
|
||||
/// `test`'s draws in solid pass `pass` (0 or 1): `draws` from phase 2's
|
||||
/// `first`. Each pass draws its own part of the section's quads.
|
||||
pub fn place(&mut self, test: u32, pass: u32, first: u32, draws: u32) {
|
||||
debug_assert!(pass < 2 && draws < 1 << 16);
|
||||
let t = &mut self.test_data[test as usize].data;
|
||||
t[1 + 2 * pass as usize] = first;
|
||||
t[2] |= draws << (16 * pass);
|
||||
}
|
||||
|
||||
fn push(&mut self, index: usize, bounds: [[f32; 3]; 2], slot: u32, indices: u32) {
|
||||
let [low, high] = bounds.map(|[x, y, z]| [x, y, z, 0.0]);
|
||||
self.test_data.push(Test {
|
||||
data: [index as u32, 0, 0, 0],
|
||||
low,
|
||||
high,
|
||||
});
|
||||
self.test_slots.push(slot);
|
||||
self.test_indices.push(indices);
|
||||
}
|
||||
@@ -454,7 +514,7 @@ impl Cull {
|
||||
self.tests = buffer(
|
||||
device,
|
||||
"Section tests",
|
||||
self.tests_capacity * 16,
|
||||
self.tests_capacity * TEST_BYTES,
|
||||
TESTS_USAGE,
|
||||
);
|
||||
self.results = buffer(
|
||||
|
||||
@@ -11,10 +11,19 @@ struct Globals {
|
||||
view_proj: mat4x4<f32>,
|
||||
}
|
||||
|
||||
// One test per section: its visible index (which names its origin), its
|
||||
// first draw in `phase2` and how many draws it has there (0: phase 1 drew
|
||||
// it, and the test only tells whether it is still in sight).
|
||||
@group(0) @binding(0) var<storage, read> tests: array<vec4<u32>>;
|
||||
// One test per section or far tile: its visible index (which names its
|
||||
// origin), then its draws in `phase2` for each solid pass (gfx/cull.rs's
|
||||
// `place`): the first pass's first draw, both passes' draw counts (the
|
||||
// second's in the high 16 bits), the second pass's first draw. No draws:
|
||||
// phase 1 drew it, and the test only tells whether it is still in sight.
|
||||
// Then its box's lowest and highest corner around the origin (a section's:
|
||||
// -1 and 17, `SECTION_BOX`).
|
||||
struct Test {
|
||||
data: vec4<u32>,
|
||||
low: vec4<f32>,
|
||||
high: vec4<f32>,
|
||||
}
|
||||
@group(0) @binding(0) var<storage, read> tests: array<Test>;
|
||||
// Per test: the stamp of the frame whose test saw the box.
|
||||
@group(0) @binding(1) var<storage, read_write> results: array<u32>;
|
||||
// Phase 2's draws, 5 words each, with instance counts of 0 until seen.
|
||||
@@ -36,23 +45,27 @@ struct Box {
|
||||
@location(0) @interpolate(flat) test: u32,
|
||||
}
|
||||
|
||||
fn corner(origin: vec3<f32>, c: u32) -> vec3<f32> {
|
||||
fn corner(origin: vec3<f32>, low: vec3<f32>, high: vec3<f32>, c: u32) -> vec3<f32> {
|
||||
return origin + vec3(
|
||||
select(-1.0, 17.0, (c & 1u) != 0u),
|
||||
select(-1.0, 17.0, (c & 2u) != 0u),
|
||||
select(-1.0, 17.0, (c & 4u) != 0u),
|
||||
select(low.x, high.x, (c & 1u) != 0u),
|
||||
select(low.y, high.y, (c & 2u) != 0u),
|
||||
select(low.z, high.z, (c & 4u) != 0u),
|
||||
);
|
||||
}
|
||||
|
||||
// Each tested section's box, grown by a block (block models reach one block
|
||||
// out of their cell), drawn after phase 1 against its depth with back faces
|
||||
// culled: a front face fragment that passes the depth test is the box's
|
||||
// nearest point on that pixel in front of phase 1's depth, so the section
|
||||
// may show there. A box failing everywhere would have every fragment of its
|
||||
// section fail the depth test against the final depth, which is only nearer.
|
||||
// Each tested section's or far tile's box, grown by a block (block models
|
||||
// reach one block out of their cell), drawn after phase 1 against its depth
|
||||
// with back faces culled: a front face fragment that passes the depth test
|
||||
// is the box's nearest point on that pixel in front of phase 1's depth, so
|
||||
// the section may show there. A box failing everywhere would have every
|
||||
// fragment of its section fail the depth test against the final depth,
|
||||
// which is only nearer.
|
||||
@vertex
|
||||
fn vs_box(@builtin(vertex_index) v: u32, @builtin(instance_index) t: u32) -> Box {
|
||||
let origin = origins[tests[t].x].xyz;
|
||||
let test = tests[t];
|
||||
let origin = origins[test.data.x].xyz;
|
||||
let low = test.low.xyz;
|
||||
let high = test.high.xyz;
|
||||
var out: Box;
|
||||
out.test = t;
|
||||
// The box's screen rectangle, from the corners in front of the camera.
|
||||
@@ -60,7 +73,7 @@ fn vs_box(@builtin(vertex_index) v: u32, @builtin(instance_index) t: u32) -> Box
|
||||
var hi = vec2(-1e30);
|
||||
var behind = false;
|
||||
for (var c = 0u; c < 8u; c++) {
|
||||
let p = globals.view_proj * vec4(corner(origin, c), 1.0);
|
||||
let p = globals.view_proj * vec4(corner(origin, low, high, c), 1.0);
|
||||
behind = behind || p.w <= 0.0;
|
||||
let q = p.xy / max(p.w, 1e-6);
|
||||
lo = min(lo, q);
|
||||
@@ -71,7 +84,7 @@ fn vs_box(@builtin(vertex_index) v: u32, @builtin(instance_index) t: u32) -> Box
|
||||
// clips its front faces away, and a box over a large part of the screen
|
||||
// is nearly always in sight and costly to test: both count as seen,
|
||||
// through a small triangle in the top left corner at the nearest depth.
|
||||
let near = all(origin < vec3(2.0)) && all(origin > vec3(-18.0));
|
||||
let near = all(origin < vec3(1.0) - low) && all(origin > vec3(-1.0) - high);
|
||||
if near || (!behind && size.x * size.y > params.big * 4.0) {
|
||||
var p = vec2(-1.0, 1.0);
|
||||
if v == 1u {
|
||||
@@ -82,7 +95,7 @@ fn vs_box(@builtin(vertex_index) v: u32, @builtin(instance_index) t: u32) -> Box
|
||||
out.position = select(vec4(0.0, 0.0, 0.0, 1.0), vec4(p, 1.0, 1.0), v < 3u);
|
||||
return out;
|
||||
}
|
||||
out.position = globals.view_proj * vec4(corner(origin, CORNERS[v]), 1.0);
|
||||
out.position = globals.view_proj * vec4(corner(origin, low, high, CORNERS[v]), 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -91,10 +104,13 @@ fn vs_box(@builtin(vertex_index) v: u32, @builtin(instance_index) t: u32) -> Box
|
||||
fn see(t: u32) {
|
||||
if results[t] != params.stamp {
|
||||
results[t] = params.stamp;
|
||||
let test = tests[t];
|
||||
for (var k = 0u; k < test.z; k++) {
|
||||
let test = tests[t].data;
|
||||
for (var k = 0u; k < (test.z & 0xffffu); k++) {
|
||||
phase2[(test.y + k) * 5u + 1u] = 1u;
|
||||
}
|
||||
for (var k = 0u; k < (test.z >> 16u); k++) {
|
||||
phase2[(test.w + k) * 5u + 1u] = 1u;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,707 @@
|
||||
//! `[displacement]` of Horizon's own shader packs (docs/api/shaders.md): near the camera the
|
||||
//! solid faces whose textures have LabPBR heights are drawn again as a grid of cells, each
|
||||
//! corner moved along the face's normal by the pack's `displacement`, while the flat quads
|
||||
//! leave those pixels to them (`native_material.rs`'s `horizon_displaced_layer`).
|
||||
//!
|
||||
//! Each frame the solid quads of the sections around the camera are copied out of the arena
|
||||
//! into one buffer, a compute pass picks the ones the grid draws (opaque, with relief, within
|
||||
//! the distance, facing the eye and in view) into an indirect draw, and the material's
|
||||
//! displaced pipeline draws one instance a quad.
|
||||
|
||||
use crate::mesher::TerrainVertex;
|
||||
|
||||
/// The solid quads the grid can draw a frame, and the sections they come from.
|
||||
const MAX_QUADS: u64 = 1 << 16;
|
||||
const MAX_SECTIONS: usize = 64;
|
||||
/// Bytes of one quad's corners.
|
||||
const QUAD_BYTES: u64 = 4 * std::mem::size_of::<TerrainVertex>() as u64;
|
||||
/// How far past the flat face the relief may reach, in blocks, for the view test.
|
||||
const RELIEF_MARGIN: f32 = 0.5;
|
||||
|
||||
/// Which quads the grid takes over, for the flat terrain's pipelines (the material's and the
|
||||
/// shadow's) and the grid's: `horizon_displace_distance` and the LabPBR normal maps
|
||||
/// `horizon_pbr_normals` (group 3, binding 12), declared by the module.
|
||||
pub const RELIEF: &str = "
|
||||
// `[displacement] distance` (0 without): the flat quads leave the pixels within it on faces
|
||||
// with relief to the grid.
|
||||
override horizon_displace_distance: f32 = 0.0;
|
||||
// The mean LabPBR height of a sprite: its normal map's last mip level (1 without one).
|
||||
fn horizon_relief_mean(layer: u32) -> f32 {
|
||||
if textureDimensions(horizon_pbr_normals).x <= 1u
|
||||
|| layer >= textureNumLayers(horizon_pbr_normals) {
|
||||
return 1.0;
|
||||
}
|
||||
let top = i32(textureNumLevels(horizon_pbr_normals)) - 1;
|
||||
return textureLoad(horizon_pbr_normals, vec2(0i), i32(layer), top).a;
|
||||
}
|
||||
// The quads the grid redraws near the camera (`PICK`): opaque, on a texture whose height
|
||||
// varies.
|
||||
fn horizon_displaced_layer(layer: u32) -> bool {
|
||||
return horizon_displace_distance > 0.0 && (layer & 0x8000u) != 0u
|
||||
&& horizon_relief_mean(layer & 0x7fffu) < 0.998;
|
||||
}
|
||||
";
|
||||
|
||||
/// The grid's points (`horizon_displace_point`): one instance a quad the pick chose, its
|
||||
/// corners read from the copy of the arena, each of its `horizon_displace_cells` squared
|
||||
/// cells two triangles moved along the face's normal by the pack's `displacement`, measured
|
||||
/// from the texture's mean height so that the face's edges, pinned there, meet their
|
||||
/// neighbours whatever their relief. Needs `RELIEF`, the pack's `displacement` and
|
||||
/// `horizon_material_displace` (its vertex stage).
|
||||
pub const GRID: &str = "
|
||||
struct HorizonDisplaceSection {
|
||||
origin: vec4<f32>,
|
||||
first: u32,
|
||||
quads: u32,
|
||||
pad0: u32,
|
||||
pad1: u32,
|
||||
}
|
||||
@group(1) @binding(8) var<storage, read> horizon_displace_vertices: array<u32>;
|
||||
@group(1) @binding(9) var<storage, read> horizon_displace_picked: array<vec2<u32>>;
|
||||
@group(1) @binding(10) var<storage, read> horizon_displace_sections: array<HorizonDisplaceSection>;
|
||||
// The grid's cells along a face's side.
|
||||
override horizon_displace_cells: u32 = 16u;
|
||||
// Each cell a flat step with walls to its neighbours (`grid_vertices`), not a smooth surface.
|
||||
override horizon_displace_steps: bool = false;
|
||||
struct HorizonDisplaceCorner {
|
||||
position: vec3<f32>,
|
||||
uv: vec2<f32>,
|
||||
layer: u32,
|
||||
light: vec2<f32>,
|
||||
color: vec4<f32>,
|
||||
}
|
||||
// A corner of a quad as `TerrainVertex` holds it (24 bytes); light: sky, block.
|
||||
fn horizon_displace_corner(quad: u32, i: u32, step: f32) -> HorizonDisplaceCorner {
|
||||
let b = (quad * 4u + i) * 6u;
|
||||
var c: HorizonDisplaceCorner;
|
||||
c.position = vec3(bitcast<f32>(horizon_displace_vertices[b]),
|
||||
bitcast<f32>(horizon_displace_vertices[b + 1u]),
|
||||
bitcast<f32>(horizon_displace_vertices[b + 2u]));
|
||||
let uv = horizon_displace_vertices[b + 3u];
|
||||
c.uv = vec2<f32>(vec2(uv & 0xffffu, uv >> 16u)) * step;
|
||||
let w = horizon_displace_vertices[b + 4u];
|
||||
c.layer = w & 0xffffu;
|
||||
c.light = vec2(f32((w >> 16u) & 0xffu), f32(w >> 24u));
|
||||
c.color = unpack4x8unorm(horizon_displace_vertices[b + 5u]);
|
||||
return c;
|
||||
}
|
||||
// The height map at `uv`, filtered between the texels of mip level `lod`.
|
||||
fn horizon_displace_height(uv: vec2<f32>, layer: u32, lod: i32) -> f32 {
|
||||
let size = vec2<i32>(textureDimensions(horizon_pbr_normals, lod));
|
||||
let p = clamp(uv, vec2(0.0), vec2(1.0)) * vec2<f32>(size) - 0.5;
|
||||
let i = vec2<i32>(floor(p));
|
||||
let f = p - floor(p);
|
||||
let m = size - 1;
|
||||
let l = i32(layer);
|
||||
let a = textureLoad(horizon_pbr_normals, clamp(i, vec2(0), m), l, lod).a;
|
||||
let b = textureLoad(horizon_pbr_normals, clamp(i + vec2(1, 0), vec2(0), m), l, lod).a;
|
||||
let c = textureLoad(horizon_pbr_normals, clamp(i + vec2(0, 1), vec2(0), m), l, lod).a;
|
||||
let d = textureLoad(horizon_pbr_normals, clamp(i + vec2(1, 1), vec2(0), m), l, lod).a;
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
// The height map's texel at `uv` of mip level `lod`.
|
||||
fn horizon_displace_texel(uv: vec2<f32>, layer: u32, lod: i32) -> f32 {
|
||||
let size = vec2<i32>(textureDimensions(horizon_pbr_normals, lod));
|
||||
let i = clamp(vec2<i32>(uv * vec2<f32>(size)), vec2(0), size - 1);
|
||||
return textureLoad(horizon_pbr_normals, i, i32(layer), lod).a;
|
||||
}
|
||||
struct HorizonDisplacePoint {
|
||||
// Where it is drawn, and where it lies on the flat face (both camera-relative).
|
||||
position: vec3<f32>,
|
||||
base: vec3<f32>,
|
||||
uv: vec2<f32>,
|
||||
layer: u32,
|
||||
light: vec2<f32>,
|
||||
color: vec4<f32>,
|
||||
// The face's normal, and how much of the relief the point has by its distance.
|
||||
normal: vec3<f32>,
|
||||
near: f32,
|
||||
}
|
||||
// A quad's corners, for the steps.
|
||||
struct HorizonDisplaceQuad {
|
||||
c0: HorizonDisplaceCorner,
|
||||
c1: HorizonDisplaceCorner,
|
||||
c2: HorizonDisplaceCorner,
|
||||
c3: HorizonDisplaceCorner,
|
||||
origin: vec3<f32>,
|
||||
normal: vec3<f32>,
|
||||
}
|
||||
fn horizon_displace_at(q: HorizonDisplaceQuad, s: f32, t: f32) -> vec3<f32> {
|
||||
return mix(mix(q.c0.position, q.c1.position, s), mix(q.c3.position, q.c2.position, s), t)
|
||||
+ q.origin;
|
||||
}
|
||||
fn horizon_displace_uv(q: HorizonDisplaceQuad, s: f32, t: f32) -> vec2<f32> {
|
||||
return mix(mix(q.c0.uv, q.c1.uv, s), mix(q.c3.uv, q.c2.uv, s), t);
|
||||
}
|
||||
// How far cell (`i`, `j`) sinks: nothing outside the face, whose edges keep the mean height so
|
||||
// that they meet their neighbours.
|
||||
fn horizon_displace_cell_depth(q: HorizonDisplaceQuad, i: i32, j: i32) -> f32 {
|
||||
let g = i32(horizon_displace_cells);
|
||||
if i < 0 || j < 0 || i >= g || j >= g {
|
||||
return 0.0;
|
||||
}
|
||||
let s = (f32(i) + 0.5) / f32(g);
|
||||
let t = (f32(j) + 0.5) / f32(g);
|
||||
let base = horizon_displace_at(q, s, t);
|
||||
let uv = horizon_displace_uv(q, s, t);
|
||||
let layer = q.c0.layer & 0x7fffu;
|
||||
let lods = i32(textureNumLevels(horizon_pbr_normals)) - 1;
|
||||
let size = f32(textureDimensions(horizon_pbr_normals).x);
|
||||
let lod = clamp(i32(floor(log2(size / f32(g)))), 0, lods);
|
||||
let material_id = horizon_sprite_material(layer);
|
||||
let height = horizon_displace_texel(uv, layer, lod);
|
||||
let mean = horizon_relief_mean(layer);
|
||||
let depth = displacement(DisplacementInput(base, q.normal, uv, material_id, height))
|
||||
- displacement(DisplacementInput(base, q.normal, uv, material_id, mean));
|
||||
let r = horizon_displace_distance;
|
||||
return depth * (1.0 - smoothstep(0.75 * r, r - 0.1, length(base)));
|
||||
}
|
||||
// Vertex `v` of a stepped grid: per cell, from -1 so that the first walls have a cell before
|
||||
// them, its flat top and its walls to the next cell along each side. A wall faces the way
|
||||
// out of the higher cell and takes that cell's texture.
|
||||
fn horizon_displace_step(v: u32, q: HorizonDisplaceQuad) -> HorizonDisplacePoint {
|
||||
let g = horizon_displace_cells;
|
||||
let cell = v / 18u;
|
||||
let part = (v / 6u) % 3u;
|
||||
let i = i32(cell % (g + 1u)) - 1;
|
||||
let j = i32(cell / (g + 1u)) - 1;
|
||||
let cells = f32(g);
|
||||
var k = v % 6u;
|
||||
var s = 0.0;
|
||||
var t = 0.0;
|
||||
var uv_s = 0.0;
|
||||
var uv_t = 0.0;
|
||||
var depth = 0.0;
|
||||
if part == 0u {
|
||||
if i >= 0 && j >= 0 {
|
||||
s = (f32(i) + f32((0x16u >> k) & 1u)) / cells;
|
||||
t = (f32(j) + f32((0x34u >> k) & 1u)) / cells;
|
||||
depth = horizon_displace_cell_depth(q, i, j);
|
||||
}
|
||||
uv_s = s;
|
||||
uv_t = t;
|
||||
} else if part == 1u {
|
||||
if j >= 0 {
|
||||
let here = horizon_displace_cell_depth(q, i, j);
|
||||
let next = horizon_displace_cell_depth(q, i + 1, j);
|
||||
if next < here {
|
||||
k = (0x453120u >> (k * 4u)) & 15u;
|
||||
}
|
||||
s = f32(i + 1) / cells;
|
||||
t = (f32(j) + f32((0x16u >> k) & 1u)) / cells;
|
||||
depth = select(max(here, next), min(here, next), ((0x34u >> k) & 1u) == 1u);
|
||||
uv_s = s + select(0.25, -0.25, here <= next) / cells;
|
||||
uv_t = t;
|
||||
}
|
||||
} else {
|
||||
if i >= 0 {
|
||||
let here = horizon_displace_cell_depth(q, i, j);
|
||||
let next = horizon_displace_cell_depth(q, i, j + 1);
|
||||
if here < next {
|
||||
k = (0x453120u >> (k * 4u)) & 15u;
|
||||
}
|
||||
s = (f32(i) + f32((0x16u >> k) & 1u)) / cells;
|
||||
t = f32(j + 1) / cells;
|
||||
depth = select(max(here, next), min(here, next), ((0x34u >> k) & 1u) == 1u);
|
||||
uv_s = s;
|
||||
uv_t = t + select(0.25, -0.25, here <= next) / cells;
|
||||
}
|
||||
}
|
||||
var p: HorizonDisplacePoint;
|
||||
let position = horizon_displace_at(q, s, t);
|
||||
let c = clamp(vec2(uv_s, uv_t), vec2(0.0), vec2(1.0));
|
||||
p.uv = horizon_displace_uv(q, c.x, c.y);
|
||||
p.light = mix(mix(q.c0.light, q.c1.light, s), mix(q.c3.light, q.c2.light, s), t);
|
||||
p.color = mix(mix(q.c0.color, q.c1.color, s), mix(q.c3.color, q.c2.color, s), t);
|
||||
p.layer = q.c0.layer;
|
||||
p.normal = q.normal;
|
||||
p.base = horizon_material_displace(position, p.uv, q.c0.layer, p.color,
|
||||
vec2<u32>(p.light + 0.5));
|
||||
p.near = 1.0 - smoothstep(0.75 * horizon_displace_distance, horizon_displace_distance - 0.1,
|
||||
length(p.base));
|
||||
p.position = p.base - p.normal * depth;
|
||||
return p;
|
||||
}
|
||||
// Vertex `v` of instance `instance`'s grid.
|
||||
fn horizon_displace_point(v: u32, instance: u32) -> HorizonDisplacePoint {
|
||||
let pick = horizon_displace_picked[instance];
|
||||
let section = horizon_displace_sections[pick.y];
|
||||
let c0 = horizon_displace_corner(pick.x, 0u, section.origin.w);
|
||||
let c1 = horizon_displace_corner(pick.x, 1u, section.origin.w);
|
||||
let c2 = horizon_displace_corner(pick.x, 2u, section.origin.w);
|
||||
let c3 = horizon_displace_corner(pick.x, 3u, section.origin.w);
|
||||
if horizon_displace_steps {
|
||||
return horizon_displace_step(v, HorizonDisplaceQuad(c0, c1, c2, c3, section.origin.xyz,
|
||||
normalize(cross(c1.position - c0.position, c2.position - c0.position))));
|
||||
}
|
||||
// The cell's two triangles wind as the quad's (0, 1, 2) and (0, 2, 3).
|
||||
let g = horizon_displace_cells;
|
||||
let cell = v / 6u;
|
||||
let k = v % 6u;
|
||||
let s = f32(cell % g + ((0x16u >> k) & 1u)) / f32(g);
|
||||
let t = f32(cell / g + ((0x34u >> k) & 1u)) / f32(g);
|
||||
var p: HorizonDisplacePoint;
|
||||
let position = mix(mix(c0.position, c1.position, s), mix(c3.position, c2.position, s), t);
|
||||
p.uv = mix(mix(c0.uv, c1.uv, s), mix(c3.uv, c2.uv, s), t);
|
||||
p.light = mix(mix(c0.light, c1.light, s), mix(c3.light, c2.light, s), t);
|
||||
p.color = mix(mix(c0.color, c1.color, s), mix(c3.color, c2.color, s), t);
|
||||
p.layer = c0.layer;
|
||||
p.normal = normalize(cross(c1.position - c0.position, c2.position - c0.position));
|
||||
p.base = horizon_material_displace(position + section.origin.xyz, p.uv, c0.layer, p.color,
|
||||
vec2<u32>(p.light + 0.5));
|
||||
let layer = c0.layer & 0x7fffu;
|
||||
// The heights at about a texel a cell.
|
||||
let lods = i32(textureNumLevels(horizon_pbr_normals)) - 1;
|
||||
let size = f32(textureDimensions(horizon_pbr_normals).x);
|
||||
let lod = clamp(i32(floor(log2(size / f32(g)))), 0, lods);
|
||||
let material_id = horizon_sprite_material(layer);
|
||||
let height = horizon_displace_height(p.uv, layer, lod);
|
||||
let mean = horizon_relief_mean(layer);
|
||||
let depth = displacement(DisplacementInput(p.base, p.normal, p.uv, material_id, height))
|
||||
- displacement(DisplacementInput(p.base, p.normal, p.uv, material_id, mean));
|
||||
// Pinned at the face's edges, easing out toward the distance.
|
||||
let edge = min(min(s, 1.0 - s), min(t, 1.0 - t)) * f32(g);
|
||||
let r = horizon_displace_distance;
|
||||
p.near = 1.0 - smoothstep(0.75 * r, r - 0.1, length(p.base));
|
||||
p.position = p.base - p.normal * depth * clamp(edge, 0.0, 1.0) * p.near;
|
||||
return p;
|
||||
}
|
||||
";
|
||||
|
||||
const PICK: &str = "
|
||||
struct Params {
|
||||
view_proj: mat4x4<f32>,
|
||||
distance: f32,
|
||||
sections: u32,
|
||||
margin: f32,
|
||||
capacity: u32,
|
||||
// 1 for the shadow map's list: every side, wherever the eye looks.
|
||||
shadow: u32,
|
||||
pad0: u32,
|
||||
pad1: u32,
|
||||
pad2: u32,
|
||||
}
|
||||
struct Section {
|
||||
origin: vec4<f32>,
|
||||
first: u32,
|
||||
quads: u32,
|
||||
pad0: u32,
|
||||
pad1: u32,
|
||||
}
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var<storage, read> sections: array<Section>;
|
||||
@group(0) @binding(2) var<storage, read> vertices: array<u32>;
|
||||
@group(0) @binding(3) var<storage, read_write> picked: array<vec2<u32>>;
|
||||
@group(0) @binding(4) var<storage, read_write> draw: array<atomic<u32>, 4>;
|
||||
@group(0) @binding(5) var normals: texture_2d_array<f32>;
|
||||
|
||||
fn corner(quad: u32, i: u32, origin: vec3<f32>) -> vec3<f32> {
|
||||
let b = (quad * 4u + i) * 6u;
|
||||
return vec3(bitcast<f32>(vertices[b]), bitcast<f32>(vertices[b + 1u]),
|
||||
bitcast<f32>(vertices[b + 2u])) + origin;
|
||||
}
|
||||
|
||||
// Every corner of the box beyond one side of the view.
|
||||
fn outside(lo: vec3<f32>, hi: vec3<f32>) -> bool {
|
||||
var out = vec4(true);
|
||||
var behind = true;
|
||||
for (var i = 0u; i < 8u; i++) {
|
||||
let p = select(lo, hi, vec3((i & 1u) != 0u, (i & 2u) != 0u, (i & 4u) != 0u));
|
||||
let c = params.view_proj * vec4(p, 1.0);
|
||||
out = out & vec4((c.x < -c.w), (c.x > c.w), (c.y < -c.w), (c.y > c.w));
|
||||
behind = behind && c.w <= 0.0;
|
||||
}
|
||||
return any(out) || behind;
|
||||
}
|
||||
|
||||
@compute @workgroup_size(64) fn pick(@builtin(global_invocation_id) id: vec3<u32>) {
|
||||
if id.y >= params.sections {
|
||||
return;
|
||||
}
|
||||
let section = sections[id.y];
|
||||
if id.x >= section.quads {
|
||||
return;
|
||||
}
|
||||
let quad = section.first + id.x;
|
||||
// As `horizon_displaced_layer`: opaque, with a LabPBR height that varies.
|
||||
let layer = vertices[quad * 24u + 4u] & 0xffffu;
|
||||
let l = layer & 0x7fffu;
|
||||
if (layer & 0x8000u) == 0u || textureDimensions(normals).x <= 1u
|
||||
|| l >= textureNumLayers(normals) {
|
||||
return;
|
||||
}
|
||||
let top = i32(textureNumLevels(normals)) - 1;
|
||||
if textureLoad(normals, vec2(0i), i32(l), top).a >= 0.998 {
|
||||
return;
|
||||
}
|
||||
let p0 = corner(quad, 0u, section.origin.xyz);
|
||||
let p1 = corner(quad, 1u, section.origin.xyz);
|
||||
let p2 = corner(quad, 2u, section.origin.xyz);
|
||||
let p3 = corner(quad, 3u, section.origin.xyz);
|
||||
let lo = min(min(p0, p1), min(p2, p3));
|
||||
let hi = max(max(p0, p1), max(p2, p3));
|
||||
if length(clamp(vec3(0.0), lo, hi)) >= params.distance {
|
||||
return;
|
||||
}
|
||||
// Facing away from the eye (at the origin), where the flat quad is culled too, or out of
|
||||
// its view; the shadow map takes them all, as the flat terrain draws both sides there.
|
||||
if params.shadow == 0u && (dot(cross(p1 - p0, p2 - p0), p0) >= 0.0
|
||||
|| outside(lo - params.margin, hi + params.margin)) {
|
||||
return;
|
||||
}
|
||||
let i = atomicAdd(&draw[1], 1u);
|
||||
if i >= params.capacity {
|
||||
atomicSub(&draw[1], 1u);
|
||||
return;
|
||||
}
|
||||
picked[i] = vec2(quad, id.y);
|
||||
}
|
||||
";
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Params {
|
||||
view_proj: [[f32; 4]; 4],
|
||||
distance: f32,
|
||||
sections: u32,
|
||||
margin: f32,
|
||||
capacity: u32,
|
||||
shadow: u32,
|
||||
pad: [u32; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Section {
|
||||
origin: [f32; 4],
|
||||
first: u32,
|
||||
quads: u32,
|
||||
pad: [u32; 2],
|
||||
}
|
||||
|
||||
/// A section the grid may draw from this frame: its arena range of solid quads and its
|
||||
/// camera-relative origin.
|
||||
pub struct Near<'a> {
|
||||
pub buffer: &'a wgpu::Buffer,
|
||||
pub start: u64,
|
||||
pub quads: u32,
|
||||
pub origin: [f32; 3],
|
||||
}
|
||||
|
||||
/// One list of picked quads and its indirect draw.
|
||||
pub struct Picks {
|
||||
/// The grid's cells along a face's side.
|
||||
pub cells: u32,
|
||||
/// Each cell a flat step with walls.
|
||||
pub steps: bool,
|
||||
params: wgpu::Buffer,
|
||||
/// The indirect draw: the grid's vertices, then the quads picked.
|
||||
draw: wgpu::Buffer,
|
||||
pick_group: wgpu::BindGroup,
|
||||
/// Group 1 of the pipelines drawing it (bindings 8 to 10).
|
||||
pub draw_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
pub struct Displacement {
|
||||
/// `[displacement] distance`, in blocks.
|
||||
pub distance: f32,
|
||||
vertices: wgpu::Buffer,
|
||||
sections: wgpu::Buffer,
|
||||
pick: wgpu::ComputePipeline,
|
||||
/// The quads the eye sees, and the ones the shadow map draws (when the pack has one).
|
||||
pub view: Picks,
|
||||
pub shadow: Option<Picks>,
|
||||
/// Group 1's layout for the grid's pipelines.
|
||||
pub draw_layout: wgpu::BindGroupLayout,
|
||||
/// Sections the pick ran over this frame (0: nothing to draw).
|
||||
pub drawn: usize,
|
||||
}
|
||||
|
||||
fn storage(
|
||||
binding: u32,
|
||||
read_only: bool,
|
||||
visibility: wgpu::ShaderStages,
|
||||
) -> wgpu::BindGroupLayoutEntry {
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
visibility,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map's grid is coarser: its texels near the camera are a 32nd of a block.
|
||||
pub const SHADOW_CELLS: u32 = 16;
|
||||
|
||||
/// The vertices of one quad's grid: two triangles a cell, or in `steps` also two walls a cell,
|
||||
/// with a row and a column more for the walls at the face's first edges.
|
||||
fn grid_vertices(cells: u32, steps: bool) -> u32 {
|
||||
if steps {
|
||||
(cells + 1) * (cells + 1) * 18
|
||||
} else {
|
||||
cells * cells * 6
|
||||
}
|
||||
}
|
||||
|
||||
impl Displacement {
|
||||
/// The grid's buffers and pick pass for `[displacement]` with `distance` and `cells`, in
|
||||
/// `steps` or not, reading the LabPBR normal maps `normals`; with a list for the shadow map
|
||||
/// when `shadow`.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
distance: f32,
|
||||
cells: u32,
|
||||
steps: bool,
|
||||
normals: &wgpu::TextureView,
|
||||
shadow: bool,
|
||||
) -> Self {
|
||||
let buffer = |label, size, usage| {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
};
|
||||
use wgpu::BufferUsages as U;
|
||||
let vertices = buffer(
|
||||
"Displacement quads",
|
||||
MAX_QUADS * QUAD_BYTES,
|
||||
U::STORAGE | U::COPY_DST,
|
||||
);
|
||||
let sections = buffer(
|
||||
"Displacement sections",
|
||||
(MAX_SECTIONS * std::mem::size_of::<Section>()) as u64,
|
||||
U::STORAGE | U::COPY_DST,
|
||||
);
|
||||
let compute = wgpu::ShaderStages::COMPUTE;
|
||||
let pick_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: compute,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
storage(1, true, compute),
|
||||
storage(2, true, compute),
|
||||
storage(3, false, compute),
|
||||
storage(4, false, compute),
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: compute,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let vertex = wgpu::ShaderStages::VERTEX;
|
||||
let draw_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Displacement draw"),
|
||||
entries: &[
|
||||
storage(8, true, vertex),
|
||||
storage(9, true, vertex),
|
||||
storage(10, true, vertex),
|
||||
],
|
||||
});
|
||||
fn entry(binding: u32, resource: wgpu::BindingResource<'_>) -> wgpu::BindGroupEntry<'_> {
|
||||
wgpu::BindGroupEntry { binding, resource }
|
||||
}
|
||||
let picks = |cells: u32| {
|
||||
let params = buffer(
|
||||
"Displacement parameters",
|
||||
std::mem::size_of::<Params>() as u64,
|
||||
U::UNIFORM | U::COPY_DST,
|
||||
);
|
||||
let picked = buffer("Displacement picks", MAX_QUADS * 8, U::STORAGE);
|
||||
let draw = buffer(
|
||||
"Displacement draw",
|
||||
16,
|
||||
U::STORAGE | U::INDIRECT | U::COPY_DST,
|
||||
);
|
||||
let pick_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
layout: &pick_layout,
|
||||
entries: &[
|
||||
entry(0, params.as_entire_binding()),
|
||||
entry(1, sections.as_entire_binding()),
|
||||
entry(2, vertices.as_entire_binding()),
|
||||
entry(3, picked.as_entire_binding()),
|
||||
entry(4, draw.as_entire_binding()),
|
||||
entry(5, wgpu::BindingResource::TextureView(normals)),
|
||||
],
|
||||
});
|
||||
let draw_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Displacement draw"),
|
||||
layout: &draw_layout,
|
||||
entries: &[
|
||||
entry(8, vertices.as_entire_binding()),
|
||||
entry(9, picked.as_entire_binding()),
|
||||
entry(10, sections.as_entire_binding()),
|
||||
],
|
||||
});
|
||||
Picks {
|
||||
cells,
|
||||
steps,
|
||||
params,
|
||||
draw,
|
||||
pick_group,
|
||||
draw_group,
|
||||
}
|
||||
};
|
||||
let view = picks(cells);
|
||||
// A step's flat top has to lie where its shadow was cast from, or it shadows itself in
|
||||
// grain; a smooth surface hides the difference and can take fewer cells.
|
||||
let shadow = shadow.then(|| {
|
||||
picks(if steps {
|
||||
cells
|
||||
} else {
|
||||
cells.min(SHADOW_CELLS)
|
||||
})
|
||||
});
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
source: wgpu::ShaderSource::Wgsl(PICK.into()),
|
||||
});
|
||||
let pick = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
layout: Some(
|
||||
&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
bind_group_layouts: &[Some(&pick_layout)],
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
module: &module,
|
||||
entry_point: Some("pick"),
|
||||
compilation_options: Default::default(),
|
||||
cache: None,
|
||||
});
|
||||
Self {
|
||||
distance,
|
||||
vertices,
|
||||
sections,
|
||||
pick,
|
||||
view,
|
||||
shadow,
|
||||
draw_layout,
|
||||
drawn: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the solid quads of `near` (nearest first) and picks this frame's in `encoder`.
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
near: &[Near<'_>],
|
||||
view_proj: glam::Mat4,
|
||||
) {
|
||||
let mut table = Vec::with_capacity(near.len().min(MAX_SECTIONS));
|
||||
let mut first = 0u64;
|
||||
let mut most = 0u32;
|
||||
for n in near.iter().take(MAX_SECTIONS) {
|
||||
let quads = u64::from(n.quads).min(MAX_QUADS - first);
|
||||
if quads == 0 {
|
||||
break;
|
||||
}
|
||||
let stride = std::mem::size_of::<TerrainVertex>() as u64;
|
||||
encoder.copy_buffer_to_buffer(
|
||||
n.buffer,
|
||||
n.start * stride,
|
||||
&self.vertices,
|
||||
first * QUAD_BYTES,
|
||||
quads * QUAD_BYTES,
|
||||
);
|
||||
table.push(Section {
|
||||
origin: [
|
||||
n.origin[0],
|
||||
n.origin[1],
|
||||
n.origin[2],
|
||||
1.0 / crate::mesher::UV_STEPS,
|
||||
],
|
||||
first: first as u32,
|
||||
quads: quads as u32,
|
||||
pad: [0; 2],
|
||||
});
|
||||
first += quads;
|
||||
most = most.max(quads as u32);
|
||||
}
|
||||
self.drawn = table.len();
|
||||
if table.is_empty() {
|
||||
return;
|
||||
}
|
||||
queue.write_buffer(&self.sections, 0, bytemuck::cast_slice(&table));
|
||||
let lists = std::iter::once((&self.view, 0)).chain(self.shadow.iter().map(|s| (s, 1)));
|
||||
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("Displacement pick"),
|
||||
timestamp_writes: None,
|
||||
});
|
||||
pass.set_pipeline(&self.pick);
|
||||
for (picks, shadow) in lists {
|
||||
let params = Params {
|
||||
view_proj: view_proj.to_cols_array_2d(),
|
||||
distance: self.distance,
|
||||
sections: table.len() as u32,
|
||||
margin: RELIEF_MARGIN,
|
||||
capacity: MAX_QUADS as u32,
|
||||
shadow,
|
||||
pad: [0; 3],
|
||||
};
|
||||
queue.write_buffer(&picks.params, 0, bytemuck::bytes_of(¶ms));
|
||||
let vertices = grid_vertices(picks.cells, picks.steps);
|
||||
queue.write_buffer(&picks.draw, 0, bytemuck::cast_slice(&[vertices, 0, 0, 0]));
|
||||
pass.set_bind_group(0, &picks.pick_group, &[]);
|
||||
pass.dispatch_workgroups(most.div_ceil(64), table.len() as u32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws `picks`' grids with `pipeline`, its other groups bound.
|
||||
pub fn draw(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
picks: &Picks,
|
||||
pipeline: &wgpu::RenderPipeline,
|
||||
) {
|
||||
if self.drawn == 0 {
|
||||
return;
|
||||
}
|
||||
pass.set_pipeline(pipeline);
|
||||
pass.set_bind_group(1, &picks.draw_group, &[]);
|
||||
pass.draw_indirect(&picks.draw, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn a_stepped_grid_has_a_top_and_two_walls_a_cell_and_the_first_edges() {
|
||||
assert_eq!(super::grid_vertices(16, false), 16 * 16 * 6);
|
||||
assert_eq!(super::grid_vertices(16, true), 17 * 17 * 18);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_pick_validates() {
|
||||
let module = naga::front::wgsl::parse_str(super::PICK)
|
||||
.unwrap_or_else(|e| panic!("{}", e.emit_to_string(super::PICK)));
|
||||
naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::all(),
|
||||
naga::valid::Capabilities::all(),
|
||||
)
|
||||
.validate(&module)
|
||||
.unwrap_or_else(|e| panic!("{}", e.emit_to_string(super::PICK)));
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,9 @@ struct Plan {
|
||||
solid_calls: usize,
|
||||
/// Where the shadow camera's solid and translucent calls begin.
|
||||
shadow_calls: [usize; 2],
|
||||
/// Where Metal's opaque pass ends and its cutout pass begins in phase 1's calls.
|
||||
pass_calls: [usize; 2],
|
||||
/// Where Metal's opaque pass ends and its cutout pass begins in phase 1's calls,
|
||||
/// and where its cutout pass begins in phase 2's.
|
||||
pass_calls: [usize; 3],
|
||||
total: u64,
|
||||
}
|
||||
const QUADS: u32 = u32::MAX;
|
||||
@@ -53,10 +54,15 @@ const GROUP_LIMIT: u32 = 65535;
|
||||
/// below it. A call names its key: its vertices come from that arena's page.
|
||||
pub const TRANSLUCENT_KEYS: u32 = 1 << 10;
|
||||
|
||||
/// terrain.wgsl's `arena_page`: the page the solid draws read their vertices from.
|
||||
pub const ARENA_BINDING: u32 = 19;
|
||||
|
||||
struct PageTable {
|
||||
/// Section id of every block of the page.
|
||||
blocks: wgpu::Buffer,
|
||||
group: Option<wgpu::BindGroup>,
|
||||
/// The section arena's page `group` binds at `ARENA_BINDING` (`bind_pages`).
|
||||
arena: Option<wgpu::Buffer>,
|
||||
}
|
||||
|
||||
/// Writes the fragment's alpha over the frame's and leaves its colour.
|
||||
@@ -81,8 +87,9 @@ pub struct Flat {
|
||||
/// The solid draws in one pass (`fs_solid`), where the GPU doesn't hide pixels
|
||||
/// by tiles (`split_solid` in `Renderer::draw_frame`: elsewhere than Metal).
|
||||
pub solid: wgpu::RenderPipeline,
|
||||
/// The solid draws in two passes over the same draws (terrain.wgsl's `opaque_quad`):
|
||||
/// the quads with no cutout first, through a shader that never discards, then the rest.
|
||||
/// The solid draws in two passes, each over its own part of the draws (terrain.wgsl's
|
||||
/// `opaque_quad`): the quads with no cutout first, through a shader that never discards,
|
||||
/// then the rest.
|
||||
pub solid_opaque: wgpu::RenderPipeline,
|
||||
pub solid_cutout: wgpu::RenderPipeline,
|
||||
/// The solid draws as the water mirror's camera draws them (`fs_solid_mirror`).
|
||||
@@ -128,8 +135,9 @@ pub struct Flat {
|
||||
pub phase1_calls: usize,
|
||||
pub solid_calls: usize,
|
||||
/// Metal's two solid passes over phase 1: the opaque one draws
|
||||
/// `calls[..pass_calls[0]]`, the cutout one `calls[pass_calls[1]..phase1_calls]`.
|
||||
pub pass_calls: [usize; 2],
|
||||
/// `calls[..pass_calls[0]]`, the cutout one `calls[pass_calls[1]..phase1_calls]`;
|
||||
/// over phase 2, `calls[phase1_calls..pass_calls[2]]` and the rest.
|
||||
pub pass_calls: [usize; 3],
|
||||
/// Where a shader pack's shadow camera's solid and translucent calls
|
||||
/// begin (`calls.len()` without them): the camera's translucent calls
|
||||
/// end at the first.
|
||||
@@ -137,6 +145,10 @@ pub struct Flat {
|
||||
/// Frames with fewer sections in view keep the per-section draws.
|
||||
pub min_sections: usize,
|
||||
range_align: u64,
|
||||
/// Phase 2's calls draw their page key as their first instance, as `draw_paged` does:
|
||||
/// the visibility buffer's ID pass (`visbuffer`) reads it. The other flat pipelines
|
||||
/// have no instance-rate inputs.
|
||||
pub page_instances: bool,
|
||||
}
|
||||
|
||||
/// Sections in view (and distant tiles) from which a frame is drawn flat.
|
||||
@@ -247,7 +259,8 @@ fn buffer(
|
||||
/// instead of a destination: the call's room holds all of its draws. Draws
|
||||
/// from `shadow[0]` (solid) and `shadow[1]` (translucent) on are a shader
|
||||
/// pack's shadow camera's, with calls of their own. Phase 1's draws before
|
||||
/// `passes[0]` and from `passes[1]` on, the two solid passes' on Metal, too.
|
||||
/// `passes[0]` and from `passes[1]` on, the two solid passes' on Metal, too,
|
||||
/// and phase 2's from `passes[2]` on (its cutout pass's).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn plan(
|
||||
draws: &[[u32; 5]],
|
||||
@@ -255,7 +268,7 @@ fn plan(
|
||||
phase1_draws: usize,
|
||||
solid_draws: usize,
|
||||
shadow: [usize; 2],
|
||||
passes: [usize; 2],
|
||||
passes: [usize; 3],
|
||||
align: u64,
|
||||
key: impl Fn(u32, Option<u32>) -> u32,
|
||||
calls: &mut Vec<Call>,
|
||||
@@ -267,7 +280,7 @@ fn plan(
|
||||
segments.clear();
|
||||
let (mut phase1_calls, mut solid_calls) = (None, None);
|
||||
let mut shadow_calls = [None; 2];
|
||||
let mut pass_calls = [None; 2];
|
||||
let mut pass_calls = [None; 3];
|
||||
let mut total = 0u64;
|
||||
let mut by_source: Vec<(Source, Vec<Range>)> = Vec::new();
|
||||
for (i, (d, &(page, sorted))) in draws.iter().zip(pages).enumerate() {
|
||||
@@ -329,14 +342,22 @@ fn plan(
|
||||
phase1_calls,
|
||||
solid_calls,
|
||||
shadow_calls: shadow_calls.map(|c| c.unwrap_or(calls.len())),
|
||||
pass_calls: pass_calls.map(|c| c.unwrap_or(calls.len()).min(phase1_calls)),
|
||||
pass_calls: {
|
||||
let [opaque, cutout, phase2] = pass_calls.map(|c| c.unwrap_or(calls.len()));
|
||||
[
|
||||
opaque.min(phase1_calls),
|
||||
cutout.min(phase1_calls),
|
||||
phase2.clamp(phase1_calls, solid_calls),
|
||||
]
|
||||
},
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
impl Flat {
|
||||
/// `pipeline(layout, label, vertex entry, fragment entry, blend, cull)` builds a
|
||||
/// terrain pipeline as the per-section ones, without the origin buffer.
|
||||
/// `pipeline(layout, label, vertex entry, fragment entry, blend, cull, pulled)` builds a
|
||||
/// terrain pipeline as the per-section ones, without the origin buffer, and without
|
||||
/// vertex buffers when `pulled` (its vertex shader reads the page, `ARENA_BINDING`).
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
terrain_layout: &wgpu::BindGroupLayout,
|
||||
@@ -349,13 +370,18 @@ impl Flat {
|
||||
&str,
|
||||
Option<wgpu::BlendState>,
|
||||
Option<wgpu::Face>,
|
||||
bool,
|
||||
) -> wgpu::RenderPipeline,
|
||||
) -> Self {
|
||||
let vertex = wgpu::ShaderStages::VERTEX;
|
||||
let compute = wgpu::ShaderStages::COMPUTE;
|
||||
let draw_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Flat section draws"),
|
||||
entries: &[storage(0, vertex, true), storage(1, vertex, true)],
|
||||
entries: &[
|
||||
storage(0, vertex, true),
|
||||
storage(1, vertex, true),
|
||||
storage(ARENA_BINDING, vertex, true),
|
||||
],
|
||||
});
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Flat terrain layout"),
|
||||
@@ -404,38 +430,44 @@ impl Flat {
|
||||
let storage_usage = wgpu::BufferUsages::STORAGE;
|
||||
let back = Some(wgpu::Face::Back);
|
||||
Self {
|
||||
// The solid draws read either layout of the solid quads (`mesher::packed`) from
|
||||
// the page; translucent ones keep their vertex buffer.
|
||||
solid: pipeline(
|
||||
&layout,
|
||||
"Terrain solid (flat)",
|
||||
"vs_terrain_flat",
|
||||
"vs_section_flat",
|
||||
"fs_solid",
|
||||
None,
|
||||
back,
|
||||
true,
|
||||
),
|
||||
solid_opaque: pipeline(
|
||||
&layout,
|
||||
"Terrain solid opaque (flat)",
|
||||
"vs_terrain_flat_opaque",
|
||||
"vs_section_flat_opaque",
|
||||
"fs_opaque",
|
||||
None,
|
||||
back,
|
||||
true,
|
||||
),
|
||||
solid_cutout: pipeline(
|
||||
&layout,
|
||||
"Terrain solid cutout (flat)",
|
||||
"vs_terrain_flat_cutout",
|
||||
"vs_section_flat_cutout",
|
||||
"fs_solid",
|
||||
None,
|
||||
back,
|
||||
true,
|
||||
),
|
||||
// The mirror flips the winding: its solid terrain culls the front.
|
||||
solid_mirror: pipeline(
|
||||
&layout,
|
||||
"Terrain solid mirrored (flat)",
|
||||
"vs_terrain_flat",
|
||||
"vs_section_flat",
|
||||
"fs_solid_mirror",
|
||||
None,
|
||||
Some(wgpu::Face::Front),
|
||||
true,
|
||||
),
|
||||
translucent: pipeline(
|
||||
&layout,
|
||||
@@ -444,6 +476,7 @@ impl Flat {
|
||||
"fs_translucent",
|
||||
Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
back,
|
||||
false,
|
||||
),
|
||||
translucent_mark: pipeline(
|
||||
&layout,
|
||||
@@ -452,6 +485,7 @@ impl Flat {
|
||||
"fs_translucent_mark",
|
||||
Some(ALPHA_ONLY),
|
||||
back,
|
||||
false,
|
||||
),
|
||||
flatten,
|
||||
compute_layout,
|
||||
@@ -490,11 +524,12 @@ impl Flat {
|
||||
calls: Vec::new(),
|
||||
phase1_calls: 0,
|
||||
solid_calls: 0,
|
||||
pass_calls: [0; 2],
|
||||
pass_calls: [0; 3],
|
||||
shadow_calls: [0; 2],
|
||||
min_sections,
|
||||
range_align: (u64::from(device.limits().min_storage_buffer_offset_alignment) / 16)
|
||||
.max(1),
|
||||
page_instances: false,
|
||||
}
|
||||
}
|
||||
/// A section uploaded to `vertices` vertices from `start` of the page keyed `page`:
|
||||
@@ -539,13 +574,15 @@ impl Flat {
|
||||
self.pages.resize_with(page + 1, || None);
|
||||
}
|
||||
let table = self.pages[page].get_or_insert_with(|| PageTable {
|
||||
// Copied whole into the visibility buffer's table of every page (`visbuffer`).
|
||||
blocks: buffer(
|
||||
device,
|
||||
"Section ids by block",
|
||||
blocks * 4,
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
|
||||
),
|
||||
group: None,
|
||||
arena: None,
|
||||
});
|
||||
let first = start / GRAIN;
|
||||
let ids = vec![id; vertices.div_ceil(GRAIN) as usize];
|
||||
@@ -606,7 +643,7 @@ impl Flat {
|
||||
phase1_draws: usize,
|
||||
solid_draws: usize,
|
||||
shadow: [usize; 2],
|
||||
passes: [usize; 2],
|
||||
passes: [usize; 3],
|
||||
phase2: Option<&wgpu::Buffer>,
|
||||
translucent_arena: bool,
|
||||
origins: impl Iterator<Item = (u32, [f32; 4])>,
|
||||
@@ -680,7 +717,7 @@ impl Flat {
|
||||
self.phase1_calls = 0;
|
||||
self.solid_calls = 0;
|
||||
self.shadow_calls = [0; 2];
|
||||
self.pass_calls = [0; 2];
|
||||
self.pass_calls = [0; 3];
|
||||
return false;
|
||||
}
|
||||
if self.range_data.len() as u64 > self.range_capacity {
|
||||
@@ -709,10 +746,11 @@ impl Flat {
|
||||
);
|
||||
// Phase 2's calls start empty, each at the start of its room.
|
||||
self.phase2_call_data.clear();
|
||||
let page_instances = self.page_instances;
|
||||
self.phase2_call_data.extend(
|
||||
self.calls[phase1_calls..solid_calls]
|
||||
.iter()
|
||||
.map(|c| [0, 1, c.first, 0, 0]),
|
||||
.map(|c| [0, 1, c.first, 0, if page_instances { c.page } else { 0 }]),
|
||||
);
|
||||
let phase2_calls = self.phase2_call_data.len() as u64;
|
||||
if phase2_calls > self.phase2_call_capacity {
|
||||
@@ -807,29 +845,53 @@ impl Flat {
|
||||
pass.dispatch_workgroups((*count).min(GROUP_LIMIT), count.div_ceil(GROUP_LIMIT), 1);
|
||||
}
|
||||
drop(pass);
|
||||
for page in self.calls.iter().map(|c| c.page) {
|
||||
let table = self.pages[page as usize]
|
||||
.as_mut()
|
||||
.expect("a drawn page has its block table");
|
||||
if table.group.is_none() {
|
||||
table.group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Flat section draws"),
|
||||
layout: &self.draw_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: table.blocks.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: self.origins.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
/// Gives every page table its bind group (`bind_page`): its blocks, the origins and, for
|
||||
/// a page of the section arena, that page (`arena`, by page) for the solid draws to read.
|
||||
/// Before each frame's draws, flat or not: the per-section solid draws bind the groups too
|
||||
/// (`page_group`), and a page that grew is another buffer.
|
||||
pub fn bind_pages<'a>(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
arena: impl Fn(u32) -> Option<&'a wgpu::Buffer>,
|
||||
) {
|
||||
for (key, slot) in self.pages.iter_mut().enumerate() {
|
||||
let Some(table) = slot else {
|
||||
continue;
|
||||
};
|
||||
let page = (key < TRANSLUCENT_KEYS as usize)
|
||||
.then(|| arena(key as u32))
|
||||
.flatten();
|
||||
if table.group.is_some() && table.arena.as_ref() == page {
|
||||
continue;
|
||||
}
|
||||
table.arena = page.cloned();
|
||||
table.group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Flat section draws"),
|
||||
layout: &self.draw_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: table.blocks.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: self.origins.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: ARENA_BINDING,
|
||||
resource: page.unwrap_or(&self.empty).as_entire_binding(),
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
}
|
||||
/// The bind group of the section arena's page `page` (`bind_pages`), which the solid
|
||||
/// draws read their vertices through.
|
||||
pub fn page_group(&self, page: u32) -> Option<&wgpu::BindGroup> {
|
||||
self.pages.get(page as usize)?.as_ref()?.group.as_ref()
|
||||
}
|
||||
/// After occlusion culling's test: copies the phase 2 draws it saw.
|
||||
pub fn flatten_phase2(
|
||||
&self,
|
||||
@@ -879,6 +941,46 @@ impl Flat {
|
||||
pass.draw_indexed(call.first..call.first + call.count, 0, 0..1);
|
||||
}
|
||||
}
|
||||
/// `draw`, each call as the instance its page key names: the visibility buffer's ID
|
||||
/// pass (`visbuffer`) tells the pages apart by it.
|
||||
pub(super) fn draw_paged<'a>(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
calls: &[Call],
|
||||
vertices: impl Fn(u32) -> wgpu::BufferSlice<'a>,
|
||||
) {
|
||||
pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for call in calls {
|
||||
self.bind_page(pass, call.page, &vertices);
|
||||
pass.draw_indexed(
|
||||
call.first..call.first + call.count,
|
||||
0,
|
||||
call.page..call.page + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Bytes of its buffers: the index list, ranges, origins and block tables.
|
||||
pub fn bytes(&self) -> u64 {
|
||||
let tables: u64 = self.pages.iter().flatten().map(|t| t.blocks.size()).sum();
|
||||
self.indices.size()
|
||||
+ self.ranges.size()
|
||||
+ self.origins.size()
|
||||
+ self.phase2_calls.size()
|
||||
+ tables
|
||||
}
|
||||
/// The origins by section id, which the visibility buffer's resolve (`visbuffer`) reads
|
||||
/// in its fragment shader.
|
||||
pub(super) fn origins(&self) -> &wgpu::Buffer {
|
||||
&self.origins
|
||||
}
|
||||
/// The block table of the section arena's page `page` (`blocks_per_page` entries), which
|
||||
/// the visibility buffer copies into its table of every page.
|
||||
pub(super) fn block_table(&self, page: u32) -> Option<&wgpu::Buffer> {
|
||||
Some(&self.pages.get(page as usize)?.as_ref()?.blocks)
|
||||
}
|
||||
pub(super) fn blocks_per_page(&self) -> u64 {
|
||||
self.blocks_per_page
|
||||
}
|
||||
/// Draws `calls` without their index buffer, `instances` times: a shader pack's geometry
|
||||
/// stage reads the indices itself (`hz_indices`) and stands for one emitted triangle per
|
||||
/// instance.
|
||||
@@ -896,19 +998,22 @@ impl Flat {
|
||||
pass.draw(call.first..call.first + call.count, 0..instances);
|
||||
}
|
||||
}
|
||||
/// Draws phase 2's calls, as `flatten_phase2` left them.
|
||||
/// Draws phase 2's `calls` (`calls[phase1_calls..solid_calls]` counted from 0), as
|
||||
/// `flatten_phase2` left them.
|
||||
pub fn draw_phase2<'a>(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
calls: std::ops::Range<usize>,
|
||||
vertices: impl Fn(u32) -> wgpu::BufferSlice<'a>,
|
||||
) {
|
||||
pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for (k, call) in self.calls[self.phase1_calls..self.solid_calls]
|
||||
let first = self.phase1_calls + calls.start;
|
||||
for (k, call) in self.calls[first..self.phase1_calls + calls.end]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
self.bind_page(pass, call.page, &vertices);
|
||||
pass.draw_indexed_indirect(&self.phase2_calls, k as u64 * 20);
|
||||
pass.draw_indexed_indirect(&self.phase2_calls, (calls.start + k) as u64 * 20);
|
||||
}
|
||||
}
|
||||
fn bind_page<'a>(
|
||||
@@ -968,7 +1073,7 @@ mod tests {
|
||||
solid,
|
||||
solid,
|
||||
[draws.len(); 2],
|
||||
[draws.len(), 0],
|
||||
[draws.len(), 0, solid],
|
||||
4,
|
||||
|page, _| page,
|
||||
&mut calls,
|
||||
@@ -1017,14 +1122,14 @@ mod tests {
|
||||
solid,
|
||||
solid,
|
||||
[draws.len(); 2],
|
||||
[3, 1],
|
||||
[3, 1, solid],
|
||||
4,
|
||||
|page, _| page,
|
||||
&mut calls,
|
||||
&mut ranges,
|
||||
&mut segments,
|
||||
);
|
||||
assert_eq!((plan.pass_calls, plan.solid_calls), ([3, 1], 4));
|
||||
assert_eq!((plan.pass_calls, plan.solid_calls), ([3, 1, 4], 4));
|
||||
let call = |page, first, count| Call { page, first, count };
|
||||
assert_eq!(
|
||||
calls,
|
||||
@@ -1053,7 +1158,7 @@ mod tests {
|
||||
solid,
|
||||
solid,
|
||||
[camera, camera + 2],
|
||||
[camera, 0],
|
||||
[camera, 0, solid],
|
||||
4,
|
||||
|page, _| page,
|
||||
&mut calls,
|
||||
@@ -1082,7 +1187,7 @@ mod tests {
|
||||
2,
|
||||
solid,
|
||||
[draws.len(); 2],
|
||||
[draws.len(), 0],
|
||||
[draws.len(), 0, solid],
|
||||
4,
|
||||
|page, _| page,
|
||||
&mut calls,
|
||||
@@ -1095,7 +1200,7 @@ mod tests {
|
||||
phase1_calls: 1,
|
||||
solid_calls: 3,
|
||||
shadow_calls: [4, 4],
|
||||
pass_calls: [1, 0],
|
||||
pass_calls: [1, 0, 3],
|
||||
total: 6 * 13
|
||||
}
|
||||
);
|
||||
@@ -1200,7 +1305,7 @@ mod tests {
|
||||
phase1,
|
||||
solid,
|
||||
[draws.len(); 2],
|
||||
[draws.len(), 0],
|
||||
[draws.len(), 0, solid],
|
||||
align,
|
||||
|page, _| page,
|
||||
&mut calls,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,30 +1,42 @@
|
||||
// GPU culling (gfx/gpu_culling.rs). `apply` writes the records that changed.
|
||||
// `cull`: one invocation per section id keeps the sections the CPU's walk
|
||||
// chose, writes each one's origin and counts it in its page, per block of 64
|
||||
// ids. `scan`: one workgroup per page turns the blocks' counts into where
|
||||
// each block's draws start, and the page's count. `scatter`: each block
|
||||
// writes its sections' draws there, in id order, so a page's draws come in
|
||||
// the same order every frame (atomics alone would order them as the GPU
|
||||
// schedules, and where faces of two sections meet at the same depth the later
|
||||
// draw keeps the pixel). Nothing here computes with floats: the origins are
|
||||
// the CPU's bits, copied.
|
||||
// `cull`: one invocation per section id keeps the sections whose column is
|
||||
// within the render distance, that cave culling's graph reaches (occlusion.rs)
|
||||
// and whose box is in the frustum, writes each one's origin and counts it in
|
||||
// its page, per block of 64 ids. `scan`: one workgroup per page turns the
|
||||
// blocks' counts into where each block's draws start, and the page's count.
|
||||
// `scatter`: each block writes its sections' draws there, in id order, so a
|
||||
// page's draws come in the same order every frame (atomics alone would order
|
||||
// them as the GPU schedules, and where faces of two sections meet at the same
|
||||
// depth the later draw keeps the pixel). Nothing here computes with floats: the
|
||||
// origins are the CPU's bits, copied, and the frustum test adds integers the
|
||||
// CPU computed.
|
||||
|
||||
struct Params {
|
||||
// Ids to test, records `apply` writes, the arena pages the counts and
|
||||
// regions cover, and where the regions and the chosen ids' bits start in
|
||||
// `tables`.
|
||||
// regions cover, and where the regions start in `tables`.
|
||||
ids: u32,
|
||||
updates: u32,
|
||||
pages: u32,
|
||||
regions: u32,
|
||||
mask: u32,
|
||||
pad0: u32,
|
||||
pad1: u32,
|
||||
pad2: u32,
|
||||
// Bits 3j, 3j + 1 and 3j + 2: frustum plane j's x, y and z are at least 0.
|
||||
signs: u32,
|
||||
// The render distance in columns, and the camera's column.
|
||||
near: i32,
|
||||
cam_x: i32,
|
||||
cam_z: i32,
|
||||
// The tables' first column: x, 0, z, 0.
|
||||
first: vec4<i32>,
|
||||
// Their entries along x, y and z, and 0.
|
||||
cells: vec4<u32>,
|
||||
// Each frustum plane's w term, its margin in, then 0, 0.
|
||||
w: array<vec4<i32>, 2>,
|
||||
// Cave culling's graph (occlusion.rs `Reach`): from x, from z, radius, lowest
|
||||
// section y; its height in sections and the level's; REACH and SKY; 0.
|
||||
reach: vec4<i32>,
|
||||
reach_height: i32,
|
||||
reach_sections: i32,
|
||||
flags: u32,
|
||||
pad: u32,
|
||||
}
|
||||
|
||||
// gpu_culling.rs `Record`.
|
||||
@@ -32,8 +44,8 @@ struct Section {
|
||||
// Section x, y (from the level's bottom), z, and its arena page (-1: a
|
||||
// free id).
|
||||
key: vec4<i32>,
|
||||
// The first vertex of its solid quads, its solid quads, then two words
|
||||
// unused here.
|
||||
// The first vertex of its solid quads, its solid quads, its box
|
||||
// (gpu_culling.rs `rounded_box`), then a word unused here.
|
||||
a: vec4<u32>,
|
||||
}
|
||||
|
||||
@@ -45,9 +57,10 @@ struct Update {
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var<storage, read_write> sections: array<Section>;
|
||||
// gpu_culling.rs `GpuCulling::prepare`: the origins' f32 bits along x, y and
|
||||
// z, each page's region of `draws` (its first draw and its size), then a bit
|
||||
// per id the CPU's walk chose.
|
||||
// gpu_culling.rs `tables`: the origins' f32 bits along x, y and z (n of them);
|
||||
// per frustum plane its terms for those origins, n each; per plane and axis its
|
||||
// terms for the 32 box codes; then each page's region of `draws` (its first
|
||||
// draw and its size).
|
||||
@group(0) @binding(2) var<storage, read> tables: array<u32>;
|
||||
@group(0) @binding(3) var<storage, read> updates: array<Update>;
|
||||
// Sections kept, draws, each page's draws, then per block of ids and page its
|
||||
@@ -59,6 +72,9 @@ struct Update {
|
||||
@group(0) @binding(6) var<storage, read_write> origins: array<vec4<u32>>;
|
||||
// Per id its page + 1 when it is drawn this frame, else 0.
|
||||
@group(0) @binding(7) var<storage, read_write> runs: array<u32>;
|
||||
// occlusion.rs `Reach::bits`: whether the search reached each cell of its box,
|
||||
// a bit each (32 a word, lowest first).
|
||||
@group(0) @binding(8) var<storage, read> reached_cells: array<u32>;
|
||||
|
||||
// gfx.rs `section_instance`'s w for a whole section: 1 / mesher::UV_STEPS.
|
||||
const STEP_BITS: u32 = 0x38000000u;
|
||||
@@ -68,6 +84,13 @@ const ROW: u32 = 65535u;
|
||||
const BLOCK: u32 = 64u;
|
||||
// Counts before the pages'.
|
||||
const STATS: u32 = 2u;
|
||||
// gpu_culling.rs `rounded_box` for a box past what its codes hold: no plane
|
||||
// rejects it.
|
||||
const UNBOUNDED: u32 = 0x80000000u;
|
||||
// In `params.flags`: cave culling's graph is in `params.reach`, and its search
|
||||
// reached the box's top layer (the sky band).
|
||||
const REACH: u32 = 1u;
|
||||
const SKY: u32 = 2u;
|
||||
|
||||
// The count of `block`'s draws in `page` (`cull`), then where they start (`scan`).
|
||||
fn block_count(block: u32, page: u32) -> u32 {
|
||||
@@ -80,9 +103,69 @@ fn region(page: u32) -> vec2<u32> {
|
||||
return vec2<u32>(tables[at], tables[at + 1u]);
|
||||
}
|
||||
|
||||
// The CPU's walk chose it (gpu_culling.rs `GpuCulling::choose`).
|
||||
fn chosen(id: u32) -> bool {
|
||||
return ((tables[params.mask + id / 32u] >> (id % 32u)) & 1u) != 0u;
|
||||
// streaming.rs `in_view_distance` around the camera's column. A gap at or past
|
||||
// the distance cannot pass, so testing it first keeps the squares in range.
|
||||
fn near(x: i32, z: i32) -> bool {
|
||||
let r = params.near;
|
||||
let gx = max(abs(x - params.cam_x) - 1, 0);
|
||||
let gz = max(abs(z - params.cam_z) - 1, 0);
|
||||
if gx >= r || gz >= r {
|
||||
return false;
|
||||
}
|
||||
return gx * gx + gz * gz < r * r;
|
||||
}
|
||||
|
||||
// occlusion.rs `Occlusion::contains` (`Graph::contains`): with no graph, or with
|
||||
// cave culling off, every section; in the searched box, whether the search
|
||||
// reached it; above a clamped box, over its columns and within the level, the
|
||||
// sky band (all of it or none); else none. `Reach::contains` on the CPU.
|
||||
fn reached(key: vec3<i32>) -> bool {
|
||||
if (params.flags & REACH) == 0u {
|
||||
return true;
|
||||
}
|
||||
let r = params.reach.z;
|
||||
let dx = key.x - params.reach.x;
|
||||
let dz = key.z - params.reach.y;
|
||||
let dy = key.y - params.reach.w;
|
||||
if dx < -r || dx > r || dz < -r || dz > r || dy < 0 {
|
||||
return false;
|
||||
}
|
||||
if dy >= params.reach_height {
|
||||
return (params.flags & SKY) != 0u && key.y >= 0 && key.y <= params.reach_sections;
|
||||
}
|
||||
let i = u32(((dz + r) * (2 * r + 1) + dx + r) * params.reach_height + dy);
|
||||
return ((reached_cells[i >> 5u] >> (i & 31u)) & 1u) != 0u;
|
||||
}
|
||||
|
||||
fn term(at: u32) -> i32 {
|
||||
return bitcast<i32>(tables[at]);
|
||||
}
|
||||
|
||||
// gfx.rs `Frustum::intersects` for the box `b` of the section in cell `c` of the
|
||||
// tables, in integers (gpu_culling.rs `tables`): it keeps every box the CPU keeps.
|
||||
fn in_frustum(c: vec3<u32>, b: u32) -> bool {
|
||||
if (b & UNBOUNDED) != 0u {
|
||||
return true;
|
||||
}
|
||||
let nx = params.cells.x;
|
||||
let ny = params.cells.y;
|
||||
let n = nx + ny + params.cells.z;
|
||||
for (var j = 0u; j < 6u; j++) {
|
||||
// The box's codes at the corner the plane looks at.
|
||||
let s = params.signs >> (3u * j);
|
||||
let kx = (b >> select(0u, 15u, (s & 1u) != 0u)) & 31u;
|
||||
let ky = (b >> select(5u, 20u, (s & 2u) != 0u)) & 31u;
|
||||
let kz = (b >> select(10u, 25u, (s & 4u) != 0u)) & 31u;
|
||||
let origin_terms = n + j * n;
|
||||
let code_terms = 7u * n + 96u * j;
|
||||
let sum = params.w[j / 4u][j % 4u] + term(origin_terms + c.x)
|
||||
+ term(origin_terms + nx + c.y) + term(origin_terms + nx + ny + c.z)
|
||||
+ term(code_terms + kx) + term(code_terms + 32u + ky) + term(code_terms + 64u + kz);
|
||||
if sum < 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// One invocation per changed record; the CPU lists each id once.
|
||||
@@ -97,19 +180,20 @@ fn apply(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
@compute @workgroup_size(64)
|
||||
fn cull(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let id = gid.y * (ROW * BLOCK) + gid.x;
|
||||
// Past the table's end the records are stale: the CPU never writes them free.
|
||||
if id >= params.ids {
|
||||
return;
|
||||
}
|
||||
runs[id] = 0u;
|
||||
let s = sections[id];
|
||||
if s.key.w < 0 || s.a.y == 0u || !chosen(id) {
|
||||
if s.key.w < 0 || s.a.y == 0u || !near(s.key.x, s.key.z) || !reached(s.key.xyz) {
|
||||
return;
|
||||
}
|
||||
let page = u32(s.key.w);
|
||||
// The tables hold every column within the distance; a key before their
|
||||
// first wraps past their end.
|
||||
let c = vec3<u32>(s.key.xyz - params.first.xyz);
|
||||
if page >= params.pages || any(c >= params.cells.xyz) {
|
||||
if page >= params.pages || any(c >= params.cells.xyz) || !in_frustum(c, s.a.z) {
|
||||
return;
|
||||
}
|
||||
let nx = params.cells.x;
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
//! Every render pipeline's stage interface against the weakest GPUs' limit (#8195).
|
||||
//!
|
||||
//! OpenGL 3.3 drivers (a GeForce 9100M G) allow `max_inter_stage_shader_variables` = 15, and wgpu
|
||||
//! counts the fragment stage's built-in inputs (`front_facing`, `sample_index`, ...) against it.
|
||||
//! A pipeline over that limit panics at start-up on such a GPU, with no way to run the game, so
|
||||
//! this checks the shaders with naga, without a GPU.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// `Limits::downlevel_defaults().max_inter_stage_shader_variables`, the least a GPU offers.
|
||||
const LIMIT: u32 = 15;
|
||||
|
||||
/// One entry point's variables: the user-defined `@location`s and the built-ins wgpu counts.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Interface {
|
||||
entry: String,
|
||||
/// Vertex outputs or fragment inputs, user-defined.
|
||||
user: u32,
|
||||
/// The fragment stage's `front_facing`, `sample_index`, `sample_mask` and `primitive_index`
|
||||
/// inputs, which wgpu counts as one variable each; 0 for a vertex stage.
|
||||
built_ins: u32,
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
fn total(&self) -> u32 {
|
||||
self.user + self.built_ins
|
||||
}
|
||||
}
|
||||
|
||||
/// The bindings an argument or a result carries, a struct's members flattened.
|
||||
fn bindings(
|
||||
module: &naga::Module,
|
||||
binding: &Option<naga::Binding>,
|
||||
ty: naga::Handle<naga::Type>,
|
||||
) -> Vec<naga::Binding> {
|
||||
if let Some(binding) = binding {
|
||||
return vec![binding.clone()];
|
||||
}
|
||||
match &module.types[ty].inner {
|
||||
naga::TypeInner::Struct { members, .. } => {
|
||||
members.iter().filter_map(|m| m.binding.clone()).collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_location(binding: &naga::Binding) -> bool {
|
||||
matches!(binding, naga::Binding::Location { .. })
|
||||
}
|
||||
|
||||
/// The stage interface of every vertex and fragment entry point of `source`.
|
||||
fn interfaces(source: &str) -> Vec<Interface> {
|
||||
let module = naga::front::wgsl::parse_str(source).unwrap();
|
||||
let mut out = Vec::new();
|
||||
for entry in &module.entry_points {
|
||||
let function = &entry.function;
|
||||
let (user, built_ins) = match entry.stage {
|
||||
naga::ShaderStage::Vertex => {
|
||||
let outputs: Vec<_> = function
|
||||
.result
|
||||
.iter()
|
||||
.flat_map(|r| bindings(&module, &r.binding, r.ty))
|
||||
.collect();
|
||||
(outputs.iter().filter(|b| is_location(b)).count(), 0)
|
||||
}
|
||||
naga::ShaderStage::Fragment => {
|
||||
let inputs: Vec<_> = function
|
||||
.arguments
|
||||
.iter()
|
||||
.flat_map(|a| bindings(&module, &a.binding, a.ty))
|
||||
.collect();
|
||||
let built_ins = inputs
|
||||
.iter()
|
||||
.filter(|b| {
|
||||
matches!(
|
||||
b,
|
||||
naga::Binding::BuiltIn(
|
||||
naga::BuiltIn::FrontFacing
|
||||
| naga::BuiltIn::SampleIndex
|
||||
| naga::BuiltIn::SampleMask
|
||||
| naga::BuiltIn::PrimitiveIndex
|
||||
)
|
||||
)
|
||||
})
|
||||
.count();
|
||||
(inputs.iter().filter(|b| is_location(b)).count(), built_ins)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
out.push(Interface {
|
||||
entry: entry.name.clone(),
|
||||
user: user as u32,
|
||||
built_ins: built_ins as u32,
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The shaders as the renderer assembles them, by the `.wgsl` files each is made of.
|
||||
fn composed() -> Vec<(&'static str, Vec<&'static str>, String)> {
|
||||
let entity = crate::overlay::wgsl_constants() + include_str!("../entity.wgsl");
|
||||
vec![
|
||||
("entity", vec!["entity.wgsl"], entity.clone()),
|
||||
(
|
||||
"static entity",
|
||||
vec!["entity.wgsl", "static_entity.wgsl"],
|
||||
entity.clone() + include_str!("../static_entity.wgsl"),
|
||||
),
|
||||
(
|
||||
"improved transparency clouds",
|
||||
vec!["entity.wgsl", "oit.wgsl", "oit_clouds.wgsl"],
|
||||
entity.clone() + include_str!("../oit.wgsl") + include_str!("../oit_clouds.wgsl"),
|
||||
),
|
||||
(
|
||||
"improved transparency features",
|
||||
vec!["entity.wgsl", "oit.wgsl", "oit_features.wgsl"],
|
||||
entity + include_str!("../oit.wgsl") + include_str!("../oit_features.wgsl"),
|
||||
),
|
||||
(
|
||||
"improved transparency world",
|
||||
vec!["terrain.wgsl", "oit.wgsl", "oit_world.wgsl"],
|
||||
include_str!("../terrain.wgsl").to_owned()
|
||||
+ include_str!("../oit.wgsl")
|
||||
+ include_str!("../oit_world.wgsl"),
|
||||
),
|
||||
(
|
||||
"visibility buffer",
|
||||
vec!["terrain.wgsl", "visbuffer.wgsl"],
|
||||
include_str!("../terrain.wgsl").to_owned() + include_str!("../visbuffer.wgsl"),
|
||||
),
|
||||
(
|
||||
"improved transparency world border",
|
||||
vec!["border.wgsl", "oit.wgsl", "oit_border.wgsl"],
|
||||
include_str!("../border.wgsl").to_owned()
|
||||
+ include_str!("../oit.wgsl")
|
||||
+ include_str!("../oit_border.wgsl"),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn wgsl_files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
for entry in std::fs::read_dir(dir).unwrap().flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
wgsl_files(&path, out);
|
||||
} else if path.extension().is_some_and(|e| e == "wgsl") {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every shader: the assembled ones, and each `.wgsl` file that stands alone. A file that does
|
||||
/// not (it needs another's declarations) must be part of an assembled one, so that a new shader
|
||||
/// cannot slip past the audit.
|
||||
fn audited() -> Vec<(String, Vec<Interface>)> {
|
||||
let composed = composed();
|
||||
let mut files = Vec::new();
|
||||
wgsl_files(
|
||||
&Path::new(env!("CARGO_MANIFEST_DIR")).join("src"),
|
||||
&mut files,
|
||||
);
|
||||
files.sort();
|
||||
let mut out: Vec<(String, Vec<Interface>)> = composed
|
||||
.iter()
|
||||
.map(|(name, _, source)| ((*name).to_owned(), interfaces(source)))
|
||||
.collect();
|
||||
for file in files {
|
||||
let name = file.file_name().unwrap().to_string_lossy().into_owned();
|
||||
let source = std::fs::read_to_string(&file).unwrap();
|
||||
let alone = naga::front::wgsl::parse_str(&source).is_ok();
|
||||
let part = composed.iter().any(|(_, parts, _)| parts.contains(&&*name));
|
||||
assert!(
|
||||
alone || part,
|
||||
"{name} needs other files' declarations: add its assembly to `composed`"
|
||||
);
|
||||
if alone {
|
||||
out.push((name, interfaces(&source)));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_pipeline_exceeds_the_weakest_gpus_inter_stage_limit() {
|
||||
let mut over = Vec::new();
|
||||
for (shader, entries) in audited() {
|
||||
for e in entries {
|
||||
if e.total() > LIMIT {
|
||||
over.push(format!(
|
||||
"{shader}::{}: {} user-defined + {} built-in = {} > {LIMIT}",
|
||||
e.entry,
|
||||
e.user,
|
||||
e.built_ins,
|
||||
e.total()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(over.is_empty(), "over the limit:\n{}", over.join("\n"));
|
||||
}
|
||||
|
||||
/// The audit counts what wgpu counts (the failure of #8195: 15 user-defined + `FrontFacing`).
|
||||
#[test]
|
||||
fn a_front_facing_input_counts_against_the_limit() {
|
||||
let source = "struct Io { @builtin(position) p: vec4<f32>, @location(0) a: vec4<f32>, @location(1) b: f32 }
|
||||
@vertex fn vs() -> Io { return Io(vec4<f32>(0.0), vec4<f32>(0.0), 0.0); }
|
||||
@fragment fn fs(in: Io, @builtin(front_facing) f: bool) -> @location(0) vec4<f32> { return in.a; }";
|
||||
assert_eq!(
|
||||
interfaces(source),
|
||||
vec![
|
||||
Interface {
|
||||
entry: "vs".into(),
|
||||
user: 2,
|
||||
built_ins: 0
|
||||
},
|
||||
Interface {
|
||||
entry: "fs".into(),
|
||||
user: 2,
|
||||
built_ins: 1
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -63,6 +63,9 @@ pub(super) struct Tile {
|
||||
/// place until they have faded in (from the job's end, the instant), so
|
||||
/// no hole shows through them.
|
||||
previous: Option<(SectionGpu, Option<Instant>)>,
|
||||
/// Its slot in occlusion culling's results (`cull::TILE`), kept while
|
||||
/// meshes of the same tile replace one another.
|
||||
pub cull_slot: u32,
|
||||
}
|
||||
|
||||
/// How long a tile takes to fade in over the ones it replaces.
|
||||
@@ -160,6 +163,18 @@ impl Tile {
|
||||
None => &mut self.mesh,
|
||||
}
|
||||
}
|
||||
/// The box occlusion culling tests (`cull::Cull`), around its origin: its
|
||||
/// mesh's bounds grown by a block, while that mesh is the one drawn
|
||||
/// whole (no stand-in, no fade). A fading tile's dither moves its depth
|
||||
/// toward the camera (terrain.wgsl's `fade_depth`), out of the box.
|
||||
pub fn cull_bounds(&self, fade: u32) -> Option<[[f32; 3]; 2]> {
|
||||
(fade == 0 && self.previous.is_none()).then(|| {
|
||||
[
|
||||
(self.bounds.min - 1.0).to_array(),
|
||||
(self.bounds.max + 1.0).to_array(),
|
||||
]
|
||||
})
|
||||
}
|
||||
/// Both meshes it holds in the arena: its own and the stand-in, if any.
|
||||
pub fn meshes_mut(&mut self) -> impl Iterator<Item = &mut SectionGpu> {
|
||||
std::iter::once(&mut self.mesh).chain(self.previous.as_mut().map(|(mesh, _)| mesh))
|
||||
@@ -192,6 +207,10 @@ impl Renderer {
|
||||
/// its old mesh stands in while they fade in. A new one waits for the rest of its job
|
||||
/// (`upload_lods`), then fades in.
|
||||
pub fn upload_lod(&mut self, key: TileKey, mesh: &SectionMesh, keep: bool) {
|
||||
if let Some(copies) = &mut self.capture_copies {
|
||||
copies.tile(key, mesh);
|
||||
}
|
||||
let old_slot = self.lods.get(&key).map(|tile| tile.cull_slot);
|
||||
let (shown, previous) = match self.lods.remove(&key) {
|
||||
Some(old) if old.dying.is_none() => match old.previous {
|
||||
Some(previous) => {
|
||||
@@ -212,6 +231,12 @@ impl Renderer {
|
||||
};
|
||||
match Bounds::mesh(mesh).zip(self.upload_mesh(mesh)) {
|
||||
Some((bounds, mesh)) => {
|
||||
let cull_slot = old_slot.unwrap_or_else(|| {
|
||||
self.lod_cull_free.pop().unwrap_or_else(|| {
|
||||
self.lod_cull_next += 1;
|
||||
self.lod_cull_next - 1
|
||||
})
|
||||
});
|
||||
self.lods.insert(
|
||||
key,
|
||||
Tile {
|
||||
@@ -221,6 +246,7 @@ impl Renderer {
|
||||
dying: None,
|
||||
alone: false,
|
||||
previous,
|
||||
cull_slot,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -228,6 +254,7 @@ impl Renderer {
|
||||
if let Some((previous, _)) = previous {
|
||||
self.release_mesh(previous);
|
||||
}
|
||||
self.lod_cull_free.extend(old_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,12 +267,19 @@ impl Renderer {
|
||||
}
|
||||
|
||||
pub fn remove_lod(&mut self, key: TileKey) {
|
||||
if let Some(copies) = &mut self.capture_copies {
|
||||
copies.forget_tile(key);
|
||||
}
|
||||
if let Some(tile) = self.lods.remove(&key) {
|
||||
self.lod_cull_free.push(tile.cull_slot);
|
||||
self.release_tile(tile);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_lods(&mut self) {
|
||||
if let Some(copies) = &mut self.capture_copies {
|
||||
copies.clear_tiles();
|
||||
}
|
||||
self.lod_pending = None;
|
||||
// No far terrain left to fade out over.
|
||||
for (key, since) in std::mem::take(&mut self.lod_leaving) {
|
||||
@@ -260,6 +294,8 @@ impl Renderer {
|
||||
for (_, tile) in std::mem::take(&mut self.lods) {
|
||||
self.release_tile(tile);
|
||||
}
|
||||
self.lod_cull_free.clear();
|
||||
self.lod_cull_next = 0;
|
||||
self.set_lod_cutout(Default::default());
|
||||
}
|
||||
|
||||
@@ -437,6 +473,15 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tiles drawn for good, not fading out (`render-capture`).
|
||||
pub(super) fn standing_lods(&self) -> FxHashSet<TileKey> {
|
||||
self.lods
|
||||
.iter()
|
||||
.filter(|(_, tile)| tile.dying.is_none())
|
||||
.map(|(&key, _)| key)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The chunk columns the resident tiles now leave out (`Lod::take_cutout`).
|
||||
/// Removed sections they no longer leave out are released.
|
||||
pub fn set_lod_cutout(&mut self, cutout: crate::fast_hash::FxHashSet<(i32, i32)>) {
|
||||
|
||||
@@ -41,17 +41,28 @@ struct HorizonMaterialOut {
|
||||
@location(10) @interpolate(flat) emission: f32,
|
||||
// terrain.wgsl's `emission`, for HDR output's mark.
|
||||
@location(11) @interpolate(flat) hdr_code: u32,
|
||||
// The point on the flat face (`position` without `[displacement]`'s relief), how much of
|
||||
// that relief it has (`MaterialInput.displaced`) and the face's normal (0 when the
|
||||
// fragment finds it).
|
||||
@location(12) base: vec3<f32>,
|
||||
@location(13) displaced: f32,
|
||||
@location(14) @interpolate(flat) normal: vec3<f32>,
|
||||
}
|
||||
fn horizon_material_vertex(position: vec3<f32>, uv: vec2<f32>, layer: u32, color: vec4<f32>,
|
||||
light: vec2<u32>, origin: vec3<f32>) -> HorizonMaterialOut {
|
||||
let pos = horizon_material_displace(position + origin, uv, layer, color, light);
|
||||
return horizon_material_out(pos, uv, layer, color, vec2<f32>(light));
|
||||
}
|
||||
// `light`: sky, block, 16 per level.
|
||||
fn horizon_material_out(pos: vec3<f32>, uv: vec2<f32>, layer: u32, color: vec4<f32>,
|
||||
light: vec2<f32>) -> HorizonMaterialOut {
|
||||
var out: HorizonMaterialOut;
|
||||
out.clip = horizon_terrain_globals.view_proj * vec4(pos, 1.0);
|
||||
out.uv = uv;
|
||||
out.layer = layer;
|
||||
out.tint = vec4(color.rgb, 1.0);
|
||||
let block = max(f32(light.y), horizon_terrain_dynamic_block_light(pos));
|
||||
let sky = f32(light.x);
|
||||
let block = max(light.y, horizon_terrain_dynamic_block_light(pos));
|
||||
let sky = light.x;
|
||||
// 16 per level, 15 levels.
|
||||
out.light = min(vec2(block, sky) / 240.0, vec2(1.0));
|
||||
out.lightmap = horizon_terrain_sample_lightmap(sky / 16.0, block / 16.0).rgb;
|
||||
@@ -73,6 +84,9 @@ fn horizon_material_vertex(position: vec3<f32>, uv: vec2<f32>, layer: u32, color
|
||||
// Codes above 4 are the same tiers, shining all over rather than in their bright parts.
|
||||
let tier = select(out.hdr_code, out.hdr_code - 4u, out.hdr_code > 4u);
|
||||
out.emission = select(min(f32(tier) * 7.0 / 15.0, 1.0), 0.0, tier == 0u);
|
||||
out.base = pos;
|
||||
out.displaced = 0.0;
|
||||
out.normal = vec3(0.0);
|
||||
return out;
|
||||
}
|
||||
@vertex fn horizon_material_vs(
|
||||
@@ -142,15 +156,17 @@ fn horizon_material_color(in: HorizonMaterialOut, translucent: bool) -> vec4<f32
|
||||
horizon_block_lod = max(0.5 * log2(max(footprint, 1e-8)), 0.0);
|
||||
// Water draws translucent and lava solid, so the pass resolves the fluid kind.
|
||||
let fluid = select(0u, select(2u, 1u, translucent), in.fluid_quad != 0u);
|
||||
let normal = horizon_material_normal(in.position);
|
||||
// `[displacement]`'s grid hands down its face's normal: its relief is the pack's to shade.
|
||||
let normal = select(horizon_material_normal(in.position), in.normal,
|
||||
dot(in.normal, in.normal) > 0.5);
|
||||
horizon_material_face_normal = normal;
|
||||
horizon_surface = vec4<f32>(0.0);
|
||||
let tangent = horizon_material_tangent(in.position, in.uv, normal);
|
||||
let tangent = horizon_material_tangent(in.base, in.uv, normal);
|
||||
return material(MaterialInput(albedo, in.tint, in.light, in.lightmap, in.position, in.uv,
|
||||
translucent, horizon_sprite_tags(in.layer & 0x7fffu),
|
||||
horizon_sprite_material(in.layer & 0x7fffu), in.clip.xy / frame.resolution,
|
||||
in.face, fluid, in.emission, normal, tangent,
|
||||
horizon_terrain_native_colored_light(in.position))).color;
|
||||
horizon_terrain_native_colored_light(in.position), in.displaced)).color;
|
||||
}
|
||||
var<private> horizon_material_face_normal: vec3<f32>;
|
||||
struct HorizonMaterialSolid {
|
||||
@@ -176,6 +192,13 @@ fn horizon_material_mark(in: HorizonMaterialOut, texel: vec4<f32>) -> f32 {
|
||||
return 1.0;
|
||||
}
|
||||
@fragment fn horizon_material_fs_solid(in: HorizonMaterialOut) -> HorizonMaterialSolid {
|
||||
// `[displacement]`'s grid draws these pixels.
|
||||
if length(in.base) < horizon_displace_distance - 0.05 && horizon_displaced_layer(in.layer) {
|
||||
discard;
|
||||
}
|
||||
return horizon_material_solid(in);
|
||||
}
|
||||
fn horizon_material_solid(in: HorizonMaterialOut) -> HorizonMaterialSolid {
|
||||
let color = horizon_material_color(in, false);
|
||||
let texel = horizon_terrain_sprite(in.uv, in.layer & 0x7fffu);
|
||||
// Bit 15 of the layer marks opaque leaves (forceOpaque): no alpha cutout.
|
||||
@@ -196,6 +219,37 @@ fn horizon_material_mark(in: HorizonMaterialOut, texel: vec4<f32>) -> f32 {
|
||||
}
|
||||
";
|
||||
|
||||
/// `[displacement]`'s grid (gfx/displace.rs): one instance a quad the pick chose, its corners
|
||||
/// read from the copy of the arena, each of its cells two triangles moved along the face's
|
||||
/// normal by the pack's `displacement`, measured from the texture's mean height so that the
|
||||
/// face's edges, pinned there, meet their neighbours whatever their relief.
|
||||
const DISPLACED_ENTRIES: &str = "
|
||||
@vertex fn horizon_material_vs_displaced(@builtin(vertex_index) v: u32,
|
||||
@builtin(instance_index) instance: u32) -> HorizonMaterialOut {
|
||||
let p = horizon_displace_point(v, instance);
|
||||
var out = horizon_material_out(p.position, p.uv, p.layer, p.color, p.light);
|
||||
out.base = p.base;
|
||||
out.displaced = p.near;
|
||||
out.normal = p.normal;
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_material_fs_displaced(in: HorizonMaterialOut) -> HorizonMaterialSolid {
|
||||
if length(in.base) >= horizon_displace_distance + 0.05 {
|
||||
discard;
|
||||
}
|
||||
return horizon_material_solid(in);
|
||||
}
|
||||
";
|
||||
|
||||
/// What the material's displaced pipeline is built against: `[displacement]`'s values and
|
||||
/// its group 1.
|
||||
pub struct DisplacedShape<'a> {
|
||||
pub distance: f32,
|
||||
pub cells: u32,
|
||||
pub steps: bool,
|
||||
pub layout: &'a wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
/// What terrain pipelines are built against: the renderer's layouts and constants.
|
||||
pub struct TerrainShape<'a> {
|
||||
/// The terrain's group 0.
|
||||
@@ -227,6 +281,8 @@ pub struct NativeMaterial {
|
||||
pub solid: wgpu::RenderPipeline,
|
||||
pub translucent: wgpu::RenderPipeline,
|
||||
pub flat: Option<(wgpu::RenderPipeline, wgpu::RenderPipeline)>,
|
||||
/// `[displacement]`'s grid of the solid terrain near the camera (gfx/displace.rs).
|
||||
pub displaced: Option<wgpu::RenderPipeline>,
|
||||
/// The stage calls `set_surface`: its solid pipelines also write the surface target.
|
||||
pub writes_surface: bool,
|
||||
}
|
||||
@@ -290,9 +346,10 @@ fn shader_source(
|
||||
tags: &[(u32, u32)],
|
||||
materials: &[(u32, u32)],
|
||||
vertex: bool,
|
||||
displaced: bool,
|
||||
) -> String {
|
||||
format!(
|
||||
"{source}\n// Horizon's terrain (terrain.wgsl)\n{}{}{}{}{ENTRIES}",
|
||||
"{source}\n// Horizon's terrain (terrain.wgsl)\n{}{}{}{}{ENTRIES}{}{}",
|
||||
vanilla_terrain(),
|
||||
if vertex {
|
||||
DISPLACE_PACK
|
||||
@@ -300,7 +357,13 @@ fn shader_source(
|
||||
DISPLACE_VANILLA
|
||||
},
|
||||
layer_table("horizon_sprite_tags", tags),
|
||||
layer_table("horizon_sprite_material", materials)
|
||||
layer_table("horizon_sprite_material", materials),
|
||||
super::displace::RELIEF,
|
||||
if displaced {
|
||||
super::displace::GRID.to_owned() + DISPLACED_ENTRIES
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -310,8 +373,9 @@ fn module(
|
||||
tags: &[(u32, u32)],
|
||||
materials: &[(u32, u32)],
|
||||
vertex: bool,
|
||||
displaced: bool,
|
||||
) -> Result<naga::Module, String> {
|
||||
let text = shader_source(&stage.source, tags, materials, vertex);
|
||||
let text = shader_source(&stage.source, tags, materials, vertex, displaced);
|
||||
naga::front::wgsl::parse_str(&text)
|
||||
.map_err(|e| format!("{}: {}", stage.file, e.emit_to_string(&text)))
|
||||
}
|
||||
@@ -326,9 +390,16 @@ impl NativeMaterial {
|
||||
constants: &[(String, f64)],
|
||||
fog: bool,
|
||||
vertex: bool,
|
||||
displaced: Option<&DisplacedShape<'_>>,
|
||||
) -> Result<Self, String> {
|
||||
let writes_surface = horizon_shader_api::stages::calls_set_surface(&stage.source);
|
||||
let module = module(stage, shape.sprite_tags, shape.sprite_materials, vertex)?;
|
||||
let module = module(
|
||||
stage,
|
||||
shape.sprite_tags,
|
||||
shape.sprite_materials,
|
||||
vertex,
|
||||
displaced.is_some(),
|
||||
)?;
|
||||
let mut constants = present_constants(&module, constants);
|
||||
let sprite_names = [
|
||||
"horizon_terrain_SPRITE_ARRAYS",
|
||||
@@ -336,6 +407,14 @@ impl NativeMaterial {
|
||||
];
|
||||
constants.extend(sprite_names.into_iter().zip(shape.sprites));
|
||||
constants.push(("horizon_material_fog", f64::from(u8::from(fog))));
|
||||
constants.push((
|
||||
"horizon_displace_distance",
|
||||
displaced.map_or(0.0, |d| f64::from(d.distance)),
|
||||
));
|
||||
if let Some(d) = displaced {
|
||||
constants.push(("horizon_displace_cells", f64::from(d.cells)));
|
||||
constants.push(("horizon_displace_steps", f64::from(u8::from(d.steps))));
|
||||
}
|
||||
let scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(&stage.label),
|
||||
@@ -411,7 +490,7 @@ impl NativeMaterial {
|
||||
module: &module,
|
||||
entry_point: Some(fragment),
|
||||
compilation_options: options,
|
||||
targets: if fragment == "horizon_material_fs_solid" && writes_surface {
|
||||
targets: if fragment != "horizon_material_fs_translucent" && writes_surface {
|
||||
&solid_targets
|
||||
} else {
|
||||
&solid_targets[..1]
|
||||
@@ -453,6 +532,16 @@ impl NativeMaterial {
|
||||
),
|
||||
)
|
||||
});
|
||||
// The grid's corners come from its group 1, not from vertex buffers.
|
||||
let displaced = displaced.map(|d| {
|
||||
pipeline(
|
||||
&layout(Some(d.layout)),
|
||||
"horizon_material_vs_displaced",
|
||||
&[],
|
||||
"horizon_material_fs_displaced",
|
||||
None,
|
||||
)
|
||||
});
|
||||
if let Some(e) = pollster::block_on(scope.pop()) {
|
||||
return Err(format!("{}: {e}", stage.file));
|
||||
}
|
||||
@@ -460,6 +549,7 @@ impl NativeMaterial {
|
||||
solid,
|
||||
translucent,
|
||||
flat,
|
||||
displaced,
|
||||
writes_surface,
|
||||
})
|
||||
}
|
||||
@@ -515,7 +605,7 @@ mod tests {
|
||||
let tags = [(7, 1), (3, 1), (9, 2)];
|
||||
let materials = [(5, 1000)];
|
||||
assert!(layer_table("t", &tags).contains("case 3u, 7u: { return 1u; }"));
|
||||
let module = module(&stage, &tags, &materials, false).unwrap();
|
||||
let module = module(&stage, &tags, &materials, false, false).unwrap();
|
||||
let info = naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::all(),
|
||||
naga::valid::Capabilities::all(),
|
||||
@@ -524,7 +614,13 @@ mod tests {
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"{}",
|
||||
e.emit_to_string(&shader_source(&stage.source, &tags, &materials, false))
|
||||
e.emit_to_string(&shader_source(
|
||||
&stage.source,
|
||||
&tags,
|
||||
&materials,
|
||||
false,
|
||||
false
|
||||
))
|
||||
)
|
||||
});
|
||||
let entries: Vec<&str> = module
|
||||
@@ -546,4 +642,39 @@ mod tests {
|
||||
[("gain", 1.5)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_displaced_grid_validates_with_a_pack() {
|
||||
let manifest = "schema = 1\nid = \"t\"\nname = \"T\"\nversion = \"1\"\napi = \"0.1\"\n\
|
||||
[material]\nfile = \"material.wgsl\"\n[displacement]\n";
|
||||
let material = "fn material(input: MaterialInput) -> Surface {\n \
|
||||
return Surface(input.albedo * (1.0 - 0.5 * input.displaced));\n}\n\
|
||||
fn displacement(input: DisplacementInput) -> f32 {\n \
|
||||
return (1.0 - input.height) * 0.125 * f32(input.material + 1u);\n}\n";
|
||||
let files = [(MANIFEST, manifest), ("material.wgsl", material)]
|
||||
.map(|(n, t)| (n.to_owned(), t.as_bytes().to_vec()))
|
||||
.into();
|
||||
let mut report = check(&Pack::from_files(files).unwrap()).unwrap();
|
||||
let stage = report.stages.remove(0);
|
||||
let materials = [(5, 1000)];
|
||||
let module = module(&stage, &[], &materials, false, true).unwrap();
|
||||
naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::all(),
|
||||
naga::valid::Capabilities::all(),
|
||||
)
|
||||
.validate(&module)
|
||||
.unwrap_or_else(|e| {
|
||||
let text = shader_source(&stage.source, &[], &materials, false, true);
|
||||
panic!("{}", e.emit_to_string(&text))
|
||||
});
|
||||
for entry in [
|
||||
"horizon_material_vs_displaced",
|
||||
"horizon_material_fs_displaced",
|
||||
] {
|
||||
assert!(
|
||||
module.entry_points.iter().any(|e| e.name == entry),
|
||||
"{entry}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
use horizon_shader_api::{Report, StageKind};
|
||||
|
||||
use super::native_material::{NativeMaterial, TerrainShape};
|
||||
use super::displace::Displacement;
|
||||
use super::native_material::{DisplacedShape, NativeMaterial, TerrainShape};
|
||||
use super::native_post::{NativeFrame, NativePost};
|
||||
use super::native_shadow::NativeShadow;
|
||||
use super::native_shadow::{NativeShadow, ShadowGrid};
|
||||
use super::native_sky::NativeSky;
|
||||
use super::pack_timing::PassTiming;
|
||||
|
||||
@@ -21,6 +22,8 @@ pub struct NativePack {
|
||||
pub weather: Option<wgpu::RenderPipeline>,
|
||||
/// The draw of the shadow map the stages read, if the pack asks for one.
|
||||
pub shadow: Option<NativeShadow>,
|
||||
/// `[displacement]`'s grid, while it is on and the resource packs have height maps.
|
||||
pub displacement: Option<Displacement>,
|
||||
/// `[[particle_lights]]`.
|
||||
pub particle_lights: Vec<horizon_shader_api::manifest::ParticleLight>,
|
||||
/// `[[lights]]`, for the colour of held and dropped items' light.
|
||||
@@ -29,6 +32,33 @@ pub struct NativePack {
|
||||
pub timing: Option<PassTiming>,
|
||||
}
|
||||
|
||||
/// Whether the option `id` (a bool, or a number above 0) is on, or there is none to ask.
|
||||
fn option_on(constants: &[(String, f64)], id: Option<&String>) -> bool {
|
||||
id.is_none_or(|id| {
|
||||
constants
|
||||
.iter()
|
||||
.find(|(k, _)| k == id)
|
||||
.is_none_or(|(_, v)| *v != 0.0)
|
||||
})
|
||||
}
|
||||
|
||||
/// `[displacement]`'s cells: the value of its `cells_option`, if any.
|
||||
fn chosen_cells(
|
||||
d: &horizon_shader_api::manifest::Displacement,
|
||||
options: &[horizon_shader_api::manifest::PackOption],
|
||||
constants: &[(String, f64)],
|
||||
) -> u32 {
|
||||
let chosen = d.cells_option.as_ref().and_then(|id| {
|
||||
let option = options.iter().find(|o| o.id == *id)?;
|
||||
let horizon_shader_api::manifest::OptionKind::Choice { values, .. } = &option.kind else {
|
||||
return None;
|
||||
};
|
||||
let (_, index) = constants.iter().find(|(k, _)| k == id)?;
|
||||
values.get(*index as usize)?.parse().ok()
|
||||
});
|
||||
chosen.unwrap_or(d.cells)
|
||||
}
|
||||
|
||||
impl NativePack {
|
||||
/// The pack of the checked stages `report` with the option values `constants`, drawing
|
||||
/// onto the world's targets as `terrain` describes; the sky reads the terrain's group 0.
|
||||
@@ -49,11 +79,48 @@ impl NativePack {
|
||||
.as_ref()
|
||||
.map(|settings| super::native_shadow::fitted(device, settings));
|
||||
let mut frame = NativeFrame::new(device, settings.as_ref(), textures, lights, terrain.pbr);
|
||||
// `[displacement]`, unless its option is off; nothing to displace without heights.
|
||||
let displacement = report
|
||||
.manifest
|
||||
.displacement
|
||||
.as_ref()
|
||||
.filter(|d| option_on(constants, d.option.as_ref()))
|
||||
.filter(|_| terrain.pbr.is_some())
|
||||
.map(|d| {
|
||||
// No more cells than the height maps have texels.
|
||||
let texels = terrain.pbr.map_or(16, |p| p[0].width());
|
||||
let cells = chosen_cells(d, &report.manifest.options, constants)
|
||||
.min(texels)
|
||||
.max(1);
|
||||
let steps =
|
||||
d.steps_option.is_some() && option_on(constants, d.steps_option.as_ref());
|
||||
Displacement::new(
|
||||
device,
|
||||
d.distance,
|
||||
cells,
|
||||
steps,
|
||||
&frame.pbr[0],
|
||||
report.shadow.is_some(),
|
||||
)
|
||||
});
|
||||
let shadow = report
|
||||
.shadow
|
||||
.as_ref()
|
||||
.map(|s| {
|
||||
let vertex = report.stages.iter().find(|s| s.kind == StageKind::Vertex);
|
||||
// `[displacement]`'s grid casts too, from the material's module.
|
||||
let material = report.stages.iter().find(|s| s.kind == StageKind::Material);
|
||||
let grid = displacement.as_ref().and_then(|d| {
|
||||
let picks = d.shadow.as_ref()?;
|
||||
Some(ShadowGrid {
|
||||
distance: d.distance,
|
||||
cells: picks.cells,
|
||||
steps: picks.steps,
|
||||
material: material?,
|
||||
layout: &d.draw_layout,
|
||||
normals: &frame.pbr[0],
|
||||
})
|
||||
});
|
||||
NativeShadow::new(
|
||||
device,
|
||||
terrain,
|
||||
@@ -61,6 +128,7 @@ impl NativePack {
|
||||
&s.solid_materials,
|
||||
vertex,
|
||||
constants,
|
||||
grid,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
@@ -71,12 +139,7 @@ impl NativePack {
|
||||
.iter()
|
||||
.find(|s| s.kind == StageKind::Material)
|
||||
.is_some_and(|s| s.reads("reflection"))
|
||||
&& option.is_none_or(|id| {
|
||||
constants
|
||||
.iter()
|
||||
.find(|(k, _)| k == id)
|
||||
.is_none_or(|(_, v)| *v != 0.0)
|
||||
});
|
||||
&& option_on(constants, option);
|
||||
let fog = report.material_fog;
|
||||
let keeps = |key: fn(&horizon_shader_api::Stage) -> Option<bool>| {
|
||||
report.sky.as_ref().and_then(key).unwrap_or(true)
|
||||
@@ -119,7 +182,22 @@ impl NativePack {
|
||||
.iter()
|
||||
.find(|s| s.kind == StageKind::Material)
|
||||
.map(|stage| {
|
||||
NativeMaterial::new(device, terrain, &frame, stage, constants, fog, has_vertex)
|
||||
let displaced = displacement.as_ref().map(|d| DisplacedShape {
|
||||
distance: d.distance,
|
||||
cells: d.view.cells,
|
||||
steps: d.view.steps,
|
||||
layout: &d.draw_layout,
|
||||
});
|
||||
NativeMaterial::new(
|
||||
device,
|
||||
terrain,
|
||||
&frame,
|
||||
stage,
|
||||
constants,
|
||||
fog,
|
||||
has_vertex,
|
||||
displaced.as_ref(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let post = (!post.is_empty())
|
||||
@@ -148,6 +226,7 @@ impl NativePack {
|
||||
vanilla_stars,
|
||||
weather,
|
||||
shadow,
|
||||
displacement,
|
||||
timing: None,
|
||||
};
|
||||
Ok((pack, labels))
|
||||
|
||||
@@ -122,7 +122,7 @@ pub struct NativeFrame {
|
||||
pub opaque_color: wgpu::Texture,
|
||||
/// The resource packs' LabPBR maps (bindings 12 and 13: normals, specular), a blank 1x1
|
||||
/// without them.
|
||||
pbr: [wgpu::TextureView; 2],
|
||||
pub(super) pbr: [wgpu::TextureView; 2],
|
||||
/// The material stage's `reflection` (binding 1): this frame's water mirror, else
|
||||
/// `blank`; and its sampler (binding 2).
|
||||
reflection: wgpu::TextureView,
|
||||
@@ -623,7 +623,8 @@ fn texture_entry(binding: u32, sample_type: wgpu::TextureSampleType) -> wgpu::Bi
|
||||
fn pbr_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
// `[displacement]`'s grid moves its corners by the heights (gfx/displace.rs).
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
|
||||
@@ -23,6 +23,9 @@ struct HorizonShadowOut {
|
||||
@location(1) @interpolate(flat) layer: u32,
|
||||
@location(2) tint: vec3<f32>,
|
||||
@location(3) @interpolate(flat) fluid: u32,
|
||||
// The point on the flat face, camera-relative (`[displacement]`'s grid takes it over near
|
||||
// the camera).
|
||||
@location(4) base: vec3<f32>,
|
||||
}
|
||||
// `color` is the vertex colour: the tint, and the fluid flag in its alpha's high nibble
|
||||
// (mesher.rs's `fluid_face`).
|
||||
@@ -37,6 +40,7 @@ fn horizon_shadow_vertex(pos: vec3<f32>, uv: vec2<f32>, layer: u32, color: vec4<
|
||||
out.tint = color.rgb;
|
||||
let high = u32(round(color.a * 255.0)) >> 4u;
|
||||
out.fluid = u32(high >= 8u && (high & 7u) < 6u);
|
||||
out.base = pos;
|
||||
return out;
|
||||
}
|
||||
@vertex fn horizon_shadow_vs(
|
||||
@@ -58,6 +62,10 @@ fn horizon_shadow_vertex(pos: vec3<f32>, uv: vec2<f32>, layer: u32, color: vec4<
|
||||
return horizon_shadow_vertex(pos, atlas, layer, color);
|
||||
}
|
||||
@fragment fn horizon_shadow_fs(in: HorizonShadowOut) {
|
||||
if horizon_shadow_taken(in.base, in.layer) { discard; }
|
||||
horizon_shadow_cutout(in);
|
||||
}
|
||||
fn horizon_shadow_cutout(in: HorizonShadowOut) {
|
||||
// Bit 15 of the layer marks opaque leaves (forceOpaque): no alpha cutout.
|
||||
let alpha = horizon_terrain_sprite(in.uv, in.layer & 0x7fffu).a;
|
||||
if alpha < 0.5 && (in.layer & 0x8000u) == 0u && !horizon_shadow_solid(in.layer & 0x7fffu) { discard; }
|
||||
@@ -105,6 +113,45 @@ struct HorizonEntityShadowOut {
|
||||
}
|
||||
";
|
||||
|
||||
/// No `[displacement]` grid: the flat terrain casts everywhere.
|
||||
const UNTAKEN: &str = "
|
||||
fn horizon_shadow_taken(base: vec3<f32>, layer: u32) -> bool {
|
||||
return false;
|
||||
}
|
||||
";
|
||||
|
||||
/// `[displacement]`'s grid in the shadow map (gfx/displace.rs), cast in the flat quads' place.
|
||||
/// The height maps join the pass's frame group.
|
||||
const GRID_ENTRIES: &str = "
|
||||
@group(3) @binding(12) var horizon_pbr_normals: texture_2d_array<f32>;
|
||||
fn horizon_shadow_taken(base: vec3<f32>, layer: u32) -> bool {
|
||||
return length(base) < horizon_displace_distance - 0.05 && horizon_displaced_layer(layer);
|
||||
}
|
||||
@vertex fn horizon_shadow_vs_displaced(@builtin(vertex_index) v: u32,
|
||||
@builtin(instance_index) instance: u32) -> HorizonShadowOut {
|
||||
let p = horizon_displace_point(v, instance);
|
||||
var out = horizon_shadow_vertex(p.position, p.uv, p.layer, p.color);
|
||||
out.base = p.base;
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_shadow_fs_displaced(in: HorizonShadowOut) {
|
||||
if length(in.base) >= horizon_displace_distance + 0.05 { discard; }
|
||||
horizon_shadow_cutout(in);
|
||||
}
|
||||
";
|
||||
|
||||
/// What the shadow pass's grid is built from: `[displacement]`'s values, the material stage
|
||||
/// (whose module holds `displacement` and the vertex stage), group 1's layout and the height
|
||||
/// maps.
|
||||
pub struct ShadowGrid<'a> {
|
||||
pub distance: f32,
|
||||
pub cells: u32,
|
||||
pub steps: bool,
|
||||
pub material: &'a horizon_shader_api::check::CheckedStage,
|
||||
pub layout: &'a wgpu::BindGroupLayout,
|
||||
pub normals: &'a wgpu::TextureView,
|
||||
}
|
||||
|
||||
/// `shadow_color`'s format.
|
||||
const SHADOW_COLOR: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
||||
|
||||
@@ -326,6 +373,8 @@ pub struct NativeShadow {
|
||||
/// The entities' draw, with the entities' group 0 (`ENTITY_ENTRIES`).
|
||||
pub entity: wgpu::RenderPipeline,
|
||||
pub group: wgpu::BindGroup,
|
||||
/// `[displacement]`'s grid (gfx/displace.rs's shadow list).
|
||||
pub displaced: Option<wgpu::RenderPipeline>,
|
||||
}
|
||||
|
||||
/// The entity draw's shader.
|
||||
@@ -418,17 +467,36 @@ fn shadow_solid(materials: &[(u32, u32)], solid: &[u32]) -> String {
|
||||
}
|
||||
|
||||
/// The shadow pass's shader: the frame, Horizon's terrain, and the entries, with the layers
|
||||
/// `solid` of `shadow_solid`.
|
||||
fn shader_source(solid: &str, vertex: Option<&str>, materials: &[(u32, u32)]) -> String {
|
||||
let (head, displace) = match vertex {
|
||||
Some(stage) => (
|
||||
stage.to_owned()
|
||||
+ &super::native_material::layer_table("horizon_sprite_material", materials),
|
||||
/// `solid` of `shadow_solid`. Under `[displacement]` (`grid`: the material stage's source, which
|
||||
/// holds the vertex stage and `displacement`), its grid too.
|
||||
fn shader_source(
|
||||
solid: &str,
|
||||
vertex: Option<&str>,
|
||||
materials: &[(u32, u32)],
|
||||
grid: Option<&str>,
|
||||
) -> String {
|
||||
let table = super::native_material::layer_table("horizon_sprite_material", materials);
|
||||
let (head, displace) = match (grid, vertex) {
|
||||
(Some(material), _) => (
|
||||
format!(
|
||||
"{material}{}{table}{}{}{GRID_ENTRIES}",
|
||||
horizon_shader_api::stages::PBR_STUB,
|
||||
super::displace::RELIEF,
|
||||
super::displace::GRID,
|
||||
),
|
||||
if vertex.is_some() {
|
||||
super::native_material::DISPLACE_PACK
|
||||
} else {
|
||||
super::native_material::DISPLACE_VANILLA
|
||||
},
|
||||
),
|
||||
(None, Some(stage)) => (
|
||||
format!("{stage}{table}{UNTAKEN}"),
|
||||
super::native_material::DISPLACE_PACK,
|
||||
),
|
||||
None => (
|
||||
(None, None) => (
|
||||
format!(
|
||||
"{}@group({GROUP}) @binding(0) var<uniform> frame: Frame;\n{}",
|
||||
"{}@group({GROUP}) @binding(0) var<uniform> frame: Frame;\n{}{UNTAKEN}",
|
||||
FrameInputs::WGSL.wgsl(),
|
||||
horizon_shader_api::stages::SHADOW_HELPERS,
|
||||
),
|
||||
@@ -452,11 +520,13 @@ impl NativeShadow {
|
||||
solid_materials: &[u32],
|
||||
vertex: Option<&horizon_shader_api::check::CheckedStage>,
|
||||
constants: &[(String, f64)],
|
||||
grid: Option<ShadowGrid<'_>>,
|
||||
) -> Result<Self, String> {
|
||||
let text = shader_source(
|
||||
&shadow_solid(shape.sprite_materials, solid_materials),
|
||||
vertex.map(|s| s.source.as_str()),
|
||||
shape.sprite_materials,
|
||||
grid.as_ref().map(|g| g.material.source.as_str()),
|
||||
);
|
||||
let module = naga::front::wgsl::parse_str(&text)
|
||||
.map_err(|e| format!("shadow pass: {}", e.emit_to_string(&text)))?;
|
||||
@@ -465,31 +535,59 @@ impl NativeShadow {
|
||||
("horizon_terrain_SPRITE_ARRAYS", shape.sprites[0]),
|
||||
("horizon_terrain_SPRITE_SPLIT", shape.sprites[1]),
|
||||
]);
|
||||
if let Some(g) = &grid {
|
||||
constants.extend([
|
||||
("horizon_displace_distance", f64::from(g.distance)),
|
||||
("horizon_displace_cells", f64::from(g.cells)),
|
||||
("horizon_displace_steps", f64::from(u8::from(g.steps))),
|
||||
]);
|
||||
}
|
||||
let scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Native shader pack shadow"),
|
||||
source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module)),
|
||||
});
|
||||
// The frame alone, and under `[displacement]` the height maps its grid moves by.
|
||||
let frame_entry = wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(size_of::<FrameInputs>() as u64),
|
||||
},
|
||||
count: None,
|
||||
};
|
||||
let normals_entry = wgpu::BindGroupLayoutEntry {
|
||||
binding: 12,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
};
|
||||
let layout_entries: Vec<_> = std::iter::once(frame_entry)
|
||||
.chain(grid.is_some().then_some(normals_entry))
|
||||
.collect();
|
||||
let frame_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Native shader pack shadow frame"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(size_of::<FrameInputs>() as u64),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
entries: &layout_entries,
|
||||
});
|
||||
let group_entries: Vec<_> = std::iter::once(wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: frame.buffer.as_entire_binding(),
|
||||
})
|
||||
.chain(grid.as_ref().map(|g| wgpu::BindGroupEntry {
|
||||
binding: 12,
|
||||
resource: wgpu::BindingResource::TextureView(g.normals),
|
||||
}))
|
||||
.collect();
|
||||
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Native shader pack shadow frame"),
|
||||
layout: &frame_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: frame.buffer.as_entire_binding(),
|
||||
}],
|
||||
entries: &group_entries,
|
||||
});
|
||||
let layout = |flat: Option<&wgpu::BindGroupLayout>| {
|
||||
let mut groups: [Option<&wgpu::BindGroupLayout>; GROUP as usize + 1] =
|
||||
@@ -519,10 +617,11 @@ impl NativeShadow {
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})];
|
||||
let pipeline = |layout: &wgpu::PipelineLayout,
|
||||
vertex: &str,
|
||||
buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
|
||||
translucent: bool| {
|
||||
let pipeline_with = |layout: &wgpu::PipelineLayout,
|
||||
vertex: &str,
|
||||
buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
|
||||
fragment: &str| {
|
||||
let translucent = fragment == "horizon_shadow_fs_translucent";
|
||||
let options = wgpu::PipelineCompilationOptions {
|
||||
constants: &constants,
|
||||
..Default::default()
|
||||
@@ -556,11 +655,7 @@ impl NativeShadow {
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &module,
|
||||
entry_point: Some(if translucent {
|
||||
"horizon_shadow_fs_translucent"
|
||||
} else {
|
||||
"horizon_shadow_fs"
|
||||
}),
|
||||
entry_point: Some(fragment),
|
||||
compilation_options: options,
|
||||
targets: if translucent { &color_target } else { &[] },
|
||||
}),
|
||||
@@ -568,6 +663,17 @@ impl NativeShadow {
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
let pipeline = |layout: &wgpu::PipelineLayout,
|
||||
vertex: &str,
|
||||
buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
|
||||
translucent: bool| {
|
||||
let fragment = if translucent {
|
||||
"horizon_shadow_fs_translucent"
|
||||
} else {
|
||||
"horizon_shadow_fs"
|
||||
};
|
||||
pipeline_with(layout, vertex, buffers, fragment)
|
||||
};
|
||||
let instanced = layout(None);
|
||||
let solid = pipeline(
|
||||
&instanced,
|
||||
@@ -595,6 +701,15 @@ impl NativeShadow {
|
||||
let flat = flat_pipeline(false);
|
||||
let flat_translucent = flat_pipeline(true);
|
||||
let entity = entity_pipeline(device, shape.entities, &frame_layout);
|
||||
// The grid's corners come from its group 1, not from vertex buffers.
|
||||
let displaced = grid.as_ref().map(|g| {
|
||||
pipeline_with(
|
||||
&layout(Some(g.layout)),
|
||||
"horizon_shadow_vs_displaced",
|
||||
&[],
|
||||
"horizon_shadow_fs_displaced",
|
||||
)
|
||||
});
|
||||
if let Some(e) = pollster::block_on(scope.pop()) {
|
||||
return Err(format!("shadow pass: {e}"));
|
||||
}
|
||||
@@ -605,6 +720,7 @@ impl NativeShadow {
|
||||
flat_translucent,
|
||||
entity,
|
||||
group,
|
||||
displaced,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -616,7 +732,7 @@ mod tests {
|
||||
#[test]
|
||||
fn the_shadow_shader_validates() {
|
||||
for materials in [&[][..], &[(3, 7), (4, 8), (9, 7)][..]] {
|
||||
let text = shader_source(&shadow_solid(materials, &[7]), None, materials);
|
||||
let text = shader_source(&shadow_solid(materials, &[7]), None, materials, None);
|
||||
let module = naga::front::wgsl::parse_str(&text).unwrap();
|
||||
naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::all(),
|
||||
@@ -627,6 +743,47 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_shadow_shader_with_the_displaced_grid_validates() {
|
||||
use horizon_shader_api::{MANIFEST, Pack, StageKind, check};
|
||||
let manifest = "schema = 1\nid = \"t\"\nname = \"T\"\nversion = \"1\"\napi = \"0.1\"\n\
|
||||
[material]\nfile = \"material.wgsl\"\n[shadow]\n[displacement]\n";
|
||||
let material = "fn material(input: MaterialInput) -> Surface {\n \
|
||||
return Surface(input.albedo);\n}\n\
|
||||
fn displacement(input: DisplacementInput) -> f32 {\n \
|
||||
return (1.0 - input.height) * 0.125;\n}\n";
|
||||
let files = [(MANIFEST, manifest), ("material.wgsl", material)]
|
||||
.map(|(n, t)| (n.to_owned(), t.as_bytes().to_vec()))
|
||||
.into();
|
||||
let report = check(&Pack::from_files(files).unwrap()).unwrap();
|
||||
let stage = report
|
||||
.stages
|
||||
.iter()
|
||||
.find(|s| s.kind == StageKind::Material)
|
||||
.unwrap();
|
||||
let materials = [(3, 7)];
|
||||
let text = shader_source(
|
||||
&shadow_solid(&materials, &[7]),
|
||||
None,
|
||||
&materials,
|
||||
Some(&stage.source),
|
||||
);
|
||||
let module = naga::front::wgsl::parse_str(&text)
|
||||
.unwrap_or_else(|e| panic!("{}", e.emit_to_string(&text)));
|
||||
naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::all(),
|
||||
naga::valid::Capabilities::all(),
|
||||
)
|
||||
.validate(&module)
|
||||
.unwrap_or_else(|e| panic!("{}", e.emit_to_string(&text)));
|
||||
for entry in ["horizon_shadow_vs_displaced", "horizon_shadow_fs_displaced"] {
|
||||
assert!(
|
||||
module.entry_points.iter().any(|e| e.name == entry),
|
||||
"{entry}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_layers_of_solid_ids_lose_their_cutouts() {
|
||||
let materials = [(3, 7), (4, 8), (9, 7)];
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
//! for the next one. A section meshed in between propagates into the graph the
|
||||
//! frame has (vanilla's partial update), so what it opens shows at once; what
|
||||
//! it closes goes at the next full search.
|
||||
//!
|
||||
//! GPU culling (gfx/gpu_culling.rs) answers as `contains` does from a copy of
|
||||
//! the graph (`Reach`): the cells it reached, kept as bits too, sent again
|
||||
//! whenever the generation moves, which it does whenever an answer may change.
|
||||
use super::SectionKey;
|
||||
use crate::fast_hash::FxHashSet;
|
||||
use crate::mesher::ALL_VISIBLE;
|
||||
@@ -129,6 +133,10 @@ struct Graph {
|
||||
/// (`Node.sourceDirections`, as the faces rather than the steps); none for
|
||||
/// the camera's.
|
||||
entered: Vec<u8>,
|
||||
/// `REACHED` of each cell again, a bit each at `index` (32 a word, lowest
|
||||
/// bit first): the GPU's copy (`Occlusion::reach`), written with it
|
||||
/// (`set_reached`).
|
||||
bits: Vec<u32>,
|
||||
/// Whether any section in the searched top layer was reached. Sections
|
||||
/// above a clamped box use this conservative sky-band answer.
|
||||
sky_reached: bool,
|
||||
@@ -177,6 +185,13 @@ impl Graph {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Cell `i` is reached, having travelled `directions` (`REACHED` among
|
||||
/// them): the one writer of `REACHED`, so `bits` say what `directions` do.
|
||||
fn set_reached(&mut self, i: usize, directions: u8) {
|
||||
debug_assert!(directions & REACHED != 0);
|
||||
self.directions[i] = directions;
|
||||
self.bits[i / 32] |= 1 << (i % 32);
|
||||
}
|
||||
/// The full search from the camera (vanilla's `scheduleFullUpdate`).
|
||||
fn search(
|
||||
&mut self,
|
||||
@@ -220,6 +235,8 @@ impl Graph {
|
||||
let size = side * side * self.height as usize;
|
||||
self.directions.clear();
|
||||
self.directions.resize(size, 0);
|
||||
self.bits.clear();
|
||||
self.bits.resize(size.div_ceil(32), 0);
|
||||
self.entered.clear();
|
||||
self.entered.resize(size, 0);
|
||||
self.sky_reached = false;
|
||||
@@ -243,7 +260,7 @@ impl Graph {
|
||||
queue.clear();
|
||||
if (0..columns.sections).contains(&from.1) {
|
||||
let start = self.index(from).expect("camera inside the searched box");
|
||||
self.directions[start] = REACHED;
|
||||
self.set_reached(start, REACHED);
|
||||
queue.push_back(start as u32);
|
||||
} else {
|
||||
self.seed_outside(camera, queue);
|
||||
@@ -294,7 +311,7 @@ impl Graph {
|
||||
let i = self
|
||||
.index((self.from.0 + dx, y, self.from.2 + dz))
|
||||
.expect("seed inside the searched box");
|
||||
self.directions[i] = directions;
|
||||
self.set_reached(i, directions);
|
||||
// Entered through the face opposite the travel (`sourceDirection`).
|
||||
self.entered[i] = 1 << (travel ^ 1);
|
||||
queue.push_back(i as u32);
|
||||
@@ -304,14 +321,15 @@ impl Graph {
|
||||
/// every face their entry faces connect to, away from the camera. A step
|
||||
/// into a section already reached adds the face it enters through.
|
||||
/// `full`: the search from `camera` (`rays` holds its lines), not a
|
||||
/// partial update.
|
||||
/// partial update. Whether it reached a cell it had not.
|
||||
fn propagate(
|
||||
&mut self,
|
||||
queue: &mut VecDeque<u32>,
|
||||
camera: DVec3,
|
||||
full: bool,
|
||||
vis: impl Fn(usize, SectionKey) -> u64,
|
||||
) {
|
||||
) -> bool {
|
||||
let mut grew = false;
|
||||
let eye = (
|
||||
(camera.x.floor() as i32) >> 4,
|
||||
(camera.y.floor() as i32 - self.min_y) >> 4,
|
||||
@@ -365,14 +383,16 @@ impl Graph {
|
||||
}
|
||||
}
|
||||
if self.directions[n] & REACHED == 0 {
|
||||
self.directions[n] = directions | 1 << d;
|
||||
self.set_reached(n, directions | 1 << d);
|
||||
self.entered[n] = 1 << (d ^ 1);
|
||||
queue.push_back(n as u32);
|
||||
grew = true;
|
||||
} else {
|
||||
self.entered[n] |= 1 << (d ^ 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
grew
|
||||
}
|
||||
/// Vanilla's distant step test: from the corner of `key` that the step `d`
|
||||
/// picks, walk towards the camera in section diagonals while farther than
|
||||
@@ -472,6 +492,42 @@ impl Graph {
|
||||
}
|
||||
}
|
||||
|
||||
/// The graph `Occlusion::contains` reads, for GPU culling's copy
|
||||
/// (gfx/gpu_culling.rs): its searched box (`Graph::index`), the level's height
|
||||
/// in sections, whether the search reached the box's top layer (the sky band
|
||||
/// above a clamped box), and its cells' `REACHED` as bits.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Reach<'a> {
|
||||
/// `Occlusion::generation` when it was taken.
|
||||
pub generation: u64,
|
||||
pub from_x: i32,
|
||||
pub from_z: i32,
|
||||
pub radius: i32,
|
||||
pub y0: i32,
|
||||
pub height: i32,
|
||||
pub sections: i32,
|
||||
pub sky_reached: bool,
|
||||
pub bits: &'a [u32],
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Reach<'_> {
|
||||
/// gpu_culling.wgsl's `reached`, statement for statement: what
|
||||
/// `Graph::contains` answers.
|
||||
pub fn contains(&self, key: SectionKey) -> bool {
|
||||
let r = self.radius;
|
||||
let (dx, dz, dy) = (key.0 - self.from_x, key.2 - self.from_z, key.1 - self.y0);
|
||||
if dx < -r || dx > r || dz < -r || dz > r || dy < 0 {
|
||||
return false;
|
||||
}
|
||||
if dy >= self.height {
|
||||
return self.sky_reached && (0..=self.sections).contains(&key.1);
|
||||
}
|
||||
let i = (((dz + r) * (2 * r + 1) + dx + r) * self.height + dy) as usize;
|
||||
(self.bits[i / 32] >> (i % 32)) & 1 != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// `Mth.floor` for coordinates inside the world, without a libm call (the
|
||||
/// x86-64 baseline has no rounding instruction).
|
||||
fn floor(x: f64) -> i32 {
|
||||
@@ -518,6 +574,14 @@ impl Worker {
|
||||
}
|
||||
}
|
||||
|
||||
/// A new value for `Occlusion::generation`, unique in the process: a copy made
|
||||
/// from an older `Occlusion` (another level's) never matches. As gfx.rs
|
||||
/// `next_block_entity_revision`.
|
||||
fn next_generation() -> u64 {
|
||||
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub struct Occlusion {
|
||||
/// The render thread's copy, for partial updates (and searches when the
|
||||
/// worker is gone).
|
||||
@@ -545,6 +609,8 @@ pub struct Occlusion {
|
||||
changed_late: Vec<SectionKey>,
|
||||
vis: Vec<u64>,
|
||||
queue: VecDeque<u32>,
|
||||
/// Changes whenever `contains` may answer differently (`generation`).
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
impl Occlusion {
|
||||
@@ -564,6 +630,7 @@ impl Occlusion {
|
||||
changed_late: Vec::new(),
|
||||
vis: Vec::new(),
|
||||
queue: VecDeque::new(),
|
||||
generation: next_generation(),
|
||||
}
|
||||
}
|
||||
pub fn set(&mut self, key: SectionKey, vis: u64) {
|
||||
@@ -608,9 +675,11 @@ impl Occlusion {
|
||||
self.edits.clear();
|
||||
self.edits.push(Edit::Clear);
|
||||
self.dirty = true;
|
||||
// Another world: the old graph says nothing about it.
|
||||
// Another world: the old graph says nothing about it. Without one, every
|
||||
// section was visible already, and stays so.
|
||||
if let Some(graph) = self.graph.take() {
|
||||
self.spare = graph;
|
||||
self.generation = next_generation();
|
||||
}
|
||||
self.searched = None;
|
||||
self.discard = self.running.is_some();
|
||||
@@ -623,6 +692,38 @@ impl Occlusion {
|
||||
pub fn contains(&self, key: SectionKey) -> bool {
|
||||
self.graph.as_ref().is_none_or(|g| g.contains(key))
|
||||
}
|
||||
/// Changes whenever `contains` may answer differently: a search landed, a
|
||||
/// partial update reached more sections, or the graph went.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn generation(&self) -> u64 {
|
||||
self.generation
|
||||
}
|
||||
/// The graph `contains` reads; `None` while every section counts as
|
||||
/// visible.
|
||||
pub fn reach(&self) -> Option<Reach<'_>> {
|
||||
self.graph.as_ref().map(|g| Reach {
|
||||
generation: self.generation,
|
||||
from_x: g.from.0,
|
||||
from_z: g.from.2,
|
||||
radius: g.radius,
|
||||
y0: g.y0,
|
||||
height: g.height,
|
||||
sections: g.sections,
|
||||
sky_reached: g.sky_reached,
|
||||
bits: &g.bits,
|
||||
})
|
||||
}
|
||||
/// The bits `reach` exports are the cells' `REACHED`, cell for cell.
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn reach_is_exact(&self) -> bool {
|
||||
self.graph.as_ref().is_none_or(|g| {
|
||||
g.bits.len() == g.directions.len().div_ceil(32)
|
||||
&& g.directions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, &d)| (d & REACHED != 0) == ((g.bits[i / 32] >> (i % 32)) & 1 != 0))
|
||||
})
|
||||
}
|
||||
/// Take a finished search, propagate changed sections into the graph,
|
||||
/// and start a search when sections changed or the camera moved to
|
||||
/// another section. Never waits for the worker.
|
||||
@@ -642,6 +743,7 @@ impl Occlusion {
|
||||
&& let Some(graph) = self.graph.take()
|
||||
{
|
||||
self.spare = graph;
|
||||
self.generation = next_generation();
|
||||
}
|
||||
}
|
||||
fn start(&mut self, camera: DVec3, radius: i32) {
|
||||
@@ -713,6 +815,7 @@ impl Occlusion {
|
||||
if let Some(old) = self.graph.replace(graph) {
|
||||
self.spare = old;
|
||||
}
|
||||
self.generation = next_generation();
|
||||
}
|
||||
/// Vanilla's `runPartialUpdate`: what changed sections now connect to.
|
||||
fn propagate_changes(&mut self, camera: DVec3) {
|
||||
@@ -729,7 +832,9 @@ impl Occlusion {
|
||||
}
|
||||
}
|
||||
let columns = &self.columns;
|
||||
graph.propagate(&mut self.queue, camera, false, |_, key| columns.get(key));
|
||||
if graph.propagate(&mut self.queue, camera, false, |_, key| columns.get(key)) {
|
||||
self.generation = next_generation();
|
||||
}
|
||||
}
|
||||
/// Wait for the running search: tests, and A/B timing of the old
|
||||
/// blocking search.
|
||||
@@ -1405,4 +1510,197 @@ mod tests {
|
||||
"enclosed cave does not see the sky"
|
||||
);
|
||||
}
|
||||
|
||||
/// `contains` over the columns `radius` + 3 around `from`, from two
|
||||
/// sections below the level to two above it, checked against the graph
|
||||
/// `reach` exports: `None` exactly without a graph, the same answer for
|
||||
/// every key, nothing past its box, its bits its cells' `REACHED`, and its
|
||||
/// sky band reached exactly when a bit of its top layer is.
|
||||
fn answers(o: &Occlusion, from: SectionKey, radius: i32) -> Vec<bool> {
|
||||
let reach = o.reach();
|
||||
assert_eq!(reach.is_some(), o.graph.is_some(), "a graph exported");
|
||||
assert!(o.reach_is_exact(), "the bits are the cells'");
|
||||
if let Some(g) = &o.graph {
|
||||
let height = g.height as usize;
|
||||
let mut top = (height - 1..g.directions.len()).step_by(height);
|
||||
let sky = top.any(|i| (g.bits[i / 32] >> (i % 32)) & 1 != 0);
|
||||
assert_eq!(g.sky_reached, sky, "the sky band");
|
||||
}
|
||||
let past = |key: SectionKey| {
|
||||
o.graph.as_ref().is_some_and(|g| {
|
||||
(key.0 - g.from.0).abs() > g.radius
|
||||
|| (key.2 - g.from.2).abs() > g.radius
|
||||
|| !(g.y0..=g.sections).contains(&key.1)
|
||||
})
|
||||
};
|
||||
let mut answers = Vec::new();
|
||||
for x in from.0 - radius - 3..=from.0 + radius + 3 {
|
||||
for z in from.2 - radius - 3..=from.2 + radius + 3 {
|
||||
for y in -2..=o.columns.sections + 1 {
|
||||
let key = (x, y, z);
|
||||
let contains = o.contains(key);
|
||||
assert_eq!(reach.is_none_or(|r| r.contains(key)), contains, "{key:?}");
|
||||
assert!(!(contains && past(key)), "{key:?} past the box");
|
||||
answers.push(contains);
|
||||
}
|
||||
}
|
||||
}
|
||||
answers
|
||||
}
|
||||
|
||||
/// The graph `reach` exports answers as `contains` (`answers`): walled in,
|
||||
/// opened by a partial update, cleared; over clamped boxes, the sky band
|
||||
/// above them reached or not (a closed cave); from above the world. The
|
||||
/// search, the partial update and `clear` each change the generation, and
|
||||
/// another level's graph never repeats one.
|
||||
#[test]
|
||||
fn the_exported_graph_answers_as_contains() {
|
||||
let mut o = Occlusion::new(0, 4);
|
||||
let first = o.generation();
|
||||
answers(&o, (0, 1, 0), 3);
|
||||
for (dx, dy, dz) in STEPS {
|
||||
o.set((dx, 1 + dy, dz), 0);
|
||||
}
|
||||
o.settle(at((0, 1, 0)), 3);
|
||||
assert_ne!(o.generation(), first, "a search landed");
|
||||
let walled = answers(&o, (0, 1, 0), 3);
|
||||
let searched = o.generation();
|
||||
o.set((0, 1, 1), ALL_VISIBLE);
|
||||
o.update(at((0, 1, 0)), 3);
|
||||
assert_ne!(o.generation(), searched, "a partial update reached more");
|
||||
assert_ne!(answers(&o, (0, 1, 0), 3), walled);
|
||||
let opened = o.generation();
|
||||
o.clear();
|
||||
assert_ne!(o.generation(), opened, "the graph went");
|
||||
answers(&o, (0, 1, 0), 3);
|
||||
// Another level's graph never takes one of this one's generations, or
|
||||
// the pass would keep its copy of this one.
|
||||
let mut other = Occlusion::new(0, 4);
|
||||
other.settle(at((0, 1, 0)), 3);
|
||||
assert_ne!(other.generation(), searched, "another level's graph");
|
||||
|
||||
let (mut clamped, mut band) = (0, [0; 2]);
|
||||
let mut check = |mut o: Occlusion, camera: DVec3, radius: i32| {
|
||||
o.settle(camera, radius);
|
||||
answers(&o, o.columns.section_of(camera), radius);
|
||||
let g = o.graph.as_ref().expect("searched");
|
||||
if g.height < o.columns.sections + 2 {
|
||||
clamped += 1;
|
||||
// The first section above the box, over the camera's column.
|
||||
band[usize::from(o.contains((g.from.0, g.y0 + g.height, g.from.2)))] += 1;
|
||||
}
|
||||
};
|
||||
for seed in [0x9e37_79b9, 7, 0xdead_beef, 0x2545_f491] {
|
||||
for (spread, count, radius) in [(6, 900, 6), (12, 2600, 12)] {
|
||||
let mut o = Occlusion::new(0, 24);
|
||||
random_world(&mut o, seed, count, spread);
|
||||
check(o, at((0, 2, 0)) + DVec3::new(3.3, -5.7, 1.9), radius);
|
||||
}
|
||||
}
|
||||
let mut cave = Occlusion::new(0, 24);
|
||||
for (dx, dy, dz) in STEPS {
|
||||
cave.set((dx, 1 + dy, dz), 0);
|
||||
}
|
||||
check(cave, at((0, 1, 0)), 4);
|
||||
let mut above = Occlusion::new(0, 24);
|
||||
random_world(&mut above, 7, 900, 6);
|
||||
check(above, DVec3::new(3.3, 400.0, -5.6), 6);
|
||||
assert!(
|
||||
clamped > 0 && band[0] > 0 && band[1] > 0,
|
||||
"{clamped} clamped, the band above hidden and seen {band:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Whenever an answer of `contains` changes, so does the generation: over
|
||||
/// 300 random steps, each a section set (open, closed or anything between),
|
||||
/// a column removed, the camera moved to another section (now and then
|
||||
/// above or below the world), or now and then another world, after a
|
||||
/// `clear` or a teleport that drops every column (`remove_columns`), each
|
||||
/// followed by a frame's `update` or a settled search; then an update from
|
||||
/// a section the graph never reached. The exported graph answers as
|
||||
/// `contains` after every step.
|
||||
#[test]
|
||||
fn every_change_of_the_answers_changes_the_generation() {
|
||||
let mut seed = 0x0bad_5eed_u64;
|
||||
let mut next = || {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 7;
|
||||
seed ^= seed << 17;
|
||||
seed
|
||||
};
|
||||
let (spread, radius, around) = (5, 3, (0, 0, 0));
|
||||
let offset = DVec3::new(3.3, -5.7, 1.9);
|
||||
let mut o = Occlusion::new(0, 24);
|
||||
random_world(&mut o, 0x2545_f491, 400, spread);
|
||||
let mut camera = at((0, 2, 0)) + offset;
|
||||
o.settle(camera, radius);
|
||||
let mut last = (o.generation(), answers(&o, around, spread + radius));
|
||||
let mut changed = 0;
|
||||
for step in 0..300 {
|
||||
let r = next();
|
||||
let key = (
|
||||
(r % 11) as i32 - spread,
|
||||
((r >> 8) % 7) as i32,
|
||||
((r >> 16) % 11) as i32 - spread,
|
||||
);
|
||||
match r >> 24 & 15 {
|
||||
0..=7 => {
|
||||
let vis = match r >> 28 & 3 {
|
||||
0 => ALL_VISIBLE,
|
||||
1 => 0,
|
||||
_ => next() & ALL_VISIBLE,
|
||||
};
|
||||
o.set(key, vis);
|
||||
}
|
||||
8 | 9 => o.remove_column(key.0, key.2),
|
||||
10..=13 => {
|
||||
let y = match r >> 32 & 7 {
|
||||
0 => 30,
|
||||
1 => -3,
|
||||
_ => key.1,
|
||||
};
|
||||
camera = at((key.0, y, key.2)) + offset;
|
||||
}
|
||||
_ if r >> 36 & 7 == 0 => {
|
||||
o.clear();
|
||||
random_world(&mut o, next(), 400, spread);
|
||||
}
|
||||
_ if r >> 36 & 7 == 1 => {
|
||||
// A teleport: every column goes at once.
|
||||
let all: FxHashSet<_> = o.columns.map.keys().copied().collect();
|
||||
o.remove_columns(&all);
|
||||
random_world(&mut o, next(), 400, spread);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if r >> 40 & 1 == 0 {
|
||||
o.update(camera, radius);
|
||||
} else {
|
||||
o.settle(camera, radius);
|
||||
}
|
||||
let now = (o.generation(), answers(&o, around, spread + radius));
|
||||
if now.1 != last.1 {
|
||||
assert_ne!(
|
||||
now.0, last.0,
|
||||
"step {step}: answers changed, the generation not"
|
||||
);
|
||||
changed += 1;
|
||||
}
|
||||
last = now;
|
||||
}
|
||||
assert!(changed >= 50, "{changed} steps changed answers");
|
||||
// Walled in, then past the wall: the graph never reached the camera's
|
||||
// section, and goes until the next search lands. (The first settle may
|
||||
// land a search that started before the wall.)
|
||||
for (dx, dy, dz) in STEPS {
|
||||
o.set((dx, 1 + dy, dz), 0);
|
||||
}
|
||||
o.settle(at((0, 1, 0)), radius);
|
||||
o.settle(at((0, 1, 0)), radius);
|
||||
let walled = (o.generation(), answers(&o, around, spread + radius));
|
||||
assert!(!o.contains((3, 1, 0)), "past the wall");
|
||||
o.update(at((3, 1, 0)), radius);
|
||||
assert_ne!(answers(&o, around, spread + radius), walled.1);
|
||||
assert_ne!(o.generation(), walled.0, "the graph went");
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,8 @@ pub(super) struct Draw {
|
||||
/// The pack's shadow program draws it into the shadow map too.
|
||||
pub shadow: bool,
|
||||
pub slot: u32,
|
||||
/// End portal or gateway faces: drawn with `end_portal.png` bound as the atlas.
|
||||
pub portal: bool,
|
||||
}
|
||||
|
||||
/// Which entity runs a pack's shadow pass draws (2.4, 3.2).
|
||||
@@ -375,38 +377,87 @@ impl PackEntities {
|
||||
pack,
|
||||
casts,
|
||||
overlay,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
let first = spans.first().map_or(entities.len(), |s| s.start as usize);
|
||||
rest.extend_from_slice(&entities[..first.min(entities.len())]);
|
||||
extend_without_portals(rest, &entities[..first.min(entities.len())]);
|
||||
let (mut other, mut portal_faces, mut beam_faces) = (Vec::new(), Vec::new(), Vec::new());
|
||||
for (span, range) in runs(entities.len(), spans) {
|
||||
let run = &entities[range];
|
||||
let program = program_of(&span).filter(|&p| {
|
||||
plain(run) || (p == DESTROY && run.iter().all(|v| matches!(v.layer, 11 | 12)))
|
||||
});
|
||||
let pack = program.is_some_and(|p| drawn[p]) && !span.hidden;
|
||||
let casts = program.is_some_and(|p| shadow.cast(&span, p));
|
||||
if !pack && !span.hidden {
|
||||
rest.extend_from_slice(run);
|
||||
}
|
||||
let Some(program) = program.filter(|_| (pack || casts) && !run.is_empty()) else {
|
||||
continue;
|
||||
let whole = &entities[range];
|
||||
// A block entity's run can hold End portal faces and beams beside plain parts (an End
|
||||
// gateway: its cube and its beam): each goes to its own program.
|
||||
// Beacon beams come in a run of their own, at stage NONE (game.rs: no stage of 5.4
|
||||
// is theirs), for gbuffers_beaconbeam.
|
||||
let split = (program_of(&span) == Some(BLOCK) || span.stage == render_stage("NONE"))
|
||||
&& whole
|
||||
.chunks(4)
|
||||
.any(|q| matches!(q[0].layer & 0xffff, 6 | 9 | 10));
|
||||
let parts: [(&[Vertex], Part); 3] = if split {
|
||||
other.clear();
|
||||
portal_faces.clear();
|
||||
beam_faces.clear();
|
||||
for quad in whole.chunks(4) {
|
||||
match quad[0].layer & 0xffff {
|
||||
9 | 10 => portal_faces.extend_from_slice(quad),
|
||||
6 => beam_faces.extend_from_slice(quad),
|
||||
_ => other.extend_from_slice(quad),
|
||||
}
|
||||
}
|
||||
[
|
||||
(&other, Part::Plain),
|
||||
(&portal_faces, Part::Portal),
|
||||
(&beam_faces, Part::Beam),
|
||||
]
|
||||
} else {
|
||||
[(whole, Part::Plain), (&[], Part::Plain), (&[], Part::Plain)]
|
||||
};
|
||||
let start = self.scratch.len() as u32;
|
||||
self.scratch.extend_from_slice(run);
|
||||
if program == DESTROY {
|
||||
project_cracks(&mut self.scratch[start as usize..], crack.0, crack.1);
|
||||
for (run, part) in parts {
|
||||
if run.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let program = match part {
|
||||
Part::Portal => Some(BLOCK),
|
||||
Part::Beam => Some(BEAM),
|
||||
Part::Plain => program_of(&span).filter(|&p| {
|
||||
plain(run)
|
||||
|| (p == DESTROY && run.iter().all(|v| matches!(v.layer, 11 | 12)))
|
||||
}),
|
||||
};
|
||||
let pack = program.is_some_and(|p| drawn[p]) && !span.hidden;
|
||||
let casts = part == Part::Plain && program.is_some_and(|p| shadow.cast(&span, p));
|
||||
if !pack && !span.hidden {
|
||||
extend_without_portals(rest, run);
|
||||
}
|
||||
let Some(program) = program.filter(|_| pack || casts) else {
|
||||
continue;
|
||||
};
|
||||
let start = self.scratch.len() as u32;
|
||||
match part {
|
||||
Part::Beam => beam_segments(run, &mut self.scratch),
|
||||
_ => self.scratch.extend_from_slice(run),
|
||||
}
|
||||
if program == DESTROY {
|
||||
project_cracks(&mut self.scratch[start as usize..], crack.0, crack.1);
|
||||
}
|
||||
if part == Part::Portal {
|
||||
as_portal_texture(&mut self.scratch[start as usize..]);
|
||||
}
|
||||
let vertices = self.scratch.len() as u32 - start;
|
||||
let overlay = self.scratch[start as usize].layer & 0xffff;
|
||||
let portal = part == Part::Portal;
|
||||
self.push_draw(
|
||||
span,
|
||||
posed + start,
|
||||
vertices,
|
||||
program,
|
||||
pack,
|
||||
casts,
|
||||
overlay,
|
||||
portal,
|
||||
);
|
||||
}
|
||||
self.push_draw(
|
||||
span,
|
||||
posed + start,
|
||||
run.len() as u32,
|
||||
program,
|
||||
pack,
|
||||
casts,
|
||||
run[0].layer & 0xffff,
|
||||
);
|
||||
}
|
||||
if self.draws.is_empty() && posed == 0 {
|
||||
return;
|
||||
@@ -453,6 +504,7 @@ impl PackEntities {
|
||||
pack: bool,
|
||||
casts: bool,
|
||||
overlay: u32,
|
||||
portal: bool,
|
||||
) {
|
||||
let (entity_id, block_id) = match program {
|
||||
BLOCK => (0, span.id),
|
||||
@@ -476,6 +528,7 @@ impl PackEntities {
|
||||
pack,
|
||||
shadow: casts,
|
||||
slot: self.draws.len() as u32,
|
||||
portal,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -504,13 +557,104 @@ fn plain(run: &[Vertex]) -> bool {
|
||||
.all(|v| !matches!(v.layer & 0xffff, 2 | 4 | 6 | 9 | 10 | 11 | 12 | 34))
|
||||
}
|
||||
|
||||
/// What part of a block entity's run goes to which program (`prepare`).
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Part {
|
||||
Plain,
|
||||
Portal,
|
||||
Beam,
|
||||
}
|
||||
|
||||
/// Beam faces (`block_entity_render::beam`: layer 6, v counting the sprite's repeats up the
|
||||
/// beam, the sprite's v range in the light bytes) for a pack: each face cut where its texture
|
||||
/// repeats, so every piece holds one copy of the sprite in plain atlas coordinates (at most 64
|
||||
/// pieces a face; a taller beam stretches its copies), white light and its colour kept.
|
||||
fn beam_segments(run: &[Vertex], out: &mut Vec<Vertex>) {
|
||||
for quad in run.as_chunks::<4>().0 {
|
||||
let wrap = quad[0].light;
|
||||
let (v0, size) = (
|
||||
(wrap & 65535) as f32 / 65535.0,
|
||||
(wrap >> 16) as f32 / 65535.0,
|
||||
);
|
||||
// Corners 0 and 3 at the beam's end (v1), 1 and 2 at its start (v2); 0 and 1 on one
|
||||
// side, 2 and 3 on the other.
|
||||
let (v1, v2) = (quad[0].uv[1], quad[1].uv[1]);
|
||||
if (v1 - v2).abs() < 1e-6 {
|
||||
continue;
|
||||
}
|
||||
let (lo, hi) = (v1.min(v2), v1.max(v2));
|
||||
let first = lo.floor();
|
||||
let repeats = (hi.ceil() - first).max(1.0);
|
||||
let step = (repeats / 64.0).ceil().max(1.0);
|
||||
let pieces = (repeats / step).ceil() as u32;
|
||||
let at = |side: usize, v: f32| {
|
||||
let (a, b) = if side == 0 {
|
||||
(quad[1], quad[0])
|
||||
} else {
|
||||
(quad[2], quad[3])
|
||||
};
|
||||
let t = (v - v2) / (v1 - v2);
|
||||
glam::Vec3::from(a.pos)
|
||||
.lerp(glam::Vec3::from(b.pos), t)
|
||||
.to_array()
|
||||
};
|
||||
for k in 0..pieces {
|
||||
let base = first + k as f32 * step;
|
||||
let (a, b) = (base.max(lo), (base + step).min(hi));
|
||||
if b <= a {
|
||||
continue;
|
||||
}
|
||||
// The end-side corners take the piece's bound nearer v1.
|
||||
let (end_v, start_v) = if v1 > v2 { (b, a) } else { (a, b) };
|
||||
for (i, corner) in quad.iter().enumerate() {
|
||||
let side = usize::from(i >= 2);
|
||||
let v = if i == 0 || i == 3 { end_v } else { start_v };
|
||||
let mut out_v = *corner;
|
||||
out_v.pos = at(side, v);
|
||||
out_v.uv = [corner.uv[0], v0 + (v - base) / step * size];
|
||||
out_v.layer = 0;
|
||||
out_v.light = 0x0000_f0f0;
|
||||
out.push(out_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `run` added to Horizon's own draw without End portal faces: under a shader pack Horizon's
|
||||
/// entity shader would put its screen-projected starfield into the pack's buffers as if they
|
||||
/// were its own frame (misplaced where the pack renders at another size), so a portal the pack
|
||||
/// does not draw (its programs not there, or not yet) is better left out.
|
||||
fn extend_without_portals(rest: &mut Vec<Vertex>, run: &[Vertex]) {
|
||||
for quad in run.chunks(4) {
|
||||
if !quad.iter().all(|v| matches!(v.layer & 0xffff, 6 | 9 | 10)) {
|
||||
rest.extend_from_slice(quad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// End portal faces for a pack's `gbuffers_block` (block entities): each face textured with the
|
||||
/// whole of `end_portal.png`, which the draw binds as the atlas (a texture of its own, so a
|
||||
/// pack's own portal effect can wrap it), white, at full light; the pack tells the portal by its
|
||||
/// `blockEntityId`.
|
||||
fn as_portal_texture(run: &mut [Vertex]) {
|
||||
const CORNERS: [[f32; 2]; 4] = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]];
|
||||
for (i, v) in run.iter_mut().enumerate() {
|
||||
v.uv = CORNERS[i % 4];
|
||||
v.layer = 0;
|
||||
v.color = 0xFFFF_FFFF;
|
||||
v.light = 0x0000_f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
/// The programs of entity runs, in the order they draw.
|
||||
pub const ENTITY: usize = 0;
|
||||
pub const BLOCK: usize = 1;
|
||||
pub const TRANSLUCENT: usize = 2;
|
||||
pub const PARTICLE: usize = 3;
|
||||
pub const DESTROY: usize = 4;
|
||||
pub const PROGRAMS: usize = 5;
|
||||
/// Beacon and End gateway beams (`gbuffers_beaconbeam`).
|
||||
pub const BEAM: usize = 5;
|
||||
pub const PROGRAMS: usize = 6;
|
||||
|
||||
/// Which program draws `span`'s run, given which of them (or their fallbacks) the pack has;
|
||||
/// `None`: Horizon draws it.
|
||||
@@ -587,11 +731,11 @@ mod tests {
|
||||
assert_eq!(drawn_by_pack(&spans[0], all), Some(ENTITY));
|
||||
assert_eq!(drawn_by_pack(&spans[1], all), Some(PARTICLE));
|
||||
assert_eq!(
|
||||
drawn_by_pack(&spans[1], [true, true, true, false, true]),
|
||||
drawn_by_pack(&spans[1], [true, true, true, false, true, true]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
drawn_by_pack(&spans[2], [true, false, true, true, true]),
|
||||
drawn_by_pack(&spans[2], [true, false, true, true, true, true]),
|
||||
None
|
||||
);
|
||||
assert_eq!(drawn_by_pack(&spans[2], all), Some(BLOCK));
|
||||
@@ -654,6 +798,103 @@ fn project_cracks(vertices: &mut [Vertex], sprite: [f32; 4], atlas: [u32; 2]) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod portal_beam_tests {
|
||||
use super::*;
|
||||
|
||||
fn quad(layer: u32, light: u32, corners: [([f32; 3], [f32; 2]); 4]) -> [Vertex; 4] {
|
||||
corners.map(|(pos, uv)| Vertex {
|
||||
pos,
|
||||
uv,
|
||||
layer,
|
||||
color: 0xFF20_40FF,
|
||||
light,
|
||||
..bytemuck::Zeroable::zeroed()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_beam_face_becomes_one_sprite_copy_per_repeat() {
|
||||
// `block_entity_render::beam`'s corner order: 0 and 3 at the end (v1), 1 and 2 at the
|
||||
// start (v2); the sprite's v range (0.25, height 0.5) in the light bytes.
|
||||
let wrap = (0.25f32 * 65535.0).round() as u32 | ((0.5f32 * 65535.0).round() as u32) << 16;
|
||||
let face = quad(
|
||||
6,
|
||||
wrap,
|
||||
[
|
||||
([0.0, 3.0, 0.0], [0.9, 3.0]),
|
||||
([0.0, 0.0, 0.0], [0.9, 0.0]),
|
||||
([1.0, 0.0, 0.0], [0.1, 0.0]),
|
||||
([1.0, 3.0, 0.0], [0.1, 3.0]),
|
||||
],
|
||||
);
|
||||
let mut out = Vec::new();
|
||||
beam_segments(&face, &mut out);
|
||||
assert_eq!(out.len(), 12, "three repeats, three pieces");
|
||||
for (k, piece) in out.chunks(4).enumerate() {
|
||||
let (bottom, top) = (k as f32, k as f32 + 1.0);
|
||||
assert_eq!([piece[0].pos[1], piece[1].pos[1]], [top, bottom]);
|
||||
assert_eq!([piece[2].pos[1], piece[3].pos[1]], [bottom, top]);
|
||||
for v in piece {
|
||||
assert!((0.25 - 1e-4..=0.75 + 1e-4).contains(&v.uv[1]), "{:?}", v.uv);
|
||||
assert_eq!(v.layer, 0);
|
||||
assert_eq!(v.light, 0x0000_f0f0);
|
||||
assert_eq!(v.color, 0xFF20_40FF);
|
||||
}
|
||||
assert!((piece[0].uv[1] - 0.75).abs() < 1e-4 && (piece[1].uv[1] - 0.25).abs() < 1e-4);
|
||||
assert_eq!([piece[0].uv[0], piece[2].uv[0]], [0.9, 0.1]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_very_tall_beam_is_cut_into_at_most_64_pieces() {
|
||||
let face = quad(
|
||||
6,
|
||||
0xFFFF_0000,
|
||||
[
|
||||
([0.0, 2000.0, 0.0], [1.0, 2000.0]),
|
||||
([0.0, 0.0, 0.0], [1.0, 0.0]),
|
||||
([1.0, 0.0, 0.0], [0.0, 0.0]),
|
||||
([1.0, 2000.0, 0.0], [0.0, 2000.0]),
|
||||
],
|
||||
);
|
||||
let mut out = Vec::new();
|
||||
beam_segments(&face, &mut out);
|
||||
assert!(out.len() / 4 <= 64, "{} pieces", out.len() / 4);
|
||||
let top = out.iter().map(|v| v.pos[1]).fold(f32::MIN, f32::max);
|
||||
let bottom = out.iter().map(|v| v.pos[1]).fold(f32::MAX, f32::min);
|
||||
assert_eq!(
|
||||
(bottom, top),
|
||||
(0.0, 2000.0),
|
||||
"the pieces still span the whole beam"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portal_faces_take_the_whole_portal_texture_at_full_light() {
|
||||
let mut face = quad(9, 0, [([0.0; 3], [0.0; 2]); 4]).to_vec();
|
||||
as_portal_texture(&mut face);
|
||||
let uvs: Vec<[f32; 2]> = face.iter().map(|v| v.uv).collect();
|
||||
assert_eq!(uvs, [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]);
|
||||
assert!(
|
||||
face.iter()
|
||||
.all(|v| v.layer == 0 && v.color == 0xFFFF_FFFF && v.light == 0x0000_f0f0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizon_keeps_no_portal_or_beam_face_under_a_pack() {
|
||||
let mut run = Vec::new();
|
||||
for layer in [0, 6, 9, 10, 2] {
|
||||
run.extend(quad(layer, 0, [([0.0; 3], [0.0; 2]); 4]));
|
||||
}
|
||||
let mut rest = Vec::new();
|
||||
extend_without_portals(&mut rest, &run);
|
||||
let kept: Vec<u32> = rest.chunks(4).map(|q| q[0].layer).collect();
|
||||
assert_eq!(kept, [0, 2]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod crack_projection_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -35,7 +35,9 @@ pub(super) fn images(defs: Vec<CustomImage>) -> Vec<PackImage> {
|
||||
defs.into_iter()
|
||||
.map(|def| PackImage {
|
||||
format: format(def.format),
|
||||
dimension: if def.is_3d() {
|
||||
dimension: if def.is_1d() {
|
||||
wgpu::TextureViewDimension::D1
|
||||
} else if def.is_3d() {
|
||||
wgpu::TextureViewDimension::D3
|
||||
} else {
|
||||
wgpu::TextureViewDimension::D2
|
||||
@@ -51,6 +53,15 @@ pub(super) fn images(defs: Vec<CustomImage>) -> Vec<PackImage> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The texture dimension a view of `view` needs.
|
||||
fn texture_dimension(view: wgpu::TextureViewDimension) -> wgpu::TextureDimension {
|
||||
match view {
|
||||
wgpu::TextureViewDimension::D1 => wgpu::TextureDimension::D1,
|
||||
wgpu::TextureViewDimension::D3 => wgpu::TextureDimension::D3,
|
||||
_ => wgpu::TextureDimension::D2,
|
||||
}
|
||||
}
|
||||
|
||||
/// The wgpu format of an image's internal format: a storage format of the same channels.
|
||||
pub(super) fn format(f: Format) -> wgpu::TextureFormat {
|
||||
use wgpu::TextureFormat as T;
|
||||
@@ -85,7 +96,9 @@ pub(super) fn makeable(device: &wgpu::Device, def: &CustomImage) -> Result<(), S
|
||||
));
|
||||
}
|
||||
let limits = device.limits();
|
||||
let largest = if def.is_3d() {
|
||||
let largest = if def.is_1d() {
|
||||
limits.max_texture_dimension_1d
|
||||
} else if def.is_3d() {
|
||||
limits.max_texture_dimension_3d
|
||||
} else {
|
||||
limits.max_texture_dimension_2d
|
||||
@@ -128,11 +141,7 @@ pub(super) fn resize_images(
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: if image.dimension == wgpu::TextureViewDimension::D3 {
|
||||
wgpu::TextureDimension::D3
|
||||
} else {
|
||||
wgpu::TextureDimension::D2
|
||||
},
|
||||
dimension: texture_dimension(image.dimension),
|
||||
format: image.format,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | usage,
|
||||
view_formats: &[],
|
||||
@@ -321,11 +330,7 @@ pub(super) fn stand_ins(
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: if dimension == wgpu::TextureViewDimension::D3 {
|
||||
wgpu::TextureDimension::D3
|
||||
} else {
|
||||
wgpu::TextureDimension::D2
|
||||
},
|
||||
dimension: texture_dimension(dimension),
|
||||
format,
|
||||
usage: wgpu::TextureUsages::STORAGE_BINDING
|
||||
| wgpu::TextureUsages::COPY_DST
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
//! Every colortex buffer a program reads or writes has two textures, A and B. Programs read
|
||||
//! "main" and write "alt", and each buffer a program wrote flips afterwards (6.1.6). The
|
||||
//! mapping carries over to the next frame, so a buffer that is not cleared reads back what was
|
||||
//! written last (OQ-8). Buffers are cleared at the start of the frame, "main" only.
|
||||
//! written last (OQ-8). A cleared buffer has both textures cleared at the start of the frame:
|
||||
//! with an odd number of flips a frame, the texture a program writes was "main" the frame before
|
||||
//! and may still hold what was stored into it after that frame's clear (Iris 1.11.7 blends onto
|
||||
//! the clear value there, probed with a pack of our own).
|
||||
//! Pack buffers store rows as GL does (row 0 at the bottom: the translator negates clip y), so
|
||||
//! the world is copied in upside down, and its reverse-Z depth becomes GL window depth.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use glam::{DMat4, DVec3};
|
||||
use horizon_shaders::{Format, Group, Kind, Post, Val};
|
||||
use horizon_shaders::{Format, Group, Kind, Post, SHADOW_COLORS, Val};
|
||||
|
||||
use super::pack_draw::DrawUniforms;
|
||||
use super::pack_entities::{PROGRAMS, PackEntities};
|
||||
@@ -28,6 +31,9 @@ const BUFFERS: usize = horizon_shaders::BUFFERS;
|
||||
#[cfg(test)]
|
||||
mod world_target_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod higher_shadowcolor_tests;
|
||||
|
||||
/// The texture behind a pack sampler name.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Source {
|
||||
@@ -365,6 +371,8 @@ struct Pass {
|
||||
/// Bind groups by the sides read (bits 0–15), the buffers whose mip chain is current (bits
|
||||
/// 16–31) and the shadowcolor sides read (bits 32–33).
|
||||
groups: HashMap<u64, wgpu::BindGroup>,
|
||||
/// The same for End portal draws, with `end_portal.png` bound as the atlas.
|
||||
portal_groups: HashMap<u64, wgpu::BindGroup>,
|
||||
/// (binding, target) of each image: for a buffer, the side its sampler reads.
|
||||
images: Vec<(u32, Image)>,
|
||||
/// Binds the pack's storage buffers in set 4.
|
||||
@@ -539,6 +547,10 @@ pub struct PackPost {
|
||||
shadow_targets: Option<super::pack_shadow::ShadowTargets>,
|
||||
/// What draws terrain from the light (`shadow`), and translucent terrain (`shadow_water`).
|
||||
shadow_terrain: Option<Pass>,
|
||||
/// `end_portal.png`'s atlas rect this frame, and a texture of its own copied from it that
|
||||
/// End portal draws bind as their atlas.
|
||||
portal_rect: [f32; 4],
|
||||
portal_texture: Option<([f32; 4], wgpu::TextureView)>,
|
||||
shadow_water: Option<Pass>,
|
||||
shadow_comp: Vec<Pass>,
|
||||
/// Buffers with mipmaps (filterable ones a program asks them for).
|
||||
@@ -566,6 +578,10 @@ pub struct PackPost {
|
||||
composite_pre: u16,
|
||||
formats: [wgpu::TextureFormat; BUFFERS],
|
||||
clears: [Option<[f32; 4]>; BUFFERS],
|
||||
/// Buffers of a three-channel format, and whether their textures are new: then they get
|
||||
/// alpha 1 before the next frame.
|
||||
rgb: u16,
|
||||
rgb_unset: bool,
|
||||
used: u16,
|
||||
/// `size.buffer.*`.
|
||||
sizes: [Option<horizon_shaders::BufferSize>; BUFFERS],
|
||||
@@ -650,6 +666,33 @@ fn texture_format(f: Format, features: wgpu::Features) -> wgpu::TextureFormat {
|
||||
}
|
||||
}
|
||||
|
||||
/// The SNORM wgpu format of SNORM pack format `f`, where this adapter can render to it. Iris
|
||||
/// stores such a buffer clamped to [-1, 1] and quantised to its bits; elsewhere the half floats of
|
||||
/// [`texture_format`] stand in.
|
||||
fn snorm_format(
|
||||
f: Format,
|
||||
features: wgpu::Features,
|
||||
adapter: &wgpu::Adapter,
|
||||
) -> Option<wgpu::TextureFormat> {
|
||||
use wgpu::TextureFormat as T;
|
||||
let format = match (f.kind, f.bits, f.channels) {
|
||||
(Kind::Snorm, 8, 1) => T::R8Snorm,
|
||||
(Kind::Snorm, 8, 2) => T::Rg8Snorm,
|
||||
(Kind::Snorm, 8, _) => T::Rgba8Snorm,
|
||||
(Kind::Snorm, 16, 1) => T::R16Snorm,
|
||||
(Kind::Snorm, 16, 2) => T::Rg16Snorm,
|
||||
(Kind::Snorm, 16, _) => T::Rgba16Snorm,
|
||||
_ => return None,
|
||||
};
|
||||
let mut needs = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
|
||||
if f.bits == 16 {
|
||||
needs |= wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
|
||||
}
|
||||
let renders = (adapter.get_texture_format_features(format).allowed_usages)
|
||||
.contains(wgpu::TextureUsages::RENDER_ATTACHMENT);
|
||||
(features.contains(needs) && renders).then_some(format)
|
||||
}
|
||||
|
||||
/// The texture behind sampler `name` in a program of the shadow buffers (`shadow_space`: buffer
|
||||
/// n is shadowcolor n) or of the others, when the shadow pass runs (`shadow_pass`). `shadow` is
|
||||
/// `shadowtex1` when the program also declares `watershadow` (6.3).
|
||||
@@ -666,11 +709,7 @@ fn fits(ty: &str, sample_type: wgpu::TextureSampleType) -> bool {
|
||||
}
|
||||
|
||||
fn source(name: &str, shadow_space: bool, shadow_pass: bool, watershadow: bool) -> Source {
|
||||
let shadow_color = match name {
|
||||
"shadowcolor" | "shadowcolor0" => Some(0),
|
||||
"shadowcolor1" => Some(1),
|
||||
_ => None,
|
||||
};
|
||||
let shadow_color = horizon_shaders::shadow_color(name);
|
||||
let shadow_depth = match name {
|
||||
"shadowtex0" | "watershadow" | "shadowtex0HW" => Some(0),
|
||||
"shadowtex1" | "shadowtex1HW" => Some(1),
|
||||
@@ -1039,6 +1078,8 @@ struct Resources<'a> {
|
||||
gpu: Gpu,
|
||||
/// The buffers whose format can be bound as an image.
|
||||
storable: u16,
|
||||
/// The buffers of a three-channel format (`rgb`): they have no alpha to write, and read 1.
|
||||
rgb: u16,
|
||||
/// The stage (`texture.<stage>`) of the programs built so far, and the buffers one of them
|
||||
/// flipped: a `texture.<stage>.colortexN` no longer stands in for buffer N in the stage's
|
||||
/// later programs, which read what was written, as in Iris. `final` continues
|
||||
@@ -1061,7 +1102,7 @@ fn bindings(
|
||||
buffers: &[u32],
|
||||
geometry: Option<&Geometry<'_>>,
|
||||
formats: &[wgpu::TextureFormat; BUFFERS],
|
||||
shadow: Option<&[wgpu::TextureFormat; 2]>,
|
||||
shadow: Option<&[wgpu::TextureFormat; SHADOW_COLORS]>,
|
||||
notes: &mut Vec<String>,
|
||||
pack: &Resources<'_>,
|
||||
) -> Option<(
|
||||
@@ -1253,7 +1294,9 @@ fn bindings(
|
||||
notes.push(format!("{name}: `{}` has format {}", i.name, i.format));
|
||||
return None;
|
||||
};
|
||||
let dimension = if i.ty.ends_with("3D") {
|
||||
let dimension = if i.ty.ends_with("1D") {
|
||||
wgpu::TextureViewDimension::D1
|
||||
} else if i.ty.ends_with("3D") {
|
||||
wgpu::TextureViewDimension::D3
|
||||
} else {
|
||||
wgpu::TextureViewDimension::D2
|
||||
@@ -1381,7 +1424,7 @@ fn build_pass(
|
||||
device: &wgpu::Device,
|
||||
p: horizon_shaders::PostProgram,
|
||||
formats: &[wgpu::TextureFormat; BUFFERS],
|
||||
shadow: Option<&[wgpu::TextureFormat; 2]>,
|
||||
shadow: Option<&[wgpu::TextureFormat; SHADOW_COLORS]>,
|
||||
screen: wgpu::TextureFormat,
|
||||
notes: &mut Vec<String>,
|
||||
geometry: Option<&Geometry<'_>>,
|
||||
@@ -1392,7 +1435,7 @@ fn build_pass(
|
||||
let features = device.features();
|
||||
// The frame's uniforms, then the pack's custom ones.
|
||||
let index = |name: &str| {
|
||||
pack_uniforms::index(name).or_else(|| {
|
||||
pack_uniforms::index_in(name, geometry.is_some()).or_else(|| {
|
||||
let k = custom.iter().position(|c| c == name)?;
|
||||
Some(pack_uniforms::NAMES.len() + k)
|
||||
})
|
||||
@@ -1441,6 +1484,12 @@ fn build_pass(
|
||||
} else if let Some(&buffer) = p.buffers.get(l) {
|
||||
let format = formats[buffer as usize];
|
||||
let mut state = target(format);
|
||||
if !p.group.shadow()
|
||||
&& pack.rgb & 1 << buffer != 0
|
||||
&& let Some(t) = &mut state
|
||||
{
|
||||
t.write_mask = wgpu::ColorWrites::COLOR;
|
||||
}
|
||||
if let (Some(t), Some(b)) = (
|
||||
&mut state,
|
||||
p.buffer_blends
|
||||
@@ -1605,6 +1654,7 @@ fn build_pass(
|
||||
atlas: geometry.and_then(|g| g.atlas.clone()),
|
||||
ids: geometry.is_some_and(|g| g.ids.is_some()),
|
||||
groups: HashMap::new(),
|
||||
portal_groups: HashMap::new(),
|
||||
copies: image_buffers(&images) & reads,
|
||||
reads: reads | image_buffers(&images),
|
||||
buffers: !interface.buffers.is_empty(),
|
||||
@@ -1638,7 +1688,7 @@ fn build_compute(
|
||||
group: Group,
|
||||
c: horizon_shaders::ComputeProgram,
|
||||
formats: &[wgpu::TextureFormat; BUFFERS],
|
||||
shadow: Option<&[wgpu::TextureFormat; 2]>,
|
||||
shadow: Option<&[wgpu::TextureFormat; SHADOW_COLORS]>,
|
||||
notes: &mut Vec<String>,
|
||||
custom: &[String],
|
||||
pack: &Resources<'_>,
|
||||
@@ -1739,6 +1789,7 @@ fn build_compute(
|
||||
atlas: None,
|
||||
ids: false,
|
||||
groups: HashMap::new(),
|
||||
portal_groups: HashMap::new(),
|
||||
buffers: !interface.buffers.is_empty(),
|
||||
images,
|
||||
compute: Vec::new(),
|
||||
@@ -1763,16 +1814,26 @@ impl PackPost {
|
||||
// What the pack's shaders need of its storage buffers, before its parts move out.
|
||||
let minimums = post.buffer_minimums();
|
||||
let features = device.features();
|
||||
let formats: [wgpu::TextureFormat; BUFFERS] =
|
||||
std::array::from_fn(|i| texture_format(post.config.buffers[i].format, features));
|
||||
let formats: [wgpu::TextureFormat; BUFFERS] = std::array::from_fn(|i| {
|
||||
let f = post.config.buffers[i].format;
|
||||
snorm_format(f, features, adapter).unwrap_or_else(|| texture_format(f, features))
|
||||
});
|
||||
let sky_clear = Some(&post.config.buffers[0])
|
||||
.filter(|b| b.clear)
|
||||
.and_then(|b| b.clear_color);
|
||||
// A three-channel buffer reads alpha 1 whatever is written or cleared, as in Iris: its
|
||||
// passes leave alpha alone, and its clears and first contents set it to 1.
|
||||
let rgb = (0..BUFFERS)
|
||||
.filter(|&i| post.config.buffers[i].format.channels == 3)
|
||||
.fold(0u16, |m, i| m | 1 << i);
|
||||
let clears = std::array::from_fn(|i| {
|
||||
let b = &post.config.buffers[i];
|
||||
b.clear.then(|| {
|
||||
b.clear_color
|
||||
.unwrap_or(if i == 1 { [1.0; 4] } else { [0.0; 4] })
|
||||
let mut c = (b.clear_color).unwrap_or(if i == 1 { [1.0; 4] } else { [0.0; 4] });
|
||||
if rgb & 1 << i != 0 {
|
||||
c[3] = 1.0;
|
||||
}
|
||||
c
|
||||
})
|
||||
});
|
||||
let mut notes = post.notes;
|
||||
@@ -1784,6 +1845,8 @@ impl PackPost {
|
||||
}
|
||||
let textures = pack_textures::upload(device, queue, post.textures, &mut notes);
|
||||
let shadow_config = post.config.shadow.clone();
|
||||
// shadowcolor2 to 7 only for a pack that names `HIGHER_SHADOWCOLOR`.
|
||||
let shadow_colors = (1u16 << post.shadow_colors) - 1;
|
||||
let mut custom = post.custom;
|
||||
let custom_names: Vec<String> = custom.uniforms().map(|u| u.0.to_owned()).collect();
|
||||
// A first run finds the names neither the frame nor the pack defines.
|
||||
@@ -1799,13 +1862,14 @@ impl PackPost {
|
||||
let mut used = 1u16;
|
||||
let draws = DrawUniforms::new(device);
|
||||
// The shadow buffers' formats when the shadow pass runs; their programs see them as
|
||||
// buffers 0 and 1.
|
||||
let shadow_formats: Option<[wgpu::TextureFormat; 2]> = post.shadow_pass.then(|| {
|
||||
std::array::from_fn(|n| texture_format(shadow_config.colors[n].format, features))
|
||||
});
|
||||
// buffers 0 to 7.
|
||||
let shadow_formats: Option<[wgpu::TextureFormat; SHADOW_COLORS]> =
|
||||
post.shadow_pass.then(|| {
|
||||
std::array::from_fn(|n| texture_format(shadow_config.colors[n].format, features))
|
||||
});
|
||||
let shadow_space: [wgpu::TextureFormat; BUFFERS] = std::array::from_fn(|i| {
|
||||
shadow_formats
|
||||
.filter(|_| i < 2)
|
||||
.filter(|_| i < SHADOW_COLORS)
|
||||
.map_or(formats[i], |f| f[i])
|
||||
});
|
||||
let (mut storage, mut copied, mut image_copied, mut snapshotted) = (0u16, 0u16, 0u64, 0u64);
|
||||
@@ -1873,6 +1937,7 @@ impl PackPost {
|
||||
indices: indices.as_ref(),
|
||||
gpu,
|
||||
storable,
|
||||
rgb,
|
||||
released: std::cell::Cell::new(("", 0)),
|
||||
stand_ins: &stand_ins,
|
||||
};
|
||||
@@ -2020,6 +2085,7 @@ impl PackPost {
|
||||
post.entities_translucent,
|
||||
post.particles,
|
||||
post.damaged_block,
|
||||
post.beacon_beam,
|
||||
];
|
||||
let late = [
|
||||
false,
|
||||
@@ -2027,6 +2093,7 @@ impl PackPost {
|
||||
post.translucent_late,
|
||||
post.particles_late,
|
||||
false,
|
||||
true,
|
||||
];
|
||||
let shadow_only = (post.shadow_only_player, post.shadow_only_lit_blocks);
|
||||
let shadow_programs = [post.shadow_entities, post.shadow_block];
|
||||
@@ -2242,7 +2309,7 @@ impl PackPost {
|
||||
0
|
||||
};
|
||||
m | own | p.shadow_reads
|
||||
});
|
||||
}) & shadow_colors;
|
||||
let depth1 = every_pass()
|
||||
.flat_map(|p| &p.samplers)
|
||||
.any(|s| matches!(s.source, Source::ShadowDepth(1, _)));
|
||||
@@ -2318,7 +2385,13 @@ impl PackPost {
|
||||
}
|
||||
});
|
||||
let float = wgpu::TextureSampleType::Float { filterable: true };
|
||||
let blit_targets = std::iter::once(target(formats[0]));
|
||||
let mut world_target = target(formats[0]);
|
||||
if rgb & 1 != 0
|
||||
&& let Some(t) = &mut world_target
|
||||
{
|
||||
t.write_mask = wgpu::ColorWrites::COLOR;
|
||||
}
|
||||
let blit_targets = std::iter::once(world_target);
|
||||
// Cleared by the pass's load operation; the shader writes nothing there.
|
||||
let blit_targets: Vec<_> = blit_targets
|
||||
.chain(cleared.iter().map(|&b| {
|
||||
@@ -2425,6 +2498,8 @@ impl PackPost {
|
||||
depth2,
|
||||
shadow_targets,
|
||||
shadow_terrain,
|
||||
portal_rect: [0.0; 4],
|
||||
portal_texture: None,
|
||||
shadow_water,
|
||||
shadow_comp,
|
||||
mipped,
|
||||
@@ -2443,6 +2518,8 @@ impl PackPost {
|
||||
composite_pre: mask(&post.composite_pre),
|
||||
formats,
|
||||
clears,
|
||||
rgb,
|
||||
rgb_unset: true,
|
||||
used,
|
||||
storage,
|
||||
copied,
|
||||
@@ -2810,7 +2887,10 @@ impl PackPost {
|
||||
let group = self.bind(device, queue, encoder, pass, size);
|
||||
let mut cp = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some(&pass.name),
|
||||
timestamp_writes: None,
|
||||
timestamp_writes: self
|
||||
.timing
|
||||
.as_mut()
|
||||
.and_then(|t| t.compute_writes(&pass.name)),
|
||||
});
|
||||
cp.set_pipeline(&pipeline);
|
||||
cp.set_bind_group(0, &group, &[]);
|
||||
@@ -2839,7 +2919,7 @@ impl PackPost {
|
||||
// one clearing them.
|
||||
let (depth_ops, shadow_ops) = match &mut self.shadow_targets {
|
||||
Some(s) if pass.group.shadow() => s.clear_ops(pass.writes.iter().map(|w| w.1)),
|
||||
_ => (super::pack_shadow::load(), [load; 2]),
|
||||
_ => (super::pack_shadow::load(), [load; SHADOW_COLORS]),
|
||||
};
|
||||
let shadow = self.shadow_targets.as_ref().filter(|_| pass.group.shadow());
|
||||
let mut attachments: Vec<Option<wgpu::RenderPassColorAttachment>> = Vec::new();
|
||||
@@ -3024,6 +3104,42 @@ impl PackPost {
|
||||
continue;
|
||||
}
|
||||
let group = self.bind(device, queue, encoder, pass, size);
|
||||
// End portal faces bind `end_portal.png` as the atlas, a texture of its own, so packs
|
||||
// that wrap it with their own coordinates sample the portal sprite only.
|
||||
let portal_group = if geometry
|
||||
.draws
|
||||
.iter()
|
||||
.any(|d| d.pack && d.program == program && d.portal)
|
||||
&& let Some(atlas) = pass.atlas.clone().or_else(|| self.atlas.clone())
|
||||
&& self.portal_rect[2] > 0.0
|
||||
{
|
||||
if self
|
||||
.portal_texture
|
||||
.as_ref()
|
||||
.is_none_or(|(rect, _)| *rect != self.portal_rect)
|
||||
{
|
||||
pass.portal_groups.clear();
|
||||
let view = super::pack_weather::copy_sprite(
|
||||
device,
|
||||
encoder,
|
||||
atlas.texture(),
|
||||
self.portal_rect,
|
||||
);
|
||||
self.portal_texture = Some((self.portal_rect, view));
|
||||
}
|
||||
let view = self.portal_texture.as_ref().map(|p| p.1.clone());
|
||||
// `bind` caches groups by a key without the atlas: the portal groups are kept
|
||||
// in a cache of their own, swapped in with the texture.
|
||||
let saved = std::mem::replace(&mut pass.atlas, view);
|
||||
let groups =
|
||||
std::mem::replace(&mut pass.groups, std::mem::take(&mut pass.portal_groups));
|
||||
let g = self.bind(device, queue, encoder, pass, size);
|
||||
pass.portal_groups = std::mem::replace(&mut pass.groups, groups);
|
||||
pass.atlas = saved;
|
||||
Some(g)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
{
|
||||
let mut rp = self.begin(encoder, pass);
|
||||
rp.set_pipeline(pass.render());
|
||||
@@ -3036,7 +3152,14 @@ impl PackPost {
|
||||
rp.set_bind_group(1, &geometry.table_group, &[]);
|
||||
rp.set_vertex_buffer(0, geometry.vertices());
|
||||
rp.set_index_buffer(geometry.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
let mut on_portal = false;
|
||||
for d in draws {
|
||||
if let Some(portal) = &portal_group
|
||||
&& d.portal != on_portal
|
||||
{
|
||||
rp.set_bind_group(0, if d.portal { portal } else { &group }, &[]);
|
||||
on_portal = d.portal;
|
||||
}
|
||||
if pass.draw {
|
||||
rp.set_bind_group(2, &geometry.group, &[geometry.offset(d.slot)]);
|
||||
}
|
||||
@@ -3110,9 +3233,11 @@ impl PackPost {
|
||||
entities: &[super::Vertex],
|
||||
spans: &[super::pack_draw::EntitySpan],
|
||||
crumbling: [f32; 4],
|
||||
portal: [f32; 4],
|
||||
rest: &mut Vec<super::Vertex>,
|
||||
skin: Option<&super::entity_skin::SkinFrame>,
|
||||
) {
|
||||
self.portal_rect = portal;
|
||||
let shadow = self.shadow_casters();
|
||||
let Some(e) = &mut self.entities else {
|
||||
rest.clear();
|
||||
@@ -3436,16 +3561,25 @@ impl PackPost {
|
||||
}
|
||||
|
||||
/// Regenerates the mip chain of buffer `b`'s side that programs read.
|
||||
fn mipmap(&self, encoder: &mut wgpu::CommandEncoder, b: usize) {
|
||||
fn mipmap(&mut self, encoder: &mut wgpu::CommandEncoder, b: usize) {
|
||||
let (Some(targets), Some((pipeline, _))) = (&self.targets, self.down.get(&self.formats[b]))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(mips) = &targets.mips[b] else { return };
|
||||
let side = &mips[usize::from(self.main & 1 << b != 0)];
|
||||
// Timed as one pass per buffer (its first level), so the pass timer shows the chains.
|
||||
let label = format!("mipmaps colortex{b}");
|
||||
let mut first = true;
|
||||
for (attachment, group) in &side.levels {
|
||||
let timestamp_writes = if std::mem::take(&mut first) {
|
||||
self.timing.as_mut().and_then(|t| t.writes(&label))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack mipmap"),
|
||||
timestamp_writes,
|
||||
color_attachments: &[attach(
|
||||
attachment,
|
||||
wgpu::Operations {
|
||||
@@ -3559,6 +3693,7 @@ impl PackPost {
|
||||
return;
|
||||
}
|
||||
self.setup_pending = true;
|
||||
self.rgb_unset = true;
|
||||
let sizes: [[u32; 2]; BUFFERS] =
|
||||
std::array::from_fn(|i| self.sizes[i].map_or(size, |s| s.resolve(size)));
|
||||
let full_chain = |size: [u32; 2]| 32 - size[0].max(size[1]).max(1).leading_zeros();
|
||||
@@ -4015,6 +4150,26 @@ impl PackPost {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
// Three-channel buffers start out with alpha 1 on both sides: a pass writes the side the
|
||||
// frame's clear didn't touch.
|
||||
if self.rgb_unset {
|
||||
self.rgb_unset = false;
|
||||
let start = wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: wgpu::StoreOp::Store,
|
||||
};
|
||||
for b in (0..BUFFERS).filter(|&b| self.rgb & 1 << b != 0) {
|
||||
for alt in [false, true] {
|
||||
if let Some(v) = view(self.main, b, alt) {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack three-channel start"),
|
||||
color_attachments: &[attach(v, start)],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let clear_op = |b: usize| {
|
||||
let c = self.clears[b].unwrap_or_default();
|
||||
wgpu::Operations {
|
||||
@@ -4035,26 +4190,35 @@ impl PackPost {
|
||||
// Attachments share a size and stay within the device's count and byte limits.
|
||||
let limits = device.limits();
|
||||
let max_attachments = (limits.max_color_attachments as usize).min(8);
|
||||
let mut by_size: Vec<([u32; 2], Vec<usize>)> = Vec::new();
|
||||
for &b in &self.other_clears {
|
||||
// The buffers the blit doesn't clear, and the other texture of every cleared buffer.
|
||||
let alt_clears = (0..BUFFERS)
|
||||
.filter(|&b| self.clears[b].is_some() && view(self.main, b, true).is_some())
|
||||
.map(|b| (b, true));
|
||||
let mut by_size: Vec<([u32; 2], Vec<(usize, bool)>)> = Vec::new();
|
||||
for (b, alt) in self
|
||||
.other_clears
|
||||
.iter()
|
||||
.map(|&b| (b, false))
|
||||
.chain(alt_clears)
|
||||
{
|
||||
let at = targets.sizes[b];
|
||||
match by_size.iter_mut().find(|(s, buffers)| {
|
||||
let bytes = buffers
|
||||
.iter()
|
||||
.fold(0, |bytes, &b| attachment_bytes(bytes, self.formats[b]));
|
||||
.fold(0, |bytes, &(b, _)| attachment_bytes(bytes, self.formats[b]));
|
||||
*s == at
|
||||
&& buffers.len() < max_attachments
|
||||
&& attachment_bytes(bytes, self.formats[b])
|
||||
<= limits.max_color_attachment_bytes_per_sample
|
||||
}) {
|
||||
Some((_, l)) => l.push(b),
|
||||
None => by_size.push((at, vec![b])),
|
||||
Some((_, l)) => l.push((b, alt)),
|
||||
None => by_size.push((at, vec![(b, alt)])),
|
||||
}
|
||||
}
|
||||
for (_, chunk) in &by_size {
|
||||
let attachments: Vec<_> = chunk
|
||||
.iter()
|
||||
.map(|&b| attach(view(self.main, b, false).expect("used buffer"), clear_op(b)))
|
||||
.map(|&(b, alt)| attach(view(self.main, b, alt).expect("used buffer"), clear_op(b)))
|
||||
.collect();
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack clear"),
|
||||
@@ -4087,9 +4251,18 @@ impl PackPost {
|
||||
self.blit_group = Some(([world.clone(), world_depth.clone()], group));
|
||||
}
|
||||
{
|
||||
// The blit leaves a three-channel colortex0's alpha at the 1 it starts with.
|
||||
let world_ops = if self.rgb & 1 != 0 {
|
||||
wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}
|
||||
} else {
|
||||
overwrite
|
||||
};
|
||||
let mut attachments = vec![attach(
|
||||
view(self.main, 0, false).expect("colortex0"),
|
||||
overwrite,
|
||||
world_ops,
|
||||
)];
|
||||
for &b in &self.blit_clears {
|
||||
attachments.push(attach(
|
||||
@@ -4291,6 +4464,9 @@ impl PackPost {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod snorm_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "pack_post/depth_test.rs"]
|
||||
mod depth_test;
|
||||
@@ -4437,6 +4613,7 @@ mod tests {
|
||||
indices: None,
|
||||
gpu: Gpu::of(&adapter, device.features()),
|
||||
storable: 0,
|
||||
rgb: 0,
|
||||
released: std::cell::Cell::new(("", 0)),
|
||||
stand_ins: &stand_ins,
|
||||
};
|
||||
@@ -4514,6 +4691,7 @@ mod tests {
|
||||
indices: None,
|
||||
gpu: Gpu::of(&adapter, device.features()),
|
||||
storable: 0,
|
||||
rgb: 0,
|
||||
released: std::cell::Cell::new(("", 0)),
|
||||
stand_ins: &stand_ins,
|
||||
};
|
||||
@@ -4594,6 +4772,169 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `blend.<program>` of a composite program blends its output onto what the buffer's side
|
||||
/// holds (Iris 1.11.7, probed: 0.25 over 0.75 with SRC_ALPHA ONE_MINUS_SRC_ALPHA ONE ZERO and
|
||||
/// a source alpha of 0.25 reads 0.625).
|
||||
#[test]
|
||||
fn a_composite_program_blends_onto_its_buffer() {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else {
|
||||
return;
|
||||
};
|
||||
// A pass's pipeline layout has six bind group slots.
|
||||
let desc = wgpu::DeviceDescriptor {
|
||||
required_limits: adapter.limits(),
|
||||
..Default::default()
|
||||
};
|
||||
let Ok((device, queue)) = pollster::block_on(adapter.request_device(&desc)) else {
|
||||
return;
|
||||
};
|
||||
let dir = std::env::temp_dir().join(format!("hz-composite-blend-{}", std::process::id()));
|
||||
let shaders = dir.join("shaders");
|
||||
std::fs::create_dir_all(&shaders).unwrap();
|
||||
let file = |name: &str, text: &str| std::fs::write(shaders.join(name), text).unwrap();
|
||||
file(
|
||||
"composite.vsh",
|
||||
"#version 120\nvoid main() { gl_Position = ftransform(); }\n",
|
||||
);
|
||||
file(
|
||||
"composite.fsh",
|
||||
"#version 120\n/* DRAWBUFFERS:3 */\n\
|
||||
void main() { gl_FragData[0] = vec4(0.25, 0.25, 1.0, 0.25); }\n",
|
||||
);
|
||||
let pack = horizon_shaders::Pack::open(&dir).unwrap();
|
||||
let properties = horizon_shaders::Properties::parse(
|
||||
"blend.composite = SRC_ALPHA ONE_MINUS_SRC_ALPHA ONE ZERO\n",
|
||||
&horizon_shaders::Macros::default(),
|
||||
);
|
||||
let mut post = horizon_shaders::load_post(
|
||||
&pack,
|
||||
"",
|
||||
&properties,
|
||||
&horizon_shaders::Macros::standard(),
|
||||
&Default::default(),
|
||||
);
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
let program = post.programs.remove(0);
|
||||
let buffers = pack_images::Buffers::new(
|
||||
&device,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
wgpu::ShaderStages::FRAGMENT,
|
||||
);
|
||||
let stand_ins = std::cell::RefCell::new(Vec::new());
|
||||
let pack = Resources {
|
||||
textures: &[],
|
||||
images: &[],
|
||||
left_out: &[],
|
||||
buffers: &buffers,
|
||||
indices: None,
|
||||
gpu: Gpu::of(&adapter, device.features()),
|
||||
storable: 0,
|
||||
rgb: 0,
|
||||
released: std::cell::Cell::new(("", 0)),
|
||||
stand_ins: &stand_ins,
|
||||
};
|
||||
let draws = DrawUniforms::new(&device);
|
||||
let mut notes = Vec::new();
|
||||
let pass = build_pass(
|
||||
&device,
|
||||
program,
|
||||
&[T::Rgba8Unorm; BUFFERS],
|
||||
None,
|
||||
T::Rgba8Unorm,
|
||||
&mut notes,
|
||||
None,
|
||||
&[],
|
||||
&draws.layout,
|
||||
&pack,
|
||||
)
|
||||
.unwrap_or_else(|| panic!("the pass builds: {notes:?}"));
|
||||
// colortex3 holds (0.75, 0.5, 0.25, 0.5) before the pass.
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size: wgpu::Extent3d {
|
||||
width: 64,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: T::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
let view = texture.create_view(&Default::default());
|
||||
let head = [64.0f32, 1.0, 1.0 / 64.0, 1.0];
|
||||
queue.write_buffer(&pass.ubo, 0, bytemuck::cast_slice(&head));
|
||||
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &pass.layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: pass.ubo.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
let mut encoder = device.create_command_encoder(&Default::default());
|
||||
let before = wgpu::Color {
|
||||
r: 0.75,
|
||||
g: 0.5,
|
||||
b: 0.25,
|
||||
a: 0.5,
|
||||
};
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[attach(
|
||||
&view,
|
||||
wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(before),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
)],
|
||||
..Default::default()
|
||||
});
|
||||
{
|
||||
// Loaded, as `PackPost::render` attaches a buffer a composite pass writes.
|
||||
let load = wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
};
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[attach(&view, load)],
|
||||
..Default::default()
|
||||
});
|
||||
rp.set_pipeline(pass.render());
|
||||
rp.set_bind_group(0, &group, &[]);
|
||||
rp.draw(0..6, 0..1);
|
||||
}
|
||||
let read = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: 256,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
encoder.copy_texture_to_buffer(
|
||||
texture.as_image_copy(),
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &read,
|
||||
layout: wgpu::TexelCopyBufferLayout::default(),
|
||||
},
|
||||
texture.size(),
|
||||
);
|
||||
queue.submit([encoder.finish()]);
|
||||
read.slice(..).map_async(wgpu::MapMode::Read, |_| {});
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
let texel: Vec<u8> = read.slice(..).get_mapped_range().unwrap()[..4].to_vec();
|
||||
// rgb = 0.25 × src + 0.75 × dst, alpha = the source's.
|
||||
let expected = [0.625, 0.4375, 0.4375, 0.25].map(|v: f32| v * 255.0);
|
||||
for (got, want) in texel.iter().zip(expected) {
|
||||
assert!(
|
||||
(f32::from(*got) - want).abs() <= 1.0,
|
||||
"{texel:?} vs {expected:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_formats_map_to_renderable_ones() {
|
||||
let f = |name| texture_format(Format::parse(name).unwrap(), wgpu::Features::empty());
|
||||
@@ -4654,3 +4995,6 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod destination_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod rgb_alpha_tests;
|
||||
@@ -151,14 +151,14 @@ fn pack(
|
||||
post
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
pub(super) struct Fixture {
|
||||
pub(super) adapter: wgpu::Adapter,
|
||||
pub(super) device: wgpu::Device,
|
||||
pub(super) queue: wgpu::Queue,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Option<Self> {
|
||||
pub(super) fn new() -> Option<Self> {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else {
|
||||
eprintln!("Post destination test skipped: no GPU adapter");
|
||||
@@ -198,7 +198,7 @@ impl Fixture {
|
||||
.create_view(&Default::default())
|
||||
}
|
||||
|
||||
fn build(&self, post: Post) -> PackPost {
|
||||
pub(super) fn build(&self, post: Post) -> PackPost {
|
||||
let lightmap = self.texture([1, 1], T::Rgba8Unorm);
|
||||
let post = PackPost::new(
|
||||
&self.device,
|
||||
@@ -226,7 +226,7 @@ impl Fixture {
|
||||
post
|
||||
}
|
||||
|
||||
fn render(&self, post: &mut PackPost, size: [u32; 2]) -> Vec<[u8; 4]> {
|
||||
pub(super) fn render(&self, post: &mut PackPost, size: [u32; 2]) -> Vec<[u8; 4]> {
|
||||
let world = self.texture(size, T::Rgba8Unorm);
|
||||
let depth = self.texture(size, T::Depth32Float);
|
||||
let screen = self.texture(size, T::Rgba8Unorm);
|
||||
@@ -454,3 +454,70 @@ fn fixture_frame(hud: &HudBatch) -> Frame<'_> {
|
||||
|
||||
#[path = "clear_budget_tests.rs"]
|
||||
mod clear_budget_tests;
|
||||
|
||||
/// A cleared buffer written three times a frame flips an odd number of times, so its first write
|
||||
/// goes to the texture that was "main" the frame before, which a later program wrote after that
|
||||
/// frame's clear. Where that first write discards, Iris 1.11.7 reads the clear value (a probe
|
||||
/// pack of our own with these three programs reads 0 there, not the previous frame's 0.25).
|
||||
#[test]
|
||||
fn a_cleared_buffer_starts_every_frame_cleared_on_both_textures() {
|
||||
let Some(gpu) = Fixture::new() else { return };
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let mut add = |name: &str, source: &str| {
|
||||
zip.start_file(
|
||||
format!("shaders/{name}"),
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
};
|
||||
for (program, fragment) in [
|
||||
(
|
||||
"composite",
|
||||
"#version 120\nvarying vec2 uv;\n/* RENDERTARGETS:4 */\n\
|
||||
void main() { if(uv.x>0.5) discard; gl_FragData[0]=vec4(0.5,0.5,0.5,1); }\n",
|
||||
),
|
||||
(
|
||||
"composite1",
|
||||
"#version 120\nuniform sampler2D colortex4;\nvarying vec2 uv;\n/* RENDERTARGETS:4,5 */\n\
|
||||
void main() { gl_FragData[0]=vec4(0.25,0.25,0.25,1); gl_FragData[1]=texture2D(colortex4,uv); }\n",
|
||||
),
|
||||
(
|
||||
"composite2",
|
||||
"#version 120\n/* RENDERTARGETS:4 */\nvoid main() { gl_FragData[0]=vec4(0.125,0.125,0.125,1); }\n",
|
||||
),
|
||||
(
|
||||
"final",
|
||||
"#version 120\nuniform sampler2D colortex5;\nvarying vec2 uv;\n\
|
||||
void main() { gl_FragColor=vec4(texture2D(colortex5,uv).rgb,1); }\n",
|
||||
),
|
||||
] {
|
||||
add(&format!("{program}.vsh"), VERTEX);
|
||||
add(&format!("{program}.fsh"), fragment);
|
||||
}
|
||||
let pack = horizon_shaders::Pack::from_zip_bytes(
|
||||
"both-textures-cleared",
|
||||
zip.finish().unwrap().into_inner(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut macros = horizon_shaders::Macros::standard();
|
||||
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
|
||||
let post = horizon_shaders::load_post(&pack, "", &properties, ¯os, &Default::default());
|
||||
let mut post = gpu.build(post);
|
||||
for frame in 0..3 {
|
||||
let actual = gpu.render(&mut post, [16, 16]);
|
||||
let wrong: Vec<_> = (actual.iter().enumerate())
|
||||
.filter(|(n, p)| {
|
||||
let expected = if n % 16 < 8 {
|
||||
[128, 128, 128, 255]
|
||||
} else {
|
||||
[0, 0, 0, 255]
|
||||
};
|
||||
p.iter().zip(expected).any(|(a, b)| a.abs_diff(b) > 1)
|
||||
})
|
||||
.map(|(n, p)| (n, *p))
|
||||
.take(4)
|
||||
.collect();
|
||||
assert!(wrong.is_empty(), "frame {frame}: {wrong:?}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//! `HIGHER_SHADOWCOLOR`: shadowcolor2 to 7 are written by shadow programs' directives, read by
|
||||
//! them and by the other programs, and cleared to their own colours, as in Iris 1.11.7 (probe
|
||||
//! packs p7951a to c; the PR has the readings). The programs here are original and cut down.
|
||||
use super::destination_tests::Fixture;
|
||||
use super::*;
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
const VERTEX: &str = "#version 120\nvarying vec2 uv;\nvoid main() { uv=gl_MultiTexCoord0.xy; gl_Position=ftransform(); }\n";
|
||||
|
||||
fn pack() -> Post {
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let mut add = |name: &str, source: &str| {
|
||||
zip.start_file(
|
||||
format!("shaders/{name}"),
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
};
|
||||
add("shadowcomp.vsh", VERTEX);
|
||||
add(
|
||||
"shadowcomp.fsh",
|
||||
"#version 120\n/* RENDERTARGETS: 2,7 */\n\
|
||||
void main() { gl_FragData[0]=vec4(0.25,0,0,1); gl_FragData[1]=vec4(0,0.5,0,1); }\n",
|
||||
);
|
||||
// A shadow program reads a higher buffer it didn't write.
|
||||
add("shadowcomp1.vsh", VERTEX);
|
||||
add(
|
||||
"shadowcomp1.fsh",
|
||||
"#version 120\n/* RENDERTARGETS: 3 */\nuniform sampler2D shadowcolor7;\nvarying vec2 uv;\n\
|
||||
void main() { gl_FragData[0]=texture2D(shadowcolor7,uv); }\n",
|
||||
);
|
||||
add("final.vsh", VERTEX);
|
||||
// Pixel column x shows shadowcolor 2, 3, 5 and 6 in turn.
|
||||
add(
|
||||
"final.fsh",
|
||||
"#version 330 compatibility\nconst int shadowMapResolution=16;\n\
|
||||
const vec4 shadowcolor5ClearColor=vec4(0,0,0.75,1);\n\
|
||||
uniform sampler2D shadowcolor2;\nuniform sampler2D shadowcolor3;\n\
|
||||
uniform sampler2D shadowcolor5;\nuniform sampler2D shadowcolor6;\nin vec2 uv;\n\
|
||||
void main() {\n vec4 c[4] = vec4[4](texture(shadowcolor2,uv), texture(shadowcolor3,uv),\n\
|
||||
texture(shadowcolor5,uv), texture(shadowcolor6,uv));\n\
|
||||
gl_FragColor = c[int(gl_FragCoord.x) % 4];\n}\n",
|
||||
);
|
||||
add(
|
||||
"shaders.properties",
|
||||
"shadow.enabled=true\niris.features.required = HIGHER_SHADOWCOLOR\n",
|
||||
);
|
||||
let pack = horizon_shaders::Pack::from_zip_bytes(
|
||||
"original-higher-shadowcolor",
|
||||
zip.finish().unwrap().into_inner(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut macros = horizon_shaders::Macros::standard();
|
||||
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
|
||||
horizon_shaders::load_post(&pack, "", &properties, ¯os, &Default::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn higher_shadowcolor_buffers_are_written_read_and_cleared() {
|
||||
let Some(gpu) = Fixture::new() else { return };
|
||||
let mut post = gpu.build(pack());
|
||||
// Two frames: the second starts from the first one's buffers.
|
||||
for frame in 0..2 {
|
||||
let pixels = gpu.render(&mut post, [8, 8]);
|
||||
// shadowcolor2 written red, 3 a copy of 7 (green), 5 its clear colour, 6 white.
|
||||
let expected = [
|
||||
[64, 0, 0, 255],
|
||||
[0, 128, 0, 255],
|
||||
[0, 0, 191, 255],
|
||||
[255; 4],
|
||||
];
|
||||
for (x, want) in expected.iter().enumerate() {
|
||||
let got = pixels[x];
|
||||
assert!(
|
||||
got.iter().zip(want).all(|(g, w)| g.abs_diff(*w) <= 1),
|
||||
"frame {frame}, column {x}: {got:?}, expected {want:?}; row {:?}",
|
||||
&pixels[..4]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//! Three-channel buffers read alpha 1, as in Iris 1.11.7: written with alpha 0, cleared with
|
||||
//! alpha 0, or never written. The values come from probe pack p8106 run in Iris (the PR has
|
||||
//! them); the programs here are the probe's, cut down.
|
||||
use super::destination_tests::Fixture;
|
||||
use super::*;
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
const VERTEX: &str = "#version 120\nvarying vec2 uv;\nvoid main() { uv=gl_MultiTexCoord0.xy; gl_Position=ftransform(); }\n";
|
||||
|
||||
fn pack() -> Post {
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let mut add = |name: &str, source: &str| {
|
||||
zip.start_file(
|
||||
format!("shaders/{name}"),
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
};
|
||||
add("composite.vsh", VERTEX);
|
||||
add(
|
||||
"composite.fsh",
|
||||
"#version 120\n/* RENDERTARGETS: 1,5,7 */\n\
|
||||
/*\nconst int colortex0Format = RGB16F;\nconst int colortex1Format = RGB16F;\n\
|
||||
const int colortex2Format = RGB8;\nconst int colortex3Format = RGB32F;\n\
|
||||
const int colortex4Format = RGB32UI;\nconst int colortex5Format = RGBA16F;\n\
|
||||
const int colortex7Format = RGB16;\n*/\n\
|
||||
const vec4 colortex2ClearColor = vec4(0.25, 0.5, 0.75, 0.0);\n\
|
||||
const bool colortex3Clear = false;\n\
|
||||
void main() {\n gl_FragData[0] = vec4(0.25, 0.5, 0.75, 0.0);\n\
|
||||
gl_FragData[1] = vec4(0.25, 0.5, 0.75, 0.0);\n gl_FragData[2] = vec4(0.25, 0.5, 0.75, 0.0);\n}\n",
|
||||
);
|
||||
add("composite1.vsh", VERTEX);
|
||||
add(
|
||||
"composite1.fsh",
|
||||
"#version 330 compatibility\n/* RENDERTARGETS: 4 */\nlayout(location = 0) out uvec4 o;\n\
|
||||
void main() { o = uvec4(5u, 6u, 7u, 0u); }\n",
|
||||
);
|
||||
add("final.vsh", VERTEX);
|
||||
// Pixel column x shows buffer x % 8: its alpha in red, its green in green.
|
||||
add(
|
||||
"final.fsh",
|
||||
"#version 330 compatibility\nuniform sampler2D colortex0;\nuniform sampler2D colortex1;\n\
|
||||
uniform sampler2D colortex2;\nuniform sampler2D colortex3;\nuniform usampler2D colortex4;\n\
|
||||
uniform sampler2D colortex5;\nuniform sampler2D colortex7;\n\
|
||||
void main() {\n ivec2 p = ivec2(gl_FragCoord.xy);\n vec4 c = vec4(0.0);\n\
|
||||
switch (p.x % 8) {\n\
|
||||
case 0: c = texelFetch(colortex0, p, 0); break;\n\
|
||||
case 1: c = texelFetch(colortex1, p, 0); break;\n\
|
||||
case 2: c = texelFetch(colortex2, p, 0); break;\n\
|
||||
case 3: c = texelFetch(colortex3, p, 0); break;\n\
|
||||
case 4: c = vec4(texelFetch(colortex4, p, 0)) / 12.0; c.a *= 12.0; break;\n\
|
||||
case 5: c = texelFetch(colortex5, p, 0); break;\n\
|
||||
case 7: c = texelFetch(colortex7, p, 0); break;\n\
|
||||
}\n gl_FragColor = vec4(c.a, c.g, 0.0, 1.0);\n}\n",
|
||||
);
|
||||
let pack = horizon_shaders::Pack::from_zip_bytes(
|
||||
"original-rgb-alpha",
|
||||
zip.finish().unwrap().into_inner(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut macros = horizon_shaders::Macros::standard();
|
||||
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
|
||||
let post = horizon_shaders::load_post(&pack, "", &properties, ¯os, &Default::default());
|
||||
assert_eq!(post.programs.len(), 3, "{:?}", post.notes);
|
||||
post
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_channel_buffers_read_alpha_1() {
|
||||
let Some(gpu) = Fixture::new() else { return };
|
||||
let mut post = gpu.build(pack());
|
||||
// Twice: the second frame reads buffers that a first frame already used.
|
||||
for frame in 0..2 {
|
||||
let pixels = gpu.render(&mut post, [8, 8]);
|
||||
// (alpha, green) per buffer: 0 is the world (black) through the blit; 1, 5 and 7 are
|
||||
// written with alpha 0, 2 is cleared with alpha 0, 3 is never cleared nor written, 4 is
|
||||
// RGB32UI written (5, 6, 7, 0). Only 5 (RGBA16F) keeps the alpha 0 it was given.
|
||||
let expected = [
|
||||
[255, 0],
|
||||
[255, 128],
|
||||
[255, 128],
|
||||
[255, 0],
|
||||
[255, 128],
|
||||
[0, 128],
|
||||
[0, 0],
|
||||
[255, 128],
|
||||
];
|
||||
let row: Vec<[u8; 2]> = pixels[..8].iter().map(|p| [p[0], p[1]]).collect();
|
||||
for (b, (got, want)) in row.iter().zip(expected).enumerate() {
|
||||
if b == 6 {
|
||||
continue;
|
||||
}
|
||||
assert!(
|
||||
got.iter().zip(want).all(|(g, w)| g.abs_diff(w) <= 1),
|
||||
"frame {frame}, colortex{b}: (alpha, green) {got:?}, expected {want:?}; row {row:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//! SNORM colortex buffers clamp to [-1, 1] and quantise, as in Iris 1.11.7, where the adapter
|
||||
//! renders to SNORM formats. Expected values are those probe pack p8106 read back in Iris (the
|
||||
//! PR has them); the programs here are the probe's, cut down. Values that land halfway between
|
||||
//! two steps (-0.5 × 127) are left out: GPUs may round them either way.
|
||||
use super::destination_tests::Fixture;
|
||||
use super::*;
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
const VERTEX: &str = "#version 120\nvarying vec2 uv;\nvoid main() { uv=gl_MultiTexCoord0.xy; gl_Position=ftransform(); }\n";
|
||||
|
||||
fn pack() -> Post {
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let mut add = |name: &str, source: &str| {
|
||||
zip.start_file(
|
||||
format!("shaders/{name}"),
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
};
|
||||
add("composite.vsh", VERTEX);
|
||||
add(
|
||||
"composite.fsh",
|
||||
"#version 120\n/* RENDERTARGETS: 1,2,3 */\n\
|
||||
/*\nconst int colortex1Format = RGBA8_SNORM;\nconst int colortex2Format = RGBA16_SNORM;\n\
|
||||
const int colortex3Format = R16_SNORM;\nconst int colortex4Format = RGBA8_SNORM;\n*/\n\
|
||||
const vec4 colortex4ClearColor = vec4(-1.5, 0.1, 0.3337, 0.0);\n\
|
||||
void main() {\n gl_FragData[0] = vec4(-1.5, 0.1, 0.3337, 1000.0);\n\
|
||||
gl_FragData[1] = vec4(-1.5, 0.1, 0.3337, 1000.0);\n gl_FragData[2] = vec4(0.1);\n}\n",
|
||||
);
|
||||
add("final.vsh", VERTEX);
|
||||
// Pixel column x shows component x % 4 of buffer 1 + x / 4, as the bytes of its bits.
|
||||
add(
|
||||
"final.fsh",
|
||||
"#version 330 compatibility\nuniform sampler2D colortex1;\nuniform sampler2D colortex2;\n\
|
||||
uniform sampler2D colortex3;\nuniform sampler2D colortex4;\n\
|
||||
void main() {\n ivec2 p = ivec2(gl_FragCoord.xy);\n vec4 c = vec4(0.0);\n\
|
||||
switch (p.x / 4) {\n\
|
||||
case 0: c = texelFetch(colortex1, p, 0); break;\n\
|
||||
case 1: c = texelFetch(colortex2, p, 0); break;\n\
|
||||
case 2: c = texelFetch(colortex3, p, 0); break;\n\
|
||||
case 3: c = texelFetch(colortex4, p, 0); break;\n\
|
||||
}\n uint u = floatBitsToUint(c[p.x % 4]);\n\
|
||||
gl_FragColor = vec4(uvec4(u >> 24u, u >> 16u, u >> 8u, u) & 255u) / 255.0;\n}\n",
|
||||
);
|
||||
let pack =
|
||||
horizon_shaders::Pack::from_zip_bytes("original-snorm", zip.finish().unwrap().into_inner())
|
||||
.unwrap();
|
||||
let mut macros = horizon_shaders::Macros::standard();
|
||||
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
|
||||
let post = horizon_shaders::load_post(&pack, "", &properties, ¯os, &Default::default());
|
||||
assert_eq!(post.programs.len(), 2, "{:?}", post.notes);
|
||||
post
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snorm_buffers_clamp_and_quantise() {
|
||||
let Some(gpu) = Fixture::new() else { return };
|
||||
// The fixture's device asks for no features; SNORM targets need these.
|
||||
let wanted = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
|
||||
| wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
|
||||
let renders = [
|
||||
wgpu::TextureFormat::Rgba8Snorm,
|
||||
wgpu::TextureFormat::Rgba16Snorm,
|
||||
]
|
||||
.iter()
|
||||
.all(|&f| {
|
||||
(gpu.adapter.get_texture_format_features(f).allowed_usages)
|
||||
.contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
|
||||
});
|
||||
if !gpu.adapter.features().contains(wanted) || !renders {
|
||||
eprintln!("SNORM test skipped: this adapter doesn't render to SNORM formats");
|
||||
return;
|
||||
}
|
||||
let (device, queue) = pollster::block_on(gpu.adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
required_features: wanted,
|
||||
required_limits: gpu.adapter.limits(),
|
||||
..Default::default()
|
||||
}))
|
||||
.expect("GPU device with SNORM formats");
|
||||
let gpu = Fixture {
|
||||
adapter: gpu.adapter,
|
||||
device,
|
||||
queue,
|
||||
};
|
||||
let mut post = gpu.build(pack());
|
||||
let pixels = gpu.render(&mut post, [16, 1]);
|
||||
let read: Vec<f32> = pixels
|
||||
.iter()
|
||||
.map(|p| f32::from_bits(u32::from_be_bytes(*p)))
|
||||
.collect();
|
||||
// Iris 1.11.7: RGBA8_SNORM written (-1.5, 0.1, 0.3337, 1000); RGBA16_SNORM the same;
|
||||
// R16_SNORM written 0.1 (G and B read 0, alpha 1); RGBA8_SNORM cleared to
|
||||
// (-1.5, 0.1, 0.3337, 0).
|
||||
let expected = [
|
||||
-1.0,
|
||||
0.102_362_2,
|
||||
0.330_708_7,
|
||||
1.0, //
|
||||
-1.0,
|
||||
0.100_009_2,
|
||||
0.333_689_4,
|
||||
1.0, //
|
||||
0.100_009_2,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0, //
|
||||
-1.0,
|
||||
0.102_362_2,
|
||||
0.330_708_7,
|
||||
0.0,
|
||||
];
|
||||
for (n, (got, want)) in read.iter().zip(expected).enumerate() {
|
||||
assert!(
|
||||
(got - want).abs() <= 1e-6,
|
||||
"buffer {} component {}: {got}, expected {want}; all {read:?}",
|
||||
1 + n / 4,
|
||||
n % 4
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
//! A shader pack's shadow buffers (spec 6.3): the shadow map's depth (`shadowtex0`, and
|
||||
//! `shadowtex1` without translucent geometry), and the colour buffers `shadowcolor0/1`, each
|
||||
//! `shadowtex1` without translucent geometry), and the colour buffers `shadowcolor0` to `7`, each
|
||||
//! with two sides that `shadowcomp` programs flip as composite programs flip colortex (6.1.6).
|
||||
//! Square, `shadowMapResolution` pixels.
|
||||
|
||||
use horizon_shaders::SHADOW_COLORS;
|
||||
|
||||
type Ops<T> = wgpu::Operations<T>;
|
||||
type Color = wgpu::Color;
|
||||
|
||||
@@ -34,9 +36,9 @@ pub(super) struct ShadowTargets {
|
||||
/// `shadowtex1`: a copy of `shadowtex0` taken before translucent terrain, when read.
|
||||
pub depth1: Option<(wgpu::Texture, wgpu::TextureView)>,
|
||||
/// Both sides of each shadowcolor buffer used.
|
||||
pub color: [Option<[wgpu::TextureView; 2]>; 2],
|
||||
pub color: [Option<[wgpu::TextureView; 2]>; SHADOW_COLORS],
|
||||
/// The clear colour of each buffer cleared every frame.
|
||||
pub clears: [Option<[f32; 4]>; 2],
|
||||
pub clears: [Option<[f32; 4]>; SHADOW_COLORS],
|
||||
/// Bit n set: B is shadowcolor n's "main".
|
||||
pub main: u16,
|
||||
/// Nothing cleared yet this frame: the first pass into the shadow buffers clears them as it
|
||||
@@ -79,7 +81,7 @@ impl ShadowTargets {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
shadow: &horizon_shaders::Shadow,
|
||||
formats: [wgpu::TextureFormat; 2],
|
||||
formats: [wgpu::TextureFormat; SHADOW_COLORS],
|
||||
colors: u16,
|
||||
depth1: bool,
|
||||
) -> Self {
|
||||
@@ -143,12 +145,12 @@ impl ShadowTargets {
|
||||
pub fn clear_ops(
|
||||
&mut self,
|
||||
writes: impl Iterator<Item = usize>,
|
||||
) -> (Ops<f32>, [Ops<Color>; 2]) {
|
||||
) -> (Ops<f32>, [Ops<Color>; SHADOW_COLORS]) {
|
||||
if !std::mem::take(&mut self.fresh) {
|
||||
return (load(), [load(); 2]);
|
||||
return (load(), [load(); SHADOW_COLORS]);
|
||||
}
|
||||
let mut colors = [load(); 2];
|
||||
for n in writes.filter(|&n| n < 2) {
|
||||
let mut colors = [load(); SHADOW_COLORS];
|
||||
for n in writes.filter(|&n| n < SHADOW_COLORS) {
|
||||
if let Some(c) = self.clears[n] {
|
||||
colors[n] = clear(c);
|
||||
self.cleared |= 1 << n;
|
||||
@@ -162,11 +164,12 @@ impl ShadowTargets {
|
||||
}
|
||||
|
||||
/// Clears what no pass has cleared this frame: all of it when no shadow program drew, the
|
||||
/// cleared shadowcolor buffers the first one did not write.
|
||||
/// cleared shadowcolor buffers the first one did not write. One buffer a pass: eight wide
|
||||
/// formats together would pass the device's bytes-per-sample limit.
|
||||
pub fn clear_rest(&mut self, encoder: &mut wgpu::CommandEncoder) {
|
||||
let depth = std::mem::take(&mut self.fresh);
|
||||
let cleared = std::mem::replace(&mut self.cleared, 0b11);
|
||||
let attachments: Vec<_> = (0..2)
|
||||
let mut depth = std::mem::take(&mut self.fresh);
|
||||
let cleared = std::mem::replace(&mut self.cleared, u8::MAX);
|
||||
let attachments = (0..SHADOW_COLORS)
|
||||
.filter(|n| cleared & 1 << n == 0)
|
||||
.filter_map(|n| {
|
||||
Some(Some(wgpu::RenderPassColorAttachment {
|
||||
@@ -175,24 +178,30 @@ impl ShadowTargets {
|
||||
resolve_target: None,
|
||||
ops: clear(self.clears[n]?),
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
if !depth && attachments.is_empty() {
|
||||
return;
|
||||
});
|
||||
let passes: Vec<Vec<_>> = attachments.map(|a| vec![a]).collect();
|
||||
let passes = if passes.is_empty() && depth {
|
||||
vec![Vec::new()]
|
||||
} else {
|
||||
passes
|
||||
};
|
||||
for attachments in &passes {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack shadow clear"),
|
||||
color_attachments: attachments,
|
||||
depth_stencil_attachment: std::mem::take(&mut depth).then_some(
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth.1,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
},
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack shadow clear"),
|
||||
color_attachments: &attachments,
|
||||
depth_stencil_attachment: depth.then_some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth.1,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
/// `shadowtex1`: the shadow map as it is now (before translucent geometry).
|
||||
|
||||
@@ -265,6 +265,13 @@ pub const NAMES: &[&str] = &[
|
||||
"playerMood",
|
||||
"constantMood",
|
||||
"centerDepthSmooth",
|
||||
"alphaTestRef",
|
||||
"entityId",
|
||||
"blockEntityId",
|
||||
"entityColor",
|
||||
GBUFFERS_FOG_MODE,
|
||||
GBUFFERS_FOG_SHAPE,
|
||||
GBUFFERS_CENTER_DEPTH,
|
||||
"isRightHanded",
|
||||
"is_on_ground",
|
||||
"bedrockLevel",
|
||||
@@ -277,6 +284,13 @@ pub const NAMES: &[&str] = &[
|
||||
"cloudHeight",
|
||||
];
|
||||
|
||||
/// What programs that draw the game's geometry (gbuffers, shadow) read as `fogMode`,
|
||||
/// `fogShape` and `centerDepthSmooth`: no GLSL name can hold a dot, so no pack reads these
|
||||
/// directly.
|
||||
const GBUFFERS_FOG_MODE: &str = "gbuffers.fogMode";
|
||||
const GBUFFERS_FOG_SHAPE: &str = "gbuffers.fogShape";
|
||||
const GBUFFERS_CENTER_DEPTH: &str = "gbuffers.centerDepthSmooth";
|
||||
|
||||
/// Values carried from frame to frame.
|
||||
pub struct FrameUniforms {
|
||||
pub values: Vec<Value>,
|
||||
@@ -472,18 +486,16 @@ impl FrameUniforms {
|
||||
});
|
||||
}
|
||||
let center_depth = self.center_depth.unwrap_or(1.0);
|
||||
// Vanilla fades by the stronger of two linear fogs; a pack gets one: the pair (start
|
||||
// and end together) of the one that is complete nearer (OQ-36). Mixing the nearer start
|
||||
// of one with the nearer end of the other made fog begin at the camera on clear days.
|
||||
// Vanilla fades by the stronger of two linear fogs; a pack gets the environmental one
|
||||
// (`FogData::environmental_start`/`environmental_end`), as Iris 1.11.7 gives it in every
|
||||
// program kind: 0 to 1024 on a clear day, -160 to 768 in rain, -8 to 96 under water, 10
|
||||
// to 96 in the Nether (measured with a probe pack).
|
||||
let (fog_color, fog_start, fog_end) = match &frame.fog {
|
||||
Some(f) => {
|
||||
let (start, end) = if f.environmental_end < f.render_distance_end {
|
||||
(f.environmental_start, f.environmental_end)
|
||||
} else {
|
||||
(f.render_distance_start, f.render_distance_end)
|
||||
};
|
||||
([f.color[0], f.color[1], f.color[2]], start, end)
|
||||
}
|
||||
Some(f) => (
|
||||
[f.color[0], f.color[1], f.color[2]],
|
||||
f.environmental_start,
|
||||
f.environmental_end,
|
||||
),
|
||||
None => (frame.sky.fog_color, frame.distance * 0.9, frame.distance),
|
||||
};
|
||||
let light = shadow_light(frame, &self.shadow);
|
||||
@@ -545,12 +557,22 @@ impl FrameUniforms {
|
||||
"thunderStrength" => Value::f(&[inputs.thunder]),
|
||||
"fogColor" => Value::f(&fog_color),
|
||||
"skyColor" => Value::f(&frame.sky.sky_color),
|
||||
// GL_LINEAR: vanilla's fog is linear.
|
||||
"fogMode" => Value::i(&[9729]),
|
||||
// Iris 1.11.7, measured: programs that draw no geometry read no fog mode (0) and
|
||||
// shape (-1); those that do read GL_LINEAR and a cylinder (1). The density is 0.
|
||||
"fogMode" => Value::i(&[0]),
|
||||
GBUFFERS_FOG_MODE => Value::i(&[9729]),
|
||||
"fogStart" => Value::f(&[fog_start]),
|
||||
"fogEnd" => Value::f(&[fog_end]),
|
||||
"fogDensity" => Value::f(&[1.0]),
|
||||
"fogShape" => Value::i(&[0]),
|
||||
"fogDensity" => Value::f(&[0.0]),
|
||||
"fogShape" => Value::i(&[-1]),
|
||||
GBUFFERS_FOG_SHAPE => Value::i(&[1]),
|
||||
// Iris 1.11.7 gives gbuffers programs 0 (measured in 16 scenes).
|
||||
GBUFFERS_CENTER_DEPTH => Value::f(&[0.0]),
|
||||
// Outside a draw (gbuffers programs get these per draw), as Iris 1.11.7 gives
|
||||
// them to composite and final programs (measured).
|
||||
"alphaTestRef" => Value::f(&[0.1]),
|
||||
"blockEntityId" => Value::i(&[-1]),
|
||||
"entityColor" => Value::f(&[0.0; 4]),
|
||||
"isEyeInWater" => Value::i(&[inputs.eye_in]),
|
||||
"blindness" => Value::f(&[inputs.blindness]),
|
||||
"nightVision" => Value::f(&[frame.night_vision]),
|
||||
@@ -690,6 +712,7 @@ fn components(name: &str) -> usize {
|
||||
| "skyColor"
|
||||
| "endFlashPosition" => 3,
|
||||
"eyeBrightness" | "eyeBrightnessSmooth" | "atlasSize" => 2,
|
||||
"entityColor" => 4,
|
||||
n if n.starts_with("gbuffer") || n.starts_with("shadow") && n != "shadowAngle" => 16,
|
||||
_ => 1,
|
||||
}
|
||||
@@ -700,6 +723,18 @@ pub fn index(name: &str) -> Option<usize> {
|
||||
NAMES.iter().position(|n| *n == name)
|
||||
}
|
||||
|
||||
/// The position in [`NAMES`] of the value a program reads as `name`; `geometry`: the program
|
||||
/// draws the game's geometry (gbuffers, shadow), which reads its own fog mode and shape and no
|
||||
/// centre depth.
|
||||
pub fn index_in(name: &str, geometry: bool) -> Option<usize> {
|
||||
match name {
|
||||
"fogMode" if geometry => index(GBUFFERS_FOG_MODE),
|
||||
"fogShape" if geometry => index(GBUFFERS_FOG_SHAPE),
|
||||
"centerDepthSmooth" if geometry => index(GBUFFERS_CENTER_DEPTH),
|
||||
_ => index(name),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1178,6 +1213,64 @@ mod initial_history_tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn int(u: &FrameUniforms, at: usize) -> i32 {
|
||||
match u.values[at] {
|
||||
Value::I(v) => v[0],
|
||||
_ => panic!("integer uniform expected"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iris 1.11.7, read back exactly with a probe pack on a clear day (render distance 12,
|
||||
/// cloud range 64): fogStart 0 and fogEnd 1024 (the environmental fog, not the render
|
||||
/// distance's 172.8 to 192), fogDensity 0; composite and final programs read alphaTestRef
|
||||
/// 0.1, blockEntityId -1, fogMode 0 and fogShape -1.
|
||||
#[test]
|
||||
fn fog_and_draw_uniforms_outside_a_draw_follow_iris() {
|
||||
let hud = HudBatch::new([800, 450]);
|
||||
let mut f = frame(&hud);
|
||||
f.fog = Some(crate::fog::FogData {
|
||||
color: [0.7, 0.8, 1.0, 1.0],
|
||||
environmental_start: 0.0,
|
||||
environmental_end: 1024.0,
|
||||
render_distance_start: 172.8,
|
||||
render_distance_end: 192.0,
|
||||
sky_end: 192.0,
|
||||
cloud_end: 1024.0,
|
||||
});
|
||||
let mut u = FrameUniforms::default();
|
||||
u.update(&f, &PackInputs::default(), [800, 450]);
|
||||
assert_eq!(float(&u, "fogStart")[0], 0.0);
|
||||
assert_eq!(float(&u, "fogEnd")[0], 1024.0);
|
||||
assert_eq!(float(&u, "fogDensity")[0], 0.0);
|
||||
assert_eq!(float(&u, "alphaTestRef")[0], 0.1);
|
||||
assert_eq!(int(&u, index("blockEntityId").unwrap()), -1);
|
||||
assert_eq!(int(&u, index("entityId").unwrap()), 0);
|
||||
assert_eq!(int(&u, index("fogMode").unwrap()), 0);
|
||||
assert_eq!(int(&u, index("fogShape").unwrap()), -1);
|
||||
}
|
||||
|
||||
/// Iris 1.11.7 (probe pack): gbuffers programs read fogMode GL_LINEAR (9729) and fogShape 1
|
||||
/// (cylinder) where composite and final programs read 0 and -1.
|
||||
#[test]
|
||||
fn programs_that_draw_geometry_read_their_own_fog_and_centre_depth() {
|
||||
let hud = HudBatch::new([800, 450]);
|
||||
let mut u = FrameUniforms::default();
|
||||
u.update(&frame(&hud), &PackInputs::default(), [800, 450]);
|
||||
let at = |name, geometry| index_in(name, geometry).unwrap();
|
||||
assert_eq!(int(&u, at("fogMode", true)), 9729);
|
||||
assert_eq!(int(&u, at("fogShape", true)), 1);
|
||||
assert_eq!(int(&u, at("fogMode", false)), 0);
|
||||
assert_eq!(int(&u, at("fogShape", false)), -1);
|
||||
assert_eq!(at("fogStart", true), at("fogStart", false));
|
||||
// Iris 1.11.7 (probe pack): `centerDepthSmooth` reads 0 in gbuffers programs.
|
||||
let depth = |geometry| match u.values[at("centerDepthSmooth", geometry)] {
|
||||
Value::F(v) => v[0],
|
||||
_ => panic!("float uniform expected"),
|
||||
};
|
||||
assert_eq!(depth(true), 0.0);
|
||||
assert_eq!(depth(false), 1.0);
|
||||
}
|
||||
|
||||
/// Iris 1.11.7, read frame by frame with a probe pack: eyeBrightnessSmooth falls by half
|
||||
/// in 1 s and is rounded down; wetness takes 60 s to rise halfway and 20 s to fall; the
|
||||
/// centre depth 0.1 s.
|
||||
|
||||
@@ -162,7 +162,7 @@ impl PackWeather {
|
||||
|
||||
/// A texture holding the atlas's texels under `rect` (normalised x, y, width, height), as
|
||||
/// Horizon's weather shader reads them (`weather_texel`).
|
||||
fn copy_sprite(
|
||||
pub(super) fn copy_sprite(
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
atlas: &wgpu::Texture,
|
||||
|
||||
@@ -17,6 +17,9 @@ struct Column {
|
||||
z: i32,
|
||||
/// Section y (from the world's bottom) and slot, in no particular order.
|
||||
sections: Vec<(i32, u32)>,
|
||||
/// Those of `sections` that are marked (`Resident::insert_marked`), in no
|
||||
/// particular order.
|
||||
marked: Vec<(i32, u32)>,
|
||||
/// The lowest and highest section y of `sections`.
|
||||
low: i32,
|
||||
high: i32,
|
||||
@@ -36,6 +39,9 @@ pub struct Resident<T> {
|
||||
min_y: i32,
|
||||
items: Vec<(SectionKey, T)>,
|
||||
bounds: Vec<Option<[Vec3; 2]>>,
|
||||
/// Per slot, whether the section is marked: within the render distance,
|
||||
/// `walk` lists only those when `marked_only_near`.
|
||||
marked: Vec<bool>,
|
||||
slots: FxHashMap<SectionKey, u32>,
|
||||
columns: Vec<Column>,
|
||||
column_index: FxHashMap<(i32, i32), u32>,
|
||||
@@ -47,6 +53,7 @@ impl<T> Default for Resident<T> {
|
||||
min_y: crate::chunk::MIN_Y,
|
||||
items: Vec::new(),
|
||||
bounds: Vec::new(),
|
||||
marked: Vec::new(),
|
||||
slots: FxHashMap::default(),
|
||||
columns: Vec::new(),
|
||||
column_index: FxHashMap::default(),
|
||||
@@ -65,12 +72,13 @@ impl<T> Resident<T> {
|
||||
use std::mem::size_of;
|
||||
self.items.capacity() * size_of::<(SectionKey, T)>()
|
||||
+ self.bounds.capacity() * size_of::<Option<[Vec3; 2]>>()
|
||||
+ self.marked.capacity() * size_of::<bool>()
|
||||
+ self.slots.capacity() * (size_of::<(SectionKey, u32)>() + 1)
|
||||
+ self.columns.capacity() * size_of::<Column>()
|
||||
+ self
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| c.sections.capacity() * size_of::<(i32, u32)>())
|
||||
.map(|c| (c.sections.capacity() + c.marked.capacity()) * size_of::<(i32, u32)>())
|
||||
.sum::<usize>()
|
||||
+ self.column_index.capacity() * (size_of::<((i32, i32), u32)>() + 1)
|
||||
}
|
||||
@@ -83,14 +91,23 @@ impl<T> Resident<T> {
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
self.items.iter_mut().map(|(_, v)| v)
|
||||
}
|
||||
/// Every section's key and value.
|
||||
/// Every section's key, value and bounds.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (SectionKey, &T)> {
|
||||
self.items.iter().map(|(key, value)| (*key, value))
|
||||
pub fn iter(&self) -> impl Iterator<Item = (SectionKey, &T, Option<[Vec3; 2]>)> {
|
||||
let items = self.items.iter().zip(&self.bounds);
|
||||
items.map(|((key, value), bounds)| (*key, value, *bounds))
|
||||
}
|
||||
/// Every section's key and value.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (SectionKey, &mut T)> {
|
||||
self.items.iter_mut().map(|(key, value)| (*key, value))
|
||||
/// Every section's key, value and bounds.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (SectionKey, &mut T, Option<[Vec3; 2]>)> {
|
||||
let items = self.items.iter_mut().zip(&self.bounds);
|
||||
items.map(|((key, value), bounds)| (*key, value, *bounds))
|
||||
}
|
||||
/// Whether the section at `key` is marked (`insert_marked`).
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
pub fn is_marked(&self, key: SectionKey) -> bool {
|
||||
self.slots
|
||||
.get(&key)
|
||||
.is_some_and(|&slot| self.marked[slot as usize])
|
||||
}
|
||||
/// `key`'s value, when it is resident.
|
||||
pub fn find(&self, key: SectionKey) -> Option<&T> {
|
||||
@@ -118,6 +135,7 @@ impl<T> Resident<T> {
|
||||
pub fn clear(&mut self) {
|
||||
self.items.clear();
|
||||
self.bounds.clear();
|
||||
self.marked.clear();
|
||||
self.slots.clear();
|
||||
self.columns.clear();
|
||||
self.column_index.clear();
|
||||
@@ -127,16 +145,31 @@ impl<T> Resident<T> {
|
||||
pub fn insert(&mut self, key: SectionKey, value: T) {
|
||||
self.insert_with_bounds(key, value, None);
|
||||
}
|
||||
/// Adds or replaces `key`'s value with optional tight geometry bounds.
|
||||
/// Adds or replaces `key`'s value with optional tight geometry bounds, unmarked.
|
||||
#[cfg(test)]
|
||||
pub fn insert_with_bounds(&mut self, key: SectionKey, value: T, bounds: Option<[Vec3; 2]>) {
|
||||
self.insert_marked(key, value, bounds, false);
|
||||
}
|
||||
/// Adds or replaces `key`'s value with optional tight geometry bounds, and
|
||||
/// whether it is marked: `walk` with `marked_only_near` lists the sections
|
||||
/// within the render distance that are.
|
||||
pub fn insert_marked(
|
||||
&mut self,
|
||||
key: SectionKey,
|
||||
value: T,
|
||||
bounds: Option<[Vec3; 2]>,
|
||||
marked: bool,
|
||||
) {
|
||||
if let Some(&slot) = self.slots.get(&key) {
|
||||
self.items[slot as usize].1 = value;
|
||||
self.bounds[slot as usize] = bounds;
|
||||
self.mark_slot(slot, marked);
|
||||
return;
|
||||
}
|
||||
let slot = self.items.len() as u32;
|
||||
self.items.push((key, value));
|
||||
self.bounds.push(bounds);
|
||||
self.marked.push(marked);
|
||||
self.slots.insert(key, slot);
|
||||
let column = match self.column_index.get(&(key.0, key.2)) {
|
||||
Some(&c) => &mut self.columns[c as usize],
|
||||
@@ -147,6 +180,7 @@ impl<T> Resident<T> {
|
||||
x: key.0,
|
||||
z: key.2,
|
||||
sections: Vec::new(),
|
||||
marked: Vec::new(),
|
||||
low: key.1,
|
||||
high: key.1,
|
||||
});
|
||||
@@ -154,15 +188,39 @@ impl<T> Resident<T> {
|
||||
}
|
||||
};
|
||||
column.sections.push((key.1, slot));
|
||||
if marked {
|
||||
column.marked.push((key.1, slot));
|
||||
}
|
||||
column.low = column.low.min(key.1);
|
||||
column.high = column.high.max(key.1);
|
||||
}
|
||||
/// Removes every section of the `gone` columns, the others keeping their order: one
|
||||
/// pass over the store, where `remove` moves a slot and looks its column up for each.
|
||||
/// Marks or unmarks the section in `slot`, in its column's list too.
|
||||
fn mark_slot(&mut self, slot: u32, marked: bool) {
|
||||
let was = std::mem::replace(&mut self.marked[slot as usize], marked);
|
||||
if was == marked {
|
||||
return;
|
||||
}
|
||||
let (x, y, z) = self.items[slot as usize].0;
|
||||
let column = &mut self.columns[self.column_index[&(x, z)] as usize];
|
||||
if marked {
|
||||
column.marked.push((y, slot));
|
||||
} else {
|
||||
let at = column
|
||||
.marked
|
||||
.iter()
|
||||
.position(|s| s.1 == slot)
|
||||
.expect("marked section in its column's list");
|
||||
column.marked.swap_remove(at);
|
||||
}
|
||||
}
|
||||
/// Removes every section of the `gone` columns, the others keeping their order and
|
||||
/// marks: one pass over the store, where `remove` moves a slot and looks its column
|
||||
/// up for each.
|
||||
pub fn remove_columns(&mut self, gone: &FxHashSet<(i32, i32)>) -> Vec<T> {
|
||||
// Every column (a far teleport): emptied, without a lookup per section.
|
||||
if self.columns.iter().all(|c| gone.contains(&(c.x, c.z))) {
|
||||
self.bounds.clear();
|
||||
self.marked.clear();
|
||||
self.slots.clear();
|
||||
self.columns.clear();
|
||||
self.column_index.clear();
|
||||
@@ -173,21 +231,23 @@ impl<T> Resident<T> {
|
||||
}
|
||||
let items = std::mem::take(&mut self.items);
|
||||
let bounds = std::mem::take(&mut self.bounds);
|
||||
let marked = std::mem::take(&mut self.marked);
|
||||
self.slots.clear();
|
||||
self.columns.clear();
|
||||
self.column_index.clear();
|
||||
let mut removed = Vec::new();
|
||||
for ((key, value), bounds) in items.into_iter().zip(bounds) {
|
||||
for (((key, value), bounds), marked) in items.into_iter().zip(bounds).zip(marked) {
|
||||
if gone.contains(&(key.0, key.2)) {
|
||||
removed.push(value);
|
||||
} else {
|
||||
self.insert_with_bounds(key, value, bounds);
|
||||
self.insert_marked(key, value, bounds, marked);
|
||||
}
|
||||
}
|
||||
removed
|
||||
}
|
||||
pub fn remove(&mut self, key: SectionKey) -> Option<T> {
|
||||
let slot = self.slots.remove(&key)?;
|
||||
self.mark_slot(slot, false);
|
||||
let c = self.column_index[&(key.0, key.2)] as usize;
|
||||
let column = &mut self.columns[c];
|
||||
let at = column
|
||||
@@ -206,8 +266,10 @@ impl<T> Resident<T> {
|
||||
column.bounds();
|
||||
}
|
||||
let _ = self.bounds.swap_remove(slot as usize);
|
||||
let _ = self.marked.swap_remove(slot as usize);
|
||||
let (_, value) = self.items.swap_remove(slot as usize);
|
||||
// The last item now sits in `slot`: point its key and column at it.
|
||||
// The last item now sits in `slot`: point its key and column, and its
|
||||
// column's marked list if it is marked, at it.
|
||||
if let Some(&(moved, _)) = self.items.get(slot as usize) {
|
||||
let last = self.items.len() as u32;
|
||||
self.slots.insert(moved, slot);
|
||||
@@ -218,6 +280,14 @@ impl<T> Resident<T> {
|
||||
.find(|s| s.1 == last)
|
||||
.expect("moved section in its column");
|
||||
entry.1 = slot;
|
||||
if self.marked[slot as usize] {
|
||||
let entry = column
|
||||
.marked
|
||||
.iter_mut()
|
||||
.find(|s| s.1 == last)
|
||||
.expect("moved marked section in its column's list");
|
||||
entry.1 = slot;
|
||||
}
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
@@ -328,6 +398,22 @@ impl<T> Resident<T> {
|
||||
reachable: impl Fn(SectionKey) -> bool,
|
||||
beyond: impl Fn(i32, i32) -> bool,
|
||||
out: &mut Vec<Visible>,
|
||||
) {
|
||||
self.walk(camera, radius, frustum, reachable, beyond, false, out);
|
||||
}
|
||||
/// `visible`, less the sections within the render distance that are not
|
||||
/// marked (`insert_marked`) when `marked_only_near`, in the same order: GPU
|
||||
/// culling's frames, where the GPU draws those. A column's box spans all of
|
||||
/// its sections either way, so both decide alike on those they share.
|
||||
pub fn walk(
|
||||
&self,
|
||||
camera: DVec3,
|
||||
radius: i32,
|
||||
frustum: &impl Cull,
|
||||
reachable: impl Fn(SectionKey) -> bool,
|
||||
beyond: impl Fn(i32, i32) -> bool,
|
||||
marked_only_near: bool,
|
||||
out: &mut Vec<Visible>,
|
||||
) {
|
||||
let from = camera_column(camera);
|
||||
for column in &self.columns {
|
||||
@@ -335,6 +421,14 @@ impl<T> Resident<T> {
|
||||
if !near && !beyond(column.x, column.z) {
|
||||
continue;
|
||||
}
|
||||
let list = if marked_only_near && near {
|
||||
&column.marked
|
||||
} else {
|
||||
&column.sections
|
||||
};
|
||||
if list.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let min = origin((column.x, column.low, column.z), self.min_y, camera).as_vec3();
|
||||
let top = origin((column.x, column.high, column.z), self.min_y, camera).as_vec3();
|
||||
let max = top + Vec3::splat(16.0);
|
||||
@@ -351,7 +445,7 @@ impl<T> Resident<T> {
|
||||
let xz_sq = dx8 * dx8 + dz8 * dz8;
|
||||
|
||||
if inside {
|
||||
for &(y, slot) in &column.sections {
|
||||
for &(y, slot) in list {
|
||||
let key = (column.x, y, column.z);
|
||||
if near && !reachable(key) {
|
||||
continue;
|
||||
@@ -368,7 +462,7 @@ impl<T> Resident<T> {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for &(y, slot) in &column.sections {
|
||||
for &(y, slot) in list {
|
||||
let key = (column.x, y, column.z);
|
||||
if near && !reachable(key) {
|
||||
continue;
|
||||
@@ -519,9 +613,10 @@ mod tests {
|
||||
assert_eq!(fast, slow);
|
||||
}
|
||||
|
||||
/// Every key reaches its value through its slot and column, and every
|
||||
/// column's bounds and sections match the keys.
|
||||
fn check(r: &Resident<u32>, model: &HashMap<SectionKey, u32>) {
|
||||
/// Every key reaches its value and mark through its slot and column, every
|
||||
/// column's bounds and sections match the keys, and its marked list holds
|
||||
/// its marked sections.
|
||||
fn check(r: &Resident<u32>, model: &HashMap<SectionKey, (u32, bool)>) {
|
||||
assert_eq!(r.len(), model.len());
|
||||
let mut seen = 0;
|
||||
for column in &r.columns {
|
||||
@@ -532,13 +627,24 @@ mod tests {
|
||||
for &(y, slot) in &column.sections {
|
||||
let (key, value) = &r.items[slot as usize];
|
||||
assert_eq!(*key, (column.x, y, column.z));
|
||||
assert_eq!(model[key], *value);
|
||||
assert_eq!(model[key], (*value, r.marked[slot as usize]));
|
||||
seen += 1;
|
||||
}
|
||||
let mut marked: Vec<(i32, u32)> = column
|
||||
.sections
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&(_, slot)| r.marked[slot as usize])
|
||||
.collect();
|
||||
let mut listed = column.marked.clone();
|
||||
marked.sort_unstable();
|
||||
listed.sort_unstable();
|
||||
assert_eq!(listed, marked, "column ({}, {})", column.x, column.z);
|
||||
}
|
||||
assert_eq!(seen, model.len());
|
||||
for key in model.keys() {
|
||||
for (key, &(_, marked)) in model {
|
||||
assert!(r.contains(*key));
|
||||
assert_eq!(r.is_marked(*key), marked, "{key:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +652,12 @@ mod tests {
|
||||
fn slots_and_columns_follow_inserts_and_removals() {
|
||||
let mut r = Resident::default();
|
||||
let mut model = HashMap::new();
|
||||
// A deterministic mix of inserts, replacements and removals.
|
||||
// Bounds that tell values apart.
|
||||
let bounds = |value: u32| {
|
||||
(!value.is_multiple_of(3)).then(|| [Vec3::splat((value % 5) as f32), Vec3::splat(16.0)])
|
||||
};
|
||||
// A deterministic mix of inserts, replacements and removals, marked at
|
||||
// random: a replacement can mark or unmark its section.
|
||||
let mut seed = 0x2545_f491_u32;
|
||||
for step in 0..4000u32 {
|
||||
seed ^= seed << 13;
|
||||
@@ -563,47 +674,130 @@ mod tests {
|
||||
.flat_map(|x| (-2..=2).map(move |z| (x, z)))
|
||||
.collect();
|
||||
let mut removed = r.remove_columns(&gone);
|
||||
let mut expected: Vec<u32> = model.drain().map(|(_, v)| v).collect();
|
||||
let mut expected: Vec<u32> = model.drain().map(|(_, (value, _))| value).collect();
|
||||
removed.sort_unstable();
|
||||
expected.sort_unstable();
|
||||
assert_eq!(removed, expected);
|
||||
assert!(r.columns.is_empty() && r.column_index.is_empty());
|
||||
} else if (seed >> 12).is_multiple_of(97) {
|
||||
// Whole columns at once: the column of `key` and the next one along x.
|
||||
// The others keep their values, bounds and marks (`check`).
|
||||
let gone: FxHashSet<(i32, i32)> =
|
||||
[(key.0, key.2), (key.0 + 1, key.2)].into_iter().collect();
|
||||
let mut removed = r.remove_columns(&gone);
|
||||
let mut expected: Vec<u32> = model
|
||||
.extract_if(|k: &SectionKey, _| gone.contains(&(k.0, k.2)))
|
||||
.map(|(_, v)| v)
|
||||
.map(|(_, (value, _))| value)
|
||||
.collect();
|
||||
removed.sort_unstable();
|
||||
expected.sort_unstable();
|
||||
assert_eq!(removed, expected);
|
||||
} else if (seed >> 12).is_multiple_of(3) {
|
||||
assert_eq!(r.remove(key), model.remove(&key));
|
||||
assert_eq!(r.remove(key), model.remove(&key).map(|(value, _)| value));
|
||||
} else {
|
||||
r.insert(key, step);
|
||||
model.insert(key, step);
|
||||
let marked = (seed >> 20).is_multiple_of(2);
|
||||
r.insert_marked(key, step, bounds(step), marked);
|
||||
model.insert(key, (step, marked));
|
||||
}
|
||||
check(&r, &model);
|
||||
// `iter_mut` yields every key once, with its value.
|
||||
// `iter_mut` yields every key once, with its value and bounds.
|
||||
let mut seen = HashSet::new();
|
||||
for (key, value) in r.iter_mut() {
|
||||
assert_eq!(*value, model[&key], "{key:?}");
|
||||
for (key, value, b) in r.iter_mut() {
|
||||
assert_eq!(*value, model[&key].0, "{key:?}");
|
||||
assert_eq!(b, bounds(*value), "{key:?}");
|
||||
assert!(seen.insert(key), "{key:?} twice");
|
||||
}
|
||||
assert_eq!(seen.len(), model.len());
|
||||
}
|
||||
for (_, value) in r.iter_mut() {
|
||||
for (_, value, _) in r.iter_mut() {
|
||||
*value += 1;
|
||||
}
|
||||
for (key, value) in &model {
|
||||
for (key, (value, _)) in &model {
|
||||
assert_eq!(r.find(*key), Some(&(value + 1)));
|
||||
}
|
||||
r.clear();
|
||||
assert_eq!(r.len(), 0);
|
||||
assert!(r.columns.is_empty());
|
||||
assert!(r.columns.is_empty() && r.marked.is_empty());
|
||||
}
|
||||
|
||||
/// GPU frames' walk (`walk` with `marked_only_near`) is `visible` less the
|
||||
/// sections within the render distance that are not marked, in the same
|
||||
/// order: with marks at random, bounds reaching out of their cells, short
|
||||
/// columns entirely inside the frustum, cave reach, and columns past the
|
||||
/// distance kept for far terrain (GPU frames have no water mirror,
|
||||
/// `gpu_frame`).
|
||||
#[test]
|
||||
fn the_marked_walk_is_the_walk_less_unmarked_sections_within_the_distance() {
|
||||
let mut r = Resident::default();
|
||||
let mut seed = 0x9e37_79b9_u32;
|
||||
let mut next = || {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 17;
|
||||
seed ^= seed << 5;
|
||||
seed
|
||||
};
|
||||
for x in -14i32..=14 {
|
||||
for z in -14i32..=14 {
|
||||
// Short columns ahead of the first camera: most lie entirely inside.
|
||||
let rows = if z > 0 && x.abs() <= 12 { 3..6 } else { 0..24 };
|
||||
for y in rows {
|
||||
let bits = next();
|
||||
if bits.is_multiple_of(5) {
|
||||
continue;
|
||||
}
|
||||
let bounds = match (bits >> 4) % 4 {
|
||||
0 => None,
|
||||
1 => Some([Vec3::splat(-1.5), Vec3::splat(17.5)]),
|
||||
2 => Some([Vec3::new(0.0, 18.0, 0.0), Vec3::new(16.0, 20.0, 16.0)]),
|
||||
_ => Some([Vec3::new(2.0, 0.0, 3.0), Vec3::new(14.0, 5.5, 16.0)]),
|
||||
};
|
||||
r.insert_marked((x, y, z), (), bounds, (bits >> 8).is_multiple_of(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
let reachable = |k: SectionKey| (3 * k.0 + k.1 + 5 * k.2).rem_euclid(7) != 0;
|
||||
let beyond = |x: i32, z: i32| (x - z).rem_euclid(3) == 0;
|
||||
let projection = super::super::world_projection(70.0, 16.0 / 9.0, 512.0);
|
||||
let (mut kept, mut dropped, mut inside) = (0, 0, 0);
|
||||
for (camera, yaw, pitch) in [
|
||||
(DVec3::new(0.5, -8.0, 0.5), 0.0, 5.0),
|
||||
(DVec3::new(-37.25, 130.5, 21.75), 135.0, 35.0),
|
||||
(DVec3::new(100.0, -60.0, -3.0), 270.0, -80.0),
|
||||
(DVec3::new(8.0, 64.0, 8.0), 45.0, 89.0),
|
||||
] {
|
||||
let view = Frustum {
|
||||
planes: super::super::frustum(projection * crate::camera::view_matrix(yaw, pitch)),
|
||||
};
|
||||
inside += r
|
||||
.columns
|
||||
.iter()
|
||||
.filter(|c| {
|
||||
let min = origin((c.x, c.low, c.z), r.min_y, camera).as_vec3();
|
||||
let top = origin((c.x, c.high, c.z), r.min_y, camera).as_vec3();
|
||||
view.contains(min, top + Vec3::splat(16.0))
|
||||
})
|
||||
.count();
|
||||
for radius in [3, 7, 12] {
|
||||
let (mut all, mut marked) = (Vec::new(), Vec::new());
|
||||
r.visible(camera, radius, &view, reachable, beyond, &mut all);
|
||||
r.walk(camera, radius, &view, reachable, beyond, true, &mut marked);
|
||||
let from = camera_column(camera);
|
||||
let near = |v: &Visible| in_view_distance(from, radius, v.key.0, v.key.2);
|
||||
let expected: Vec<Visible> = all
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|v| r.is_marked(v.key) || !near(v))
|
||||
.collect();
|
||||
assert_eq!(marked, expected, "{camera} {yaw} {pitch} {radius}");
|
||||
kept += marked.len();
|
||||
dropped += all.len() - marked.len();
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
kept > 100 && dropped > 100,
|
||||
"{kept} kept, {dropped} dropped"
|
||||
);
|
||||
assert!(inside > 50, "{inside} columns entirely inside");
|
||||
}
|
||||
|
||||
/// The old walk (every key of a hash map, sorted by distance, then two
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
//! A visibility buffer for solid terrain, opt-in with `HORIZON_VISBUFFER=1` (read once, at
|
||||
//! startup): pass 1 rasterises the solid flat draws (near sections and far tiles, phase 1 and,
|
||||
//! with occlusion culling, phase 2) writing only depth and the ID of the quad each pixel shows
|
||||
//! (`visbuffer.wgsl`'s `vis_id`: arena page and quad); cutout and fading
|
||||
//! quads keep their alpha test and dither there, the rest never discard. Pass 2 is one
|
||||
//! full-screen draw with up to `PAGE_SLOTS` arena pages bound at once (more pages, more draws)
|
||||
//! that reads the quad back from its page and its section's origin (through a copy of every
|
||||
//! drawn page's block table in one buffer), and runs terrain.wgsl's
|
||||
//! vertex work and solid shading once per pixel with analytic screen gradients. Every solid
|
||||
//! pixel is shaded once, however many quads were drawn over it and however small they are.
|
||||
//! A draw per page instead made each GPU thread group run the shading once for every page
|
||||
//! among its pixels, with most lanes idle: the pages' sections lie all over the screen.
|
||||
//!
|
||||
//! It runs only in frames drawn flat (`flat`: Metal past `flat::MIN_SECTIONS`, or
|
||||
//! `HORIZON_FLAT_DRAWS=1`) and falls back to the normal solid passes in frames with a shader
|
||||
//! pack drawing terrain, one of Horizon's own packs with a material stage or a shadow pass, a
|
||||
//! pack's surface target, and on the title screen. The water mirror's own pass stays as it is.
|
||||
use super::arena::Arena;
|
||||
use super::flat::Flat;
|
||||
use super::pack_timing::PassTiming;
|
||||
|
||||
/// Arena pages one resolve draw reads (`visbuffer.wgsl`'s `vis_page_0`...).
|
||||
pub const PAGE_SLOTS: u32 = 16;
|
||||
/// Arena pages an ID names (`visbuffer.wgsl`'s `vis_id`): frames with more draw without it.
|
||||
pub const MAX_PAGES: u32 = 64;
|
||||
/// The resolve binds that many pages, the block tables and the origins by section id.
|
||||
const STORAGE_BUFFERS: u32 = PAGE_SLOTS + 2;
|
||||
const IDS: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint;
|
||||
|
||||
/// Whether the visibility buffer is asked for, and the device binds storage buffers enough.
|
||||
pub fn wanted(device: &wgpu::Device) -> bool {
|
||||
std::env::var("HORIZON_VISBUFFER").as_deref() == Ok("1")
|
||||
&& device.limits().max_storage_buffers_per_shader_stage >= STORAGE_BUFFERS
|
||||
}
|
||||
|
||||
pub struct Visbuffer {
|
||||
/// Pass 1 over the quads `fs_opaque` draws (no discard) and the rest.
|
||||
opaque: wgpu::RenderPipeline,
|
||||
cutout: wgpu::RenderPipeline,
|
||||
resolve: wgpu::RenderPipeline,
|
||||
/// Pass 2's group 1: the block tables (binding 0), the origins by section id (1),
|
||||
/// `PAGE_SLOTS` pages' vertices (from 2), the IDs (after them).
|
||||
page_layout: wgpu::BindGroupLayout,
|
||||
/// Every page's block table, page after page (`Flat::blocks_per_page` entries each),
|
||||
/// copied each frame from the drawn pages' own, and its size in pages.
|
||||
blocks: Option<(wgpu::Buffer, u32)>,
|
||||
blocks_per_page: u64,
|
||||
/// The IDs, the size of the world's depth.
|
||||
ids: Option<(wgpu::TextureView, [u32; 2])>,
|
||||
}
|
||||
|
||||
impl Visbuffer {
|
||||
/// `terrain_layout`: terrain.wgsl's group 0; `flat_layout`: the flat draws' group 1;
|
||||
/// `color`: the world pass's colour format; `constants`: the sprite arrays' pipeline
|
||||
/// constants.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
terrain_layout: &wgpu::BindGroupLayout,
|
||||
flat: &Flat,
|
||||
color: wgpu::TextureFormat,
|
||||
constants: &[(&str, f64)],
|
||||
) -> Self {
|
||||
let flat_layout = &flat.draw_layout;
|
||||
let blocks_per_page = flat.blocks_per_page();
|
||||
let resolve_constants: Vec<(&str, f64)> = constants
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([("VIS_BLOCKS_PER_PAGE", blocks_per_page as f64)])
|
||||
.collect();
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Horizon visibility buffer"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
(include_str!("../terrain.wgsl").to_owned() + include_str!("../visbuffer.wgsl"))
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
let fragment = wgpu::ShaderStages::FRAGMENT;
|
||||
let storage = |binding| wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
visibility: fragment,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
};
|
||||
let entries: Vec<_> = (0..2 + PAGE_SLOTS)
|
||||
.map(storage)
|
||||
.chain([wgpu::BindGroupLayoutEntry {
|
||||
binding: 2 + PAGE_SLOTS,
|
||||
visibility: fragment,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Uint,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
}])
|
||||
.collect();
|
||||
let page_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Visibility buffer pages"),
|
||||
entries: &entries,
|
||||
});
|
||||
let id_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Visibility buffer IDs"),
|
||||
bind_group_layouts: &[Some(terrain_layout), Some(flat_layout)],
|
||||
..Default::default()
|
||||
});
|
||||
let resolve_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Visibility buffer resolve"),
|
||||
bind_group_layouts: &[Some(terrain_layout), Some(&page_layout)],
|
||||
..Default::default()
|
||||
});
|
||||
let ids = |label: &'static str, vertex: &'static str, fragment: &'static str| {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(&id_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &module,
|
||||
entry_point: Some(vertex),
|
||||
compilation_options: Default::default(),
|
||||
// `section_corner` reads the arena page (`flat::ARENA_BINDING`).
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: Some(true),
|
||||
depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &module,
|
||||
entry_point: Some(fragment),
|
||||
compilation_options: wgpu::PipelineCompilationOptions {
|
||||
constants,
|
||||
..Default::default()
|
||||
},
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: IDS,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
let opaque = ids("Visibility buffer opaque", "vs_vis_opaque", "fs_vis_id");
|
||||
let cutout = ids("Visibility buffer cutout", "vs_vis_cutout", "fs_vis_cutout");
|
||||
let resolve = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Visibility buffer resolve"),
|
||||
layout: Some(&resolve_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &module,
|
||||
entry_point: Some("vs_vis_resolve"),
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: Default::default(),
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &module,
|
||||
entry_point: Some("fs_vis_resolve"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions {
|
||||
constants: &resolve_constants,
|
||||
..Default::default()
|
||||
},
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: color,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
Self {
|
||||
opaque,
|
||||
cutout,
|
||||
resolve,
|
||||
page_layout,
|
||||
blocks: None,
|
||||
blocks_per_page,
|
||||
ids: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes the IDs the world depth's `size`, and the block tables hold the pages `flat`
|
||||
/// draws this frame.
|
||||
pub fn fit(&mut self, device: &wgpu::Device, size: [u32; 2], flat: &Flat) {
|
||||
let pages = flat.calls[..flat.solid_calls]
|
||||
.iter()
|
||||
.map(|c| c.page + 1)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
if self.blocks.as_ref().is_none_or(|(_, n)| *n < pages) {
|
||||
let pages = pages.next_power_of_two();
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Visibility buffer block tables"),
|
||||
size: u64::from(pages) * self.blocks_per_page * 4,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
self.blocks = Some((buffer, pages));
|
||||
}
|
||||
if self.ids.as_ref().is_some_and(|(_, s)| *s == size) {
|
||||
return;
|
||||
}
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Visibility buffer IDs"),
|
||||
size: wgpu::Extent3d {
|
||||
width: size[0].max(1),
|
||||
height: size[1].max(1),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: IDS,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
self.ids = Some((texture.create_view(&Default::default()), size));
|
||||
}
|
||||
|
||||
/// Pass 1's render pass over the world's `depth`: the IDs cleared (`clear`, phase 1) or
|
||||
/// kept (phase 2 of occlusion culling).
|
||||
pub fn id_pass<'e>(
|
||||
&self,
|
||||
encoder: &'e mut wgpu::CommandEncoder,
|
||||
depth: &wgpu::TextureView,
|
||||
clear: bool,
|
||||
timing: Option<&mut PassTiming>,
|
||||
) -> wgpu::RenderPass<'e> {
|
||||
let (ids, _) = self.ids.as_ref().expect("fitted before the world pass");
|
||||
let name = if clear {
|
||||
"Visibility IDs"
|
||||
} else {
|
||||
"Visibility IDs after culling"
|
||||
};
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Horizon visibility buffer"),
|
||||
timestamp_writes: timing.and_then(|t| t.writes(name)),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: ids,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: if clear {
|
||||
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
|
||||
} else {
|
||||
wgpu::LoadOp::Load
|
||||
},
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: depth,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 1's solid calls into the IDs, as the normal passes draw them: the opaque
|
||||
/// pipeline over `calls[..pass_calls[0]]`, the cutout one over
|
||||
/// `calls[pass_calls[1]..phase1_calls]` (each leaves out the other's quads).
|
||||
pub fn draw_ids<'a>(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
flat: &Flat,
|
||||
vertices: impl Fn(u32) -> wgpu::BufferSlice<'a> + Copy,
|
||||
) {
|
||||
let [opaque, cutout, _] = flat.pass_calls;
|
||||
pass.set_pipeline(&self.opaque);
|
||||
flat.draw_paged(pass, &flat.calls[..opaque], vertices);
|
||||
pass.set_pipeline(&self.cutout);
|
||||
flat.draw_paged(pass, &flat.calls[cutout..flat.phase1_calls], vertices);
|
||||
}
|
||||
|
||||
/// Phase 2's calls as `Flat::flatten_phase2` left them (their first instance is their
|
||||
/// page: `Flat::page_instances`), through both pipelines.
|
||||
pub fn draw_ids_phase2<'a>(
|
||||
&self,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
flat: &Flat,
|
||||
vertices: impl Fn(u32) -> wgpu::BufferSlice<'a> + Copy,
|
||||
) {
|
||||
// Phase 2 lists each section's opaque runs first, its cutout runs after.
|
||||
let split = flat.pass_calls[2] - flat.phase1_calls;
|
||||
let total = flat.solid_calls - flat.phase1_calls;
|
||||
pass.set_pipeline(&self.opaque);
|
||||
flat.draw_phase2(pass, 0..split, vertices);
|
||||
pass.set_pipeline(&self.cutout);
|
||||
flat.draw_phase2(pass, split..total, vertices);
|
||||
}
|
||||
|
||||
/// Bytes of its block tables and IDs.
|
||||
pub fn bytes(&self) -> u64 {
|
||||
let blocks = self.blocks.as_ref().map_or(0, |(b, _)| b.size());
|
||||
let ids = self
|
||||
.ids
|
||||
.as_ref()
|
||||
.map_or(0, |(_, [w, h])| u64::from(*w) * u64::from(*h) * 4);
|
||||
blocks + ids
|
||||
}
|
||||
|
||||
/// Pass 2: shades the solid terrain the IDs show into `target`, the world's colour,
|
||||
/// over the sky drawn before. `terrain`: terrain.wgsl's group 0.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn resolve(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
terrain: &wgpu::BindGroup,
|
||||
flat: &Flat,
|
||||
arena: &Arena,
|
||||
timing: Option<&mut PassTiming>,
|
||||
) {
|
||||
let (Some((ids, _)), Some((blocks, _))) = (&self.ids, &self.blocks) else {
|
||||
return;
|
||||
};
|
||||
let mut pages: Vec<u32> = flat.calls[..flat.solid_calls]
|
||||
.iter()
|
||||
.map(|c| c.page)
|
||||
.collect();
|
||||
pages.sort_unstable();
|
||||
pages.dedup();
|
||||
let Some(&any) = pages.first() else {
|
||||
return;
|
||||
};
|
||||
let table = self.blocks_per_page * 4;
|
||||
for &page in &pages {
|
||||
if let Some(from) = flat.block_table(page) {
|
||||
encoder.copy_buffer_to_buffer(from, 0, blocks, u64::from(page) * table, table);
|
||||
}
|
||||
}
|
||||
// Pages in groups of `PAGE_SLOTS` by number: a page's slot is its number's remainder.
|
||||
// Slots no drawn page fills take one that is, which no pixel's ID names.
|
||||
let mut chunks: Vec<u32> = pages.iter().map(|p| p / PAGE_SLOTS).collect();
|
||||
chunks.dedup();
|
||||
let groups: Vec<(u32, wgpu::BindGroup)> = chunks
|
||||
.into_iter()
|
||||
.map(|chunk| {
|
||||
let mut entries = vec![
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: blocks.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: flat.origins().as_entire_binding(),
|
||||
},
|
||||
];
|
||||
entries.extend((0..PAGE_SLOTS).map(|slot| {
|
||||
let page = chunk * PAGE_SLOTS + slot;
|
||||
let page = if pages.binary_search(&page).is_ok() {
|
||||
page
|
||||
} else {
|
||||
any
|
||||
};
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2 + slot,
|
||||
resource: arena.buffer(page).as_entire_binding(),
|
||||
}
|
||||
}));
|
||||
entries.push(wgpu::BindGroupEntry {
|
||||
binding: 2 + PAGE_SLOTS,
|
||||
resource: wgpu::BindingResource::TextureView(ids),
|
||||
});
|
||||
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Visibility buffer pages"),
|
||||
layout: &self.page_layout,
|
||||
entries: &entries,
|
||||
});
|
||||
(chunk, group)
|
||||
})
|
||||
.collect();
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Horizon visibility buffer resolve"),
|
||||
timestamp_writes: timing.and_then(|t| t.writes("Visibility resolve")),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
..Default::default()
|
||||
});
|
||||
pass.set_pipeline(&self.resolve);
|
||||
pass.set_bind_group(0, terrain, &[]);
|
||||
// The group of pages goes as the instance: each draw shades the pixels whose quad is
|
||||
// in one of its pages (one draw, up to `PAGE_SLOTS` pages).
|
||||
for (chunk, group) in &groups {
|
||||
pass.set_bind_group(1, group, &[]);
|
||||
pass.draw(0..3, *chunk..*chunk + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,44 @@ impl EntityCullingMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Particle visibility strategy, independent of Entity Culling.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum ParticleCullingMode {
|
||||
#[default]
|
||||
Off,
|
||||
Synchronous,
|
||||
Asynchronous,
|
||||
}
|
||||
impl ParticleCullingMode {
|
||||
pub const ALL: [ParticleCullingMode; 3] = [
|
||||
ParticleCullingMode::Off,
|
||||
ParticleCullingMode::Synchronous,
|
||||
ParticleCullingMode::Asynchronous,
|
||||
];
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Off => "off",
|
||||
Self::Synchronous => "sync",
|
||||
Self::Asynchronous => "async",
|
||||
}
|
||||
}
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Off => "Off",
|
||||
Self::Synchronous => "Synchronous",
|
||||
Self::Asynchronous => "Asynchronous",
|
||||
}
|
||||
}
|
||||
fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"synchronous" | "sync" => Self::Synchronous,
|
||||
"asynchronous" | "async" => Self::Asynchronous,
|
||||
"off" => Self::Off,
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How the swapchain presents frames.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum PresentMode {
|
||||
@@ -333,6 +371,13 @@ pub enum LeafCullingMode {
|
||||
/// Every leaf face, as vanilla's fancy leaves draw them.
|
||||
#[default]
|
||||
Off,
|
||||
/// Drops a face between two leaves when the leaf it faces has a leaf or
|
||||
/// a full opaque cube behind it too: that face only shows through two
|
||||
/// cutout layers. The faces behind the outer layer stay, so a crown
|
||||
/// looks the same from outside. The faced leaf must be in the section
|
||||
/// (as `Hollow`), so the block behind it is at most a border cell of the
|
||||
/// next section, whose edits remesh this one.
|
||||
Hidden,
|
||||
/// Leaves surrounded on all six sides by leaves or full opaque cubes draw
|
||||
/// in the opaque layer, so their cutouts no longer show the layers behind.
|
||||
Solid,
|
||||
@@ -344,8 +389,9 @@ pub enum LeafCullingMode {
|
||||
Hollow,
|
||||
}
|
||||
impl LeafCullingMode {
|
||||
pub const ALL: [LeafCullingMode; 4] = [
|
||||
pub const ALL: [LeafCullingMode; 5] = [
|
||||
LeafCullingMode::Off,
|
||||
LeafCullingMode::Hidden,
|
||||
LeafCullingMode::Solid,
|
||||
LeafCullingMode::SolidAggressive,
|
||||
LeafCullingMode::Hollow,
|
||||
@@ -353,6 +399,7 @@ impl LeafCullingMode {
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
LeafCullingMode::Off => "off",
|
||||
LeafCullingMode::Hidden => "hidden",
|
||||
LeafCullingMode::Solid => "solid",
|
||||
LeafCullingMode::SolidAggressive => "solid_aggressive",
|
||||
LeafCullingMode::Hollow => "hollow",
|
||||
@@ -361,6 +408,7 @@ impl LeafCullingMode {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
LeafCullingMode::Off => "Off",
|
||||
LeafCullingMode::Hidden => "Hidden Faces",
|
||||
LeafCullingMode::Solid => "Solid",
|
||||
LeafCullingMode::SolidAggressive => "Solid Aggressive",
|
||||
LeafCullingMode::Hollow => "Hollow",
|
||||
@@ -551,6 +599,8 @@ pub struct Graphics {
|
||||
pub cave_culling: bool,
|
||||
/// Skip drawing entities hidden behind opaque terrain, synchronously or asynchronously.
|
||||
pub entity_culling: EntityCullingMode,
|
||||
/// Skip particles outside the view or hidden behind opaque terrain.
|
||||
pub particle_culling: ParticleCullingMode,
|
||||
/// Terrain leaf faces inside a canopy (see [`LeafCullingMode`]); `Off` draws
|
||||
/// vanilla's fancy leaves. Ignored with a resource pack that may replace
|
||||
/// the vanilla leaf models ([`Graphics::leaf_culling_effective`]).
|
||||
@@ -648,6 +698,7 @@ impl Default for Graphics {
|
||||
blue_map: crate::bluemap::Settings::default(),
|
||||
cave_culling: true,
|
||||
entity_culling: EntityCullingMode::Off,
|
||||
particle_culling: ParticleCullingMode::Off,
|
||||
leaf_culling: LeafCullingMode::Off,
|
||||
cache_shulker_boxes: false,
|
||||
// Sign text has used its renderer cache independently of the new model caches.
|
||||
@@ -741,6 +792,7 @@ impl Graphics {
|
||||
"blueMapAutoDetect" => g.blue_map.auto_detect = flag(g.blue_map.auto_detect),
|
||||
"caveCulling" => g.cave_culling = flag(g.cave_culling),
|
||||
"entityCulling" => g.entity_culling = EntityCullingMode::parse(value),
|
||||
"particleCulling" => g.particle_culling = ParticleCullingMode::parse(value),
|
||||
"leafCulling" => g.leaf_culling = LeafCullingMode::parse(value),
|
||||
"blockEntityCache" => {
|
||||
let enabled = flag(false);
|
||||
@@ -831,12 +883,13 @@ impl Graphics {
|
||||
}
|
||||
pub fn serialize(&self) -> String {
|
||||
let mut text = format!(
|
||||
"preset:{}\nlodDistance:{}\nfarDetail:{}\ncaveCulling:{}\nentityCulling:{}\nleafCulling:{}\nblockEntityCacheShulkerBoxes:{}\nblockEntityCacheSigns:{}\nblockEntityCacheDecoratedPots:{}\nblockEntityCacheBells:{}\nblockEntityCacheChests:{}\nblockEntityCacheBanners:{}\nblockEntityCacheCopperGolemStatues:{}\nchunkUpdates:{}\nuploadBudgetKiB:{}\ntextureBudgetMiB:{}\nsky:{}\nfog:{}\nweather:{}\nanimatedTextures:{}\nditherCutoutMips:{}\npresentMode:{}\nframePacing:{}\nworkers:{}\noverlay:{}\nresolutionScale:{}\nfxaa:{}\ntaa:{}\nupscaling:{}\nscreenshotQuality:{}\nframeGeneration:{}\nhdr:{}\nhdrPaperWhite:{}\nhdrPeak:{}\nhdrBlackLevel:{}\nhdrUiBrightness:{}\nhdrKey:{}\nbatteryMode:{}\nshadowCache:{}\ngraphicsApi:{}\npickedShaderPack:{}\nbatchTranslucent:{}\n",
|
||||
"preset:{}\nlodDistance:{}\nfarDetail:{}\ncaveCulling:{}\nentityCulling:{}\nparticleCulling:{}\nleafCulling:{}\nblockEntityCacheShulkerBoxes:{}\nblockEntityCacheSigns:{}\nblockEntityCacheDecoratedPots:{}\nblockEntityCacheBells:{}\nblockEntityCacheChests:{}\nblockEntityCacheBanners:{}\nblockEntityCacheCopperGolemStatues:{}\nchunkUpdates:{}\nuploadBudgetKiB:{}\ntextureBudgetMiB:{}\nsky:{}\nfog:{}\nweather:{}\nanimatedTextures:{}\nditherCutoutMips:{}\npresentMode:{}\nframePacing:{}\nworkers:{}\noverlay:{}\nresolutionScale:{}\nfxaa:{}\ntaa:{}\nupscaling:{}\nscreenshotQuality:{}\nframeGeneration:{}\nhdr:{}\nhdrPaperWhite:{}\nhdrPeak:{}\nhdrBlackLevel:{}\nhdrUiBrightness:{}\nhdrKey:{}\nbatteryMode:{}\nshadowCache:{}\ngraphicsApi:{}\npickedShaderPack:{}\nbatchTranslucent:{}\n",
|
||||
self.preset.name(),
|
||||
self.lod_distance,
|
||||
self.far_detail.name(),
|
||||
bool_text(self.cave_culling),
|
||||
self.entity_culling.name(),
|
||||
self.particle_culling.name(),
|
||||
self.leaf_culling.name(),
|
||||
bool_text(self.cache_shulker_boxes),
|
||||
bool_text(self.cache_signs),
|
||||
@@ -1015,6 +1068,7 @@ mod tests {
|
||||
},
|
||||
cave_culling: false,
|
||||
entity_culling: EntityCullingMode::Synchronous,
|
||||
particle_culling: ParticleCullingMode::Asynchronous,
|
||||
leaf_culling: LeafCullingMode::SolidAggressive,
|
||||
cache_shulker_boxes: true,
|
||||
cache_signs: true,
|
||||
@@ -1061,6 +1115,16 @@ mod tests {
|
||||
};
|
||||
assert_eq!(Graphics::parse(&graphics.serialize()), graphics);
|
||||
}
|
||||
for mode in ParticleCullingMode::ALL {
|
||||
let graphics = Graphics {
|
||||
particle_culling: mode,
|
||||
..Graphics::default()
|
||||
};
|
||||
assert_eq!(
|
||||
Graphics::parse(&graphics.serialize()).particle_culling,
|
||||
mode
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
Graphics::parse("entityCulling:true\n").entity_culling,
|
||||
EntityCullingMode::Synchronous
|
||||
|
||||
@@ -209,7 +209,29 @@ pub fn button(
|
||||
}
|
||||
let color = if live { WHITE } else { MUTED };
|
||||
let ty = y + ((h - 8.0) / 2.0).floor();
|
||||
ui::centered_text(batch, atlas, label, x + 2.0, ty, w - 4.0, color);
|
||||
scrolling_label(batch, atlas, label, [x + 2.0, ty, w - 4.0], (y, h), color);
|
||||
}
|
||||
/// A widget label of `[left, y, width]` inside the widget's `(top, height)`: centred when it fits,
|
||||
/// else slid (vanilla `AbstractWidget.extractScrollingStringOverContents`) and clipped to the box.
|
||||
fn scrolling_label(
|
||||
batch: &mut HudBatch,
|
||||
atlas: &HudAtlas,
|
||||
text: &Text,
|
||||
[left, y, width]: [f32; 3],
|
||||
(top, height): (f32, f32),
|
||||
color: u32,
|
||||
) {
|
||||
ui::scrolling_text(
|
||||
batch,
|
||||
atlas,
|
||||
text,
|
||||
left,
|
||||
left + width,
|
||||
top,
|
||||
top + height,
|
||||
y,
|
||||
color,
|
||||
);
|
||||
}
|
||||
/// A slider: its value over a thin track filled in the accent up to the handle at `position`.
|
||||
pub fn slider(
|
||||
@@ -222,7 +244,14 @@ pub fn slider(
|
||||
) {
|
||||
let [x, y, w, h] = rect;
|
||||
let color = if live { WHITE } else { MUTED };
|
||||
ui::centered_text(batch, atlas, value, x + 2.0, y + 1.0, w - 4.0, color);
|
||||
scrolling_label(
|
||||
batch,
|
||||
atlas,
|
||||
value,
|
||||
[x + 2.0, y + 1.0, w - 4.0],
|
||||
(y, h),
|
||||
color,
|
||||
);
|
||||
let line = y + h - 3.0;
|
||||
let at = x + (position.clamp(0.0, 1.0) * (w - 4.0)).round();
|
||||
fill(batch, atlas, [x, line, w, 2.0], TRACK);
|
||||
@@ -260,7 +289,7 @@ pub fn cycler(
|
||||
(true, true) => WHITE,
|
||||
(true, false) => SOFT,
|
||||
};
|
||||
batch.text_styled(atlas, value, x + 9.0, ty, color, true);
|
||||
scrolling_label(batch, atlas, value, [x + 9.0, ty, w - 18.0], (y, h), color);
|
||||
}
|
||||
/// A screen's title, centred at the top (split-screen profiles).
|
||||
pub fn title(batch: &mut HudBatch, atlas: &HudAtlas, text: &Text) {
|
||||
@@ -271,3 +300,76 @@ pub fn title(batch: &mut HudBatch, atlas: &HudAtlas, text: &Text) {
|
||||
pub fn cost_color(cost: Cost) -> u32 {
|
||||
hex(cost.rgb(), 255)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn atlas() -> HudAtlas {
|
||||
HudAtlas::build(std::path::Path::new("/nonexistent/horizon-test-assets")).unwrap()
|
||||
}
|
||||
|
||||
fn x_span(batch: &HudBatch, plain: &HudBatch) -> (f32, f32) {
|
||||
let label = |v: &&crate::hud::HudVertex| !plain.vertices.iter().any(|p| p.pos == v.pos);
|
||||
batch
|
||||
.vertices
|
||||
.iter()
|
||||
.filter(label)
|
||||
.map(|v| v.pos[0])
|
||||
.fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), x| {
|
||||
(lo.min(x), hi.max(x))
|
||||
})
|
||||
}
|
||||
|
||||
/// A label wider than its widget slides inside the widget's box instead of being cut by
|
||||
/// characters; a short one stays centred.
|
||||
#[test]
|
||||
fn long_labels_stay_in_the_widget_and_short_ones_centred() {
|
||||
let atlas = atlas();
|
||||
let rect = [10.0, 20.0, 100.0, 20.0];
|
||||
let long = Text::literal("Graphics API: Prefer Vulkan (Experimental)");
|
||||
assert!(atlas.font.width_text(&long.resolved()) > rect[2]);
|
||||
let short = Text::literal("Done");
|
||||
let short_w = atlas.font.width_text(&short.resolved());
|
||||
let draws: [(&str, fn(&mut HudBatch, &HudAtlas, [f32; 4], &Text)); 3] = [
|
||||
("button", |b, a, r, t| {
|
||||
button(b, a, r, t, (false, true, false))
|
||||
}),
|
||||
("slider", |b, a, r, t| {
|
||||
slider(b, a, r, t, 0.5, (false, true))
|
||||
}),
|
||||
("cycler", |b, a, r, t| cycler(b, a, r, t, (false, true))),
|
||||
];
|
||||
for (name, draw) in draws {
|
||||
let mut plain = HudBatch::new([640, 480]);
|
||||
draw(&mut plain, &atlas, rect, &Text::empty());
|
||||
let base = plain.vertices.len();
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
draw(&mut batch, &atlas, rect, &long);
|
||||
assert!(batch.vertices.len() > base, "{name}: long label drawn");
|
||||
// The widget's own fill, track and handle also draw: only the label's vertices count.
|
||||
let label = batch
|
||||
.vertices
|
||||
.iter()
|
||||
.filter(|v| !plain.vertices.iter().any(|p| p.pos == v.pos));
|
||||
for v in label {
|
||||
assert!(
|
||||
(rect[0]..=rect[0] + rect[2]).contains(&v.pos[0])
|
||||
&& (rect[1]..=rect[1] + rect[3]).contains(&v.pos[1]),
|
||||
"{name}: {:?} outside the widget",
|
||||
v.pos
|
||||
);
|
||||
}
|
||||
// The label's own glyphs (past the widget's fill) start centred.
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
draw(&mut batch, &atlas, rect, &short);
|
||||
let (lo, hi) = x_span(&batch, &plain);
|
||||
let middle = (lo + hi) / 2.0;
|
||||
let want = rect[0] + rect[2] / 2.0;
|
||||
assert!(
|
||||
(middle - want).abs() <= short_w / 2.0 + 2.0 && (middle - want).abs() < 3.0,
|
||||
"{name}: centre {middle}, want {want}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::{
|
||||
graphics::{
|
||||
EntityCullingMode, FarDetail, FrameGeneration, GraphicsApi, LeafCullingMode, Overlay,
|
||||
Pacing, PresentMode, Preset, Upscaling,
|
||||
Pacing, ParticleCullingMode, PresentMode, Preset, Upscaling,
|
||||
},
|
||||
graphics_style::{self as style, Cost},
|
||||
hud::{HudAtlas, HudBatch},
|
||||
@@ -66,6 +66,7 @@ pub enum H {
|
||||
BlueMapRadius,
|
||||
CaveCulling,
|
||||
EntityCulling,
|
||||
ParticleCulling,
|
||||
LeafCulling,
|
||||
CacheShulkerBoxes,
|
||||
CacheSigns,
|
||||
@@ -379,6 +380,11 @@ pub fn rows() -> Vec<(Page, Vec<(&'static str, Vec<Setting>)>)> {
|
||||
None,
|
||||
"Hides entities behind opaque blocks.",
|
||||
),
|
||||
h(
|
||||
H::ParticleCulling,
|
||||
None,
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.",
|
||||
),
|
||||
h(
|
||||
H::LeafCulling,
|
||||
Saves,
|
||||
@@ -620,6 +626,9 @@ fn description(item: Item, s: &Settings, text: &'static str) -> &'static str {
|
||||
},
|
||||
(Item::Horizon(H::LeafCulling), _) => match s.horizon.leaf_culling {
|
||||
LeafCullingMode::Off => "Every leaf face. Vanilla's fancy leaves.",
|
||||
LeafCullingMode::Hidden => {
|
||||
"Drops leaf faces behind two leaf layers: faster, same crowns."
|
||||
}
|
||||
LeafCullingMode::Solid => {
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers."
|
||||
}
|
||||
@@ -628,6 +637,15 @@ fn description(item: Item, s: &Settings, text: &'static str) -> &'static str {
|
||||
}
|
||||
LeafCullingMode::Hollow => "Drops faces between leaves: faster, but gaps look through.",
|
||||
},
|
||||
(Item::Horizon(H::ParticleCulling), _) => match s.horizon.particle_culling {
|
||||
ParticleCullingMode::Off => "Draws every particle.",
|
||||
ParticleCullingMode::Synchronous => {
|
||||
"Skips offscreen, subpixel and terrain-hidden particles."
|
||||
}
|
||||
ParticleCullingMode::Asynchronous => {
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work."
|
||||
}
|
||||
},
|
||||
_ => text,
|
||||
}
|
||||
}
|
||||
@@ -719,6 +737,7 @@ impl H {
|
||||
),
|
||||
H::CaveCulling => labelled("Cave Culling", on_off(g.cave_culling)),
|
||||
H::EntityCulling => labelled("Entity Culling", g.entity_culling.label()),
|
||||
H::ParticleCulling => labelled("Particle Culling", g.particle_culling.label()),
|
||||
H::LeafCulling => labelled("Leaf Culling", g.leaf_culling.label()),
|
||||
H::CacheShulkerBoxes => labelled("Shulker Boxes", on_off(g.cache_shulker_boxes)),
|
||||
H::CacheSigns => labelled("Signs", on_off(g.cache_signs)),
|
||||
@@ -871,6 +890,9 @@ impl H {
|
||||
H::EntityCulling => {
|
||||
g.entity_culling = step(&EntityCullingMode::ALL, g.entity_culling, back)
|
||||
}
|
||||
H::ParticleCulling => {
|
||||
g.particle_culling = step(&ParticleCullingMode::ALL, g.particle_culling, back)
|
||||
}
|
||||
H::LeafCulling => {
|
||||
g.leaf_culling = step(&LeafCullingMode::ALL, g.leaf_culling, back);
|
||||
}
|
||||
@@ -1902,6 +1924,7 @@ impl GraphicsUi {
|
||||
framebuffer: [i32; 2],
|
||||
drag: bool,
|
||||
right: bool,
|
||||
shift: bool,
|
||||
) -> Vec<Action> {
|
||||
self.keep(s);
|
||||
let (cols, lines, content) = self.layout(s, w, h);
|
||||
@@ -1979,7 +2002,7 @@ impl GraphicsUi {
|
||||
return Vec::new();
|
||||
}
|
||||
self.focus = Some(Target::Item(item));
|
||||
let actions = self.activate(s, item, false, framebuffer, Some((x, &cols)));
|
||||
let actions = self.activate(s, item, shift, framebuffer, Some((x, &cols)));
|
||||
self.mark(&actions);
|
||||
actions
|
||||
}
|
||||
@@ -2358,7 +2381,15 @@ mod tests {
|
||||
[cols.list_x, ui.row_y(&cols, &line), cols.list_w, style::ROW]
|
||||
}
|
||||
fn press(ui: &mut GraphicsUi, s: &mut Settings, (x, y): (f32, f32)) -> Vec<Action> {
|
||||
ui.click(s, (x, y), SIZE, FB, false, false)
|
||||
ui.click(s, (x, y), SIZE, FB, false, false, false)
|
||||
}
|
||||
fn press_shift(
|
||||
ui: &mut GraphicsUi,
|
||||
s: &mut Settings,
|
||||
(x, y): (f32, f32),
|
||||
shift: bool,
|
||||
) -> Vec<Action> {
|
||||
ui.click(s, (x, y), SIZE, FB, false, false, shift)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2449,7 +2480,15 @@ mod tests {
|
||||
assert!(y >= 0.0 && x + w <= size.0);
|
||||
// Undo, over Done, stays clear of the page list.
|
||||
assert!(cols.button(1)[1] >= y + h);
|
||||
ui.click(&mut s, (x + w / 2.0, y + h / 2.0), size, FB, false, false);
|
||||
ui.click(
|
||||
&mut s,
|
||||
(x + w / 2.0, y + h / 2.0),
|
||||
size,
|
||||
FB,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let (_, lines, _) = ui.layout(&s, size.0, size.1);
|
||||
assert_eq!(ui.current(&lines), Some(Page::Hdr));
|
||||
}
|
||||
@@ -2566,7 +2605,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(H::ChunkUpdates.slider(&s), Some(1.0));
|
||||
assert_eq!(
|
||||
ui.click(&mut s, (-100.0, -100.0), SIZE, FB, true, false),
|
||||
ui.click(&mut s, (-100.0, -100.0), SIZE, FB, true, false, false),
|
||||
[Action::SliderDrag]
|
||||
);
|
||||
assert_eq!(H::ChunkUpdates.slider(&s), Some(0.0));
|
||||
@@ -2638,7 +2677,7 @@ mod tests {
|
||||
let (cols, _, content) = ui.layout(&s, SIZE.0, SIZE.1);
|
||||
let x = cols.bar_x() + 2.0;
|
||||
let drag = |ui: &mut GraphicsUi, s: &mut Settings, y, drag| {
|
||||
ui.click(s, (x, y), SIZE, FB, drag, false)
|
||||
ui.click(s, (x, y), SIZE, FB, drag, false, false)
|
||||
};
|
||||
assert!(drag(&mut ui, &mut s, cols.top + 5.0, false).is_empty());
|
||||
drag(&mut ui, &mut s, cols.top + 15.0, true);
|
||||
@@ -2658,13 +2697,13 @@ mod tests {
|
||||
let mut ui = GraphicsUi::default();
|
||||
let (cols, ..) = ui.layout(&s, SIZE.0, SIZE.1);
|
||||
let x = cols.bar_x() + 2.0;
|
||||
ui.click(&mut s, (x, cols.top + 5.0), SIZE, FB, false, false);
|
||||
ui.click(&mut s, (x, cols.top + 5.0), SIZE, FB, false, false, false);
|
||||
assert!(ui.scroll.held());
|
||||
// A 130-high GUI leaves the list under the 40 px the scroller's thumb needs.
|
||||
let short = (SIZE.0, 130.0);
|
||||
assert!(!Columns::of(short.0, short.1).scrolls());
|
||||
assert!(
|
||||
ui.click(&mut s, (x, 80.0), short, FB, true, false)
|
||||
ui.click(&mut s, (x, 80.0), short, FB, true, false, false)
|
||||
.is_empty()
|
||||
);
|
||||
assert!(!ui.scroll.held());
|
||||
@@ -2974,6 +3013,92 @@ mod tests {
|
||||
assert_eq!(item.label(&s).plain(), "Entity Culling: Asynchronous");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn particle_culling_modes_are_opt_in_and_cycle_from_its_row() {
|
||||
let mut s = Settings::default();
|
||||
let item = Item::Horizon(H::ParticleCulling);
|
||||
assert_eq!(s.horizon.particle_culling, ParticleCullingMode::Off);
|
||||
assert_eq!(item.label(&s).plain(), "Particle Culling: Off");
|
||||
for mode in ParticleCullingMode::ALL {
|
||||
s.horizon.particle_culling = mode;
|
||||
for preset in Preset::ALL {
|
||||
crate::graphics::apply_preset(preset, &mut s);
|
||||
assert_eq!(s.horizon.particle_culling, mode);
|
||||
}
|
||||
assert_eq!(
|
||||
crate::graphics::Graphics::parse(&s.horizon.serialize()).particle_culling,
|
||||
mode
|
||||
);
|
||||
}
|
||||
s.horizon.particle_culling = ParticleCullingMode::Off;
|
||||
let mut ui = GraphicsUi::default();
|
||||
let r = row(&mut ui, &s, item);
|
||||
for (mode, label) in [
|
||||
(ParticleCullingMode::Synchronous, "Synchronous"),
|
||||
(ParticleCullingMode::Asynchronous, "Asynchronous"),
|
||||
(ParticleCullingMode::Off, "Off"),
|
||||
] {
|
||||
s.horizon.preset = Preset::Balanced;
|
||||
assert_eq!(
|
||||
press(&mut ui, &mut s, (r[0] + 4.0, r[1] + 4.0)),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.particle_culling, mode);
|
||||
assert_eq!(s.horizon.preset, Preset::Custom);
|
||||
assert_eq!(item.label(&s).plain(), format!("Particle Culling: {label}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphics_cycle_button_cycles_forward_and_shift_click_cycles_backward_with_wrapping() {
|
||||
let mut s = Settings::default();
|
||||
let item = Item::Horizon(H::EntityCulling);
|
||||
let mut ui = GraphicsUi::default();
|
||||
let r = row(&mut ui, &s, item);
|
||||
let at = (r[0] + 4.0, r[1] + 4.0);
|
||||
|
||||
// 1. Feature without shift (normal click): cycles forward (+1)
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Off);
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, false),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Synchronous);
|
||||
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, false),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Asynchronous);
|
||||
|
||||
// 3. Wrap-around forward (at max value, normal click wraps to min / 0)
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, false),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Off);
|
||||
|
||||
// 4. Wrap-around backward (at min / 0, shift-click wraps to max)
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, true),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Asynchronous);
|
||||
|
||||
// 2. Feature with shift: cycles backward (-1)
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, true),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Synchronous);
|
||||
|
||||
assert_eq!(
|
||||
press_shift(&mut ui, &mut s, at, true),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.entity_culling, EntityCullingMode::Off);
|
||||
}
|
||||
|
||||
/// Translucent Batching: a switch on the Advanced page, on by default, no part of a
|
||||
/// preset, and greyed out, reading Off, where the device can't batch.
|
||||
#[test]
|
||||
@@ -3031,7 +3156,7 @@ mod tests {
|
||||
let item = Item::Horizon(H::LeafCulling);
|
||||
assert_eq!(item.switch(&s), None);
|
||||
assert_eq!(item.label(&s).plain(), "Leaf Culling: Off");
|
||||
for label in ["Solid", "Solid Aggressive", "Hollow", "Off"] {
|
||||
for label in ["Hidden Faces", "Solid", "Solid Aggressive", "Hollow", "Off"] {
|
||||
item.cycle(&mut s, false, FB);
|
||||
assert_eq!(item.label(&s).plain(), format!("Leaf Culling: {label}"));
|
||||
assert_eq!(
|
||||
@@ -3050,7 +3175,12 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(culling.0, Page::Performance);
|
||||
let block: Vec<Item> = culling.1.iter().map(|r| r.item).collect();
|
||||
for h in [H::CaveCulling, H::EntityCulling, H::LeafCulling] {
|
||||
for h in [
|
||||
H::CaveCulling,
|
||||
H::EntityCulling,
|
||||
H::ParticleCulling,
|
||||
H::LeafCulling,
|
||||
] {
|
||||
assert!(block.contains(&Item::Horizon(h)), "{h:?}");
|
||||
}
|
||||
// The block-entity caches are the group after it, in this order.
|
||||
@@ -3101,7 +3231,7 @@ mod tests {
|
||||
press(&mut ui, &mut s, (r[0] + 4.0, r[1] + 4.0)),
|
||||
[Action::ButtonClick, Action::Changed]
|
||||
);
|
||||
assert_eq!(s.horizon.leaf_culling, LeafCullingMode::Solid);
|
||||
assert_eq!(s.horizon.leaf_culling, LeafCullingMode::Hidden);
|
||||
assert_eq!(s.horizon.preset, Preset::Custom);
|
||||
}
|
||||
|
||||
@@ -3266,36 +3396,47 @@ mod tests {
|
||||
let mut s = Settings::default();
|
||||
s.horizon.leaf_culling = LeafCullingMode::Hollow;
|
||||
s.horizon.entity_culling = EntityCullingMode::Asynchronous;
|
||||
for (code, leaf, entity, leaf_text) in [
|
||||
s.horizon.particle_culling = ParticleCullingMode::Asynchronous;
|
||||
for (code, leaf, entity, leaf_text, particle, particle_text) in [
|
||||
(
|
||||
"de_de",
|
||||
"Laub-Culling: Hohl",
|
||||
"Objekt-Culling: Asynchron",
|
||||
"Lässt Flächen zwischen Blättern weg: schneller, aber Lücken sind durchsichtig.",
|
||||
"Partikel-Culling: Asynchron",
|
||||
"Überspringt Partikel außerhalb des Bildes, unterhalb eines Pixels oder hinter Terrain und entlastet den Render-Thread.",
|
||||
),
|
||||
(
|
||||
"pl_pl",
|
||||
"Pomijanie liści: Puste",
|
||||
"Pomijanie bytów: Asynchroniczne",
|
||||
"Pomija ściany między liśćmi: szybciej, ale luki są przezroczyste.",
|
||||
"Pomijanie cząsteczek: Asynchroniczne",
|
||||
"Pomija cząsteczki poza ekranem, mniejsze niż piksel i zasłonięte przez teren, odciążając wątek renderowania.",
|
||||
),
|
||||
(
|
||||
"uk_ua",
|
||||
"Відсікання листя: Порожнє",
|
||||
"Відсік. сутностей: Асинхронно",
|
||||
"Прибирає грані між листям: швидше, але прогалини прозорі.",
|
||||
"Відсікання частинок: Асинхронно",
|
||||
"Пропускає частинки поза екраном, менші за піксель і закриті місцевістю, зменшуючи роботу потоку рендерингу.",
|
||||
),
|
||||
(
|
||||
"zh_cn",
|
||||
"树叶剔除: 镂空",
|
||||
"实体剔除: 异步",
|
||||
"省略树叶之间的面:更快,但缝隙会透视。",
|
||||
"粒子剔除: 异步",
|
||||
"跳过屏幕外、小于一个像素和被地形遮挡的粒子,减少渲染线程的工作量。",
|
||||
),
|
||||
(
|
||||
"fr_fr",
|
||||
"Occlusion feuillage: Creux",
|
||||
"Occlusion des entités: Asynchrone",
|
||||
"Supprime les faces entre feuilles : plus rapide, mais on voit à travers les trous.",
|
||||
"Occlusion des particules: Asynchrone",
|
||||
"Ignore les particules hors écran, trop petites ou masquées par le terrain avec moins de travail sur le thread de rendu.",
|
||||
),
|
||||
] {
|
||||
let (shown, _) = lang::scoped(lang::Language::load(&[], code), || {
|
||||
@@ -3304,11 +3445,19 @@ mod tests {
|
||||
item(H::LeafCulling).label(&s).plain(),
|
||||
item(H::EntityCulling).label(&s).plain(),
|
||||
lang::get(description(item(H::LeafCulling), &s, "")),
|
||||
item(H::ParticleCulling).label(&s).plain(),
|
||||
lang::get(description(item(H::ParticleCulling), &s, "")),
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
shown,
|
||||
(leaf.into(), entity.into(), leaf_text.into()),
|
||||
(
|
||||
leaf.into(),
|
||||
entity.into(),
|
||||
leaf_text.into(),
|
||||
particle.into(),
|
||||
particle_text.into(),
|
||||
),
|
||||
"{code}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ pub struct HudAtlas {
|
||||
/// A resource pack's own `core/text` shader for the GUI's text, translated;
|
||||
/// none draws the text natively.
|
||||
pub text_shader: Option<std::sync::Arc<horizon_shaders::CoreProgram>>,
|
||||
/// A resource pack's own `core/rendertype_lines` shader for the block outline, translated;
|
||||
/// none draws the outline natively.
|
||||
pub lines_shader: Option<std::sync::Arc<horizon_shaders::CoreProgram>>,
|
||||
/// The packs' OptiFine random entity textures, whose variants this atlas packs.
|
||||
pub random_textures: horizon_optifine::random_textures::RandomTextures,
|
||||
/// Their emissive overlays (`_e`), packed here too.
|
||||
@@ -813,6 +816,13 @@ impl HudAtlas {
|
||||
pub fn build_over_with_image(root: &Path, defaults: &Path, mod_image: bool) -> Result<Self> {
|
||||
let mut atlas = Self::pack(root, defaults, WALKED_TEXTURE_FOLDERS, true, mod_image)?;
|
||||
atlas.text_shader = pack_text::load(root, defaults);
|
||||
atlas.lines_shader = pack_text::load_core(
|
||||
root,
|
||||
defaults,
|
||||
"rendertype_lines",
|
||||
&[],
|
||||
&crate::gfx::core_lines::GLUE,
|
||||
);
|
||||
Ok(atlas)
|
||||
}
|
||||
/// Empty slots written at run time: map textures (game/maps.rs), the
|
||||
@@ -941,6 +951,7 @@ impl HudAtlas {
|
||||
glint_armor: (vec![0; 4], 1, 1),
|
||||
white,
|
||||
text_shader: None,
|
||||
lines_shader: None,
|
||||
random_textures: Default::default(),
|
||||
emissive: Default::default(),
|
||||
blinks: Default::default(),
|
||||
@@ -1384,6 +1395,7 @@ impl HudAtlas {
|
||||
glint,
|
||||
glint_armor,
|
||||
text_shader: None,
|
||||
lines_shader: None,
|
||||
random_textures,
|
||||
emissive,
|
||||
blinks,
|
||||
@@ -2996,8 +3008,14 @@ mod tests {
|
||||
}
|
||||
/// A resource root holding one animated block texture: a strip of two
|
||||
/// 16x16 frames, red then blue, whose mcmeta starts on the blue one.
|
||||
/// Each call gets its own folder: tests run side by side, and one adds
|
||||
/// a strip of its own that another would otherwise find.
|
||||
fn animated_root() -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!("horizon-atlas-anim-{}", std::process::id()));
|
||||
static NEXT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
let n = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let root =
|
||||
std::env::temp_dir().join(format!("horizon-atlas-anim-{}-{n}", std::process::id()));
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
let block = root.join("assets/minecraft/textures/block");
|
||||
fs::create_dir_all(&block).unwrap();
|
||||
write_strip(
|
||||
|
||||
@@ -29,36 +29,47 @@ const GLUE: Glue = Glue {
|
||||
vertex_id: "hz_vertex_id()",
|
||||
};
|
||||
|
||||
const VERTEX: &str = "assets/minecraft/shaders/core/text.vsh";
|
||||
const FRAGMENT: &str = "assets/minecraft/shaders/core/text.fsh";
|
||||
|
||||
/// The packs' text shader over `defaults` (the game's own resources, which have vanilla's and
|
||||
/// its includes), when a pack replaces either stage.
|
||||
/// The packs' text shader, when a pack replaces either stage.
|
||||
pub(super) fn load(root: &Path, defaults: &Path) -> Option<Arc<CoreProgram>> {
|
||||
load_core(root, defaults, "text", &["IS_GUI"], &GLUE)
|
||||
}
|
||||
|
||||
/// The packs' core shader `name` (`shaders/core/<name>.vsh` and `.fsh`) over `defaults` (the
|
||||
/// game's own resources, which have vanilla's and its includes), when a pack replaces either
|
||||
/// stage.
|
||||
pub(super) fn load_core(
|
||||
root: &Path,
|
||||
defaults: &Path,
|
||||
name: &str,
|
||||
defines: &[&str],
|
||||
glue: &Glue,
|
||||
) -> Option<Arc<CoreProgram>> {
|
||||
if root == defaults {
|
||||
return None;
|
||||
}
|
||||
let read = |base: &Path, path: &str| std::fs::read_to_string(base.join(path)).ok();
|
||||
let own =
|
||||
|path: &str| read(root, path).filter(|text| read(defaults, path).as_ref() != Some(text));
|
||||
let (own_vertex, own_fragment) = (own(VERTEX), own(FRAGMENT));
|
||||
let vertex_path = format!("assets/minecraft/shaders/core/{name}.vsh");
|
||||
let fragment_path = format!("assets/minecraft/shaders/core/{name}.fsh");
|
||||
let (own_vertex, own_fragment) = (own(&vertex_path), own(&fragment_path));
|
||||
if own_vertex.is_none() && own_fragment.is_none() {
|
||||
return None;
|
||||
}
|
||||
let vertex = own_vertex.or_else(|| read(defaults, VERTEX))?;
|
||||
let fragment = own_fragment.or_else(|| read(defaults, FRAGMENT))?;
|
||||
let vertex = own_vertex.or_else(|| read(defaults, &vertex_path))?;
|
||||
let fragment = own_fragment.or_else(|| read(defaults, &fragment_path))?;
|
||||
let include = |id: &str| {
|
||||
let (namespace, path) = id.split_once(':')?;
|
||||
let path = format!("assets/{namespace}/{path}");
|
||||
read(root, &path).or_else(|| read(defaults, &path))
|
||||
};
|
||||
match horizon_shaders::translate_core(&vertex, &fragment, &["IS_GUI"], &include, &GLUE) {
|
||||
match horizon_shaders::translate_core(&vertex, &fragment, defines, &include, glue) {
|
||||
Ok(program) => {
|
||||
log::info!("the resource pack's text shader draws the GUI's text");
|
||||
log::info!("the resource pack's {name} shader is used");
|
||||
Some(Arc::new(program))
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("the resource pack's text shader is not used: {e}");
|
||||
log::warn!("the resource pack's {name} shader is not used: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,27 @@ const QUEUED_PER_SAMPLER: usize = 32;
|
||||
const MAX_REFRESHED: usize = 64;
|
||||
/// Threads that sample far tiles beside the mesher. Sampling is the cold-load
|
||||
/// bottleneck (each task evaluates a shared density lattice for 256 columns),
|
||||
/// so medium machines use half their cores, not a quarter.
|
||||
/// so medium machines use two thirds of their cores, not half.
|
||||
///
|
||||
/// The mesher has little to do until tiles are sampled, so its threads sit
|
||||
/// idle through the part of a cold build that waits on sampling. Measured on
|
||||
/// a 12-thread machine building 4336 tiles at LOD distance 256, from the game
|
||||
/// answering the control socket until every target tile is sampled, four runs
|
||||
/// a configuration:
|
||||
///
|
||||
/// | samplers / meshers | sampling | whole build |
|
||||
/// |---|---|---|
|
||||
/// | 6 / 5 (before) | 15.0 s | 23.9 s |
|
||||
/// | 8 / 3 | 11.8 s | 18.6 s |
|
||||
/// | 9 / 2 | 10.6 s | 19.1 s |
|
||||
///
|
||||
/// Nine samplers finish sampling sooner still, but leave the mesher two
|
||||
/// threads and the whole build ends later. The cap keeps a large machine
|
||||
/// from starving its mesher, and `meshing_threads` keeps the frame and the
|
||||
/// ticks a core either way.
|
||||
fn sampling_threads(cores: usize) -> usize {
|
||||
if cores >= 8 {
|
||||
(cores / 2).min(8)
|
||||
(cores * 2 / 3).min(8)
|
||||
} else {
|
||||
(cores / 4).clamp(1, 3)
|
||||
}
|
||||
@@ -69,6 +86,9 @@ const WORLD_START: i32 = 1024 * 16;
|
||||
/// How many tile widths away from the camera a tile of the world-wide terrain
|
||||
/// splits (`Plan::visit`: cells then span about 1/48 of the distance).
|
||||
const WORLD_SPLIT: i32 = 3;
|
||||
/// A split tile stays split until its distance passes the threshold by this
|
||||
/// ratio (`Plan::visit`): 1 / 0.95.
|
||||
const RETURN: (i64, i64) = (20, 19);
|
||||
/// The farthest `Plan` reaches, in chunks.
|
||||
pub const MAX_CHUNKS: u32 = 1 << 22;
|
||||
/// The coarsest tile level whose chunks are generated in full (trees,
|
||||
@@ -176,7 +196,11 @@ pub struct Column {
|
||||
}
|
||||
impl Column {
|
||||
/// Reduce an entire horizontal footprint, rather than inflating its corner
|
||||
/// column (a single leaf used to become a whole distant tree-sized slab).
|
||||
/// column. A height is drawn where any column of the footprint holds a
|
||||
/// block there, in the material most of them hold: anything joined to
|
||||
/// the ground stays joined at every width. Drawing only heights most of
|
||||
/// the footprint held dropped trunks (one column of 16 or 256) under the
|
||||
/// crowns they hold up, and the crowns hung in the air as slabs.
|
||||
pub fn reduced(columns: &[Column; 256], x: usize, z: usize, width: usize) -> Self {
|
||||
if width == 1 {
|
||||
return columns[z * 16 + x].clone();
|
||||
@@ -200,7 +224,7 @@ impl Column {
|
||||
let mut bottom = MIN_Y as i16;
|
||||
for (y, material, change) in events {
|
||||
if bottom < y {
|
||||
if occupied * 2 >= (width * width) as i32
|
||||
if occupied > 0
|
||||
&& let Some(&((state, biome), _)) =
|
||||
votes.iter().max_by_key(|&(material, count)| {
|
||||
(*count, std::cmp::Reverse((material.0.0, material.1)))
|
||||
@@ -272,39 +296,6 @@ impl Column {
|
||||
self.spans.push(span);
|
||||
}
|
||||
}
|
||||
/// Open water and its floor lit as open sky, as the seed's far columns
|
||||
/// are: a chunk's own light fades a level per block of water, so the
|
||||
/// chunks a player passed drew a darker band through the seed's sea.
|
||||
/// The water is one span again.
|
||||
fn light_open_water(&mut self, kinds: &[u8]) {
|
||||
// Water; lava glows.
|
||||
self.light_open_water_where(|state| {
|
||||
let kind = kinds.get(state.0 as usize).copied().unwrap_or(0);
|
||||
kind & FLUID != 0 && kind >> 4 == 0
|
||||
});
|
||||
}
|
||||
/// [`Column::light_open_water`] for a caller that knows which states are
|
||||
/// water without `BlockFacts`: the save decoder, whose palette already
|
||||
/// names water apart from lava. Saved columns skipping this lit their
|
||||
/// water a level darker per block, which drew a band through the sea
|
||||
/// where they met the generator's columns.
|
||||
pub fn light_open_water_where(&mut self, is_water: impl Fn(BlockStateId) -> bool) {
|
||||
let water = |span: &Span| is_water(span.state);
|
||||
if !self.spans.last().is_some_and(water) {
|
||||
return;
|
||||
}
|
||||
let floor = self
|
||||
.spans
|
||||
.iter()
|
||||
.rposition(|span| !water(span))
|
||||
.unwrap_or(0);
|
||||
for (i, mut span) in std::mem::take(&mut self.spans).into_iter().enumerate() {
|
||||
if i >= floor {
|
||||
span.light |= 0xf0;
|
||||
}
|
||||
self.push(span);
|
||||
}
|
||||
}
|
||||
/// `kinds`: `state_kinds`, per block state; `water`: `water_source(kinds)`,
|
||||
/// what blocks holding water (kelp, seagrass, waterlogged ones) are drawn as.
|
||||
fn from_chunk(
|
||||
@@ -369,7 +360,6 @@ impl Column {
|
||||
});
|
||||
}
|
||||
}
|
||||
column.light_open_water(kinds);
|
||||
let mut water = None;
|
||||
for span in column.spans.iter().rev() {
|
||||
if kind(span.state) & FLUID != 0 {
|
||||
@@ -670,20 +660,36 @@ impl Plan {
|
||||
}
|
||||
// Keep distant cells small on screen; finest tiles retain individual
|
||||
// blocks. Reduced keeps them twice as large (a quarter of the cells).
|
||||
let split = if previous.split.contains(&key) { 10 } else { 8 };
|
||||
let held = previous.split.contains(&key);
|
||||
let split = if held { 10 } else { 8 };
|
||||
// Beyond the old 1024-chunk limit the cells are as large as `reduced`'s:
|
||||
// the whole world is 22 levels, not all of them worth full cells.
|
||||
let threshold = key.size()
|
||||
* if key.distance(camera) >= WORLD_START {
|
||||
let world = key.distance(camera) >= WORLD_START;
|
||||
let threshold = i64::from(key.size())
|
||||
* i64::from(if world {
|
||||
WORLD_SPLIT
|
||||
} else if reduced {
|
||||
split / 2
|
||||
} else {
|
||||
split
|
||||
};
|
||||
});
|
||||
// The 8 to 10 band above is wide already. The ring of full detail
|
||||
// around the camera and the world-wide threshold have none: a tile
|
||||
// split last plan stays split until it is 5% past them, or it
|
||||
// flickered between two levels as the camera moved a block to and
|
||||
// fro across the line.
|
||||
let hold = |bound: i64| {
|
||||
if held {
|
||||
bound * RETURN.0 / RETURN.1
|
||||
} else {
|
||||
bound
|
||||
}
|
||||
};
|
||||
let distance = i64::from(key.distance(camera));
|
||||
if key.level > 0
|
||||
&& (key.distance(camera) < detail * 16 + 16
|
||||
|| key.distance(camera).max(self.elevation) < threshold)
|
||||
&& (distance < hold(i64::from(detail) * 16 + 16)
|
||||
|| distance.max(i64::from(self.elevation))
|
||||
< if world { hold(threshold) } else { threshold })
|
||||
{
|
||||
self.split.insert(key);
|
||||
for child in key.children() {
|
||||
@@ -823,6 +829,10 @@ struct Snapshot {
|
||||
samples: FxHashMap<TileKey, Arc<Vec<Column>>>,
|
||||
saved: Saved,
|
||||
reduced: Reduced,
|
||||
/// The plan's tiles, to tell terrain not drawn yet from terrain out of
|
||||
/// range (`Snapshot::planned_leaf`).
|
||||
nodes: FxHashSet<TileKey>,
|
||||
split: FxHashSet<TileKey>,
|
||||
}
|
||||
/// One column of a mesh input, by reference into the snapshot. Mesh jobs used
|
||||
/// to clone all 256 columns of a tile (each with its own span allocation);
|
||||
@@ -853,6 +863,8 @@ struct EdgeSource {
|
||||
start: i32,
|
||||
end: i32,
|
||||
column: ColumnSource,
|
||||
/// Planned but not drawn yet (`mesh::EdgeColumn::unknown`).
|
||||
unknown: bool,
|
||||
}
|
||||
struct MeshInput {
|
||||
/// The tile's samples, with `overrides` replacing some of them.
|
||||
@@ -885,6 +897,7 @@ impl MeshInput {
|
||||
start: edge.start,
|
||||
end: edge.end,
|
||||
column: edge.column.get(),
|
||||
unknown: edge.unknown,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
@@ -1011,6 +1024,18 @@ struct Shared {
|
||||
approximate: AtomicBool,
|
||||
/// On small machines, hold queued far meshing while near terrain loads.
|
||||
mesh_held: AtomicBool,
|
||||
/// The near terrain is loading: the far threads step behind it
|
||||
/// (`horizon_threads::far_terrain_behind`) and come back once it is in.
|
||||
near_loading: AtomicBool,
|
||||
}
|
||||
/// Puts the calling far terrain thread behind the near terrain while it
|
||||
/// loads, and back once it is in. `behind`: where the thread is now.
|
||||
fn step_behind(shared: &Shared, behind: &mut bool) {
|
||||
let loading = shared.near_loading.load(Ordering::Relaxed);
|
||||
if loading != *behind {
|
||||
horizon_threads::far_terrain_behind(loading);
|
||||
*behind = loading;
|
||||
}
|
||||
}
|
||||
/// A background worker, sampling and meshing threads, and a bounded queue of
|
||||
/// sample jobs (`sample_jobs`). Only the current view's quadtree and a
|
||||
@@ -1139,6 +1164,7 @@ impl Lod {
|
||||
stop: AtomicBool::new(false),
|
||||
approximate: AtomicBool::new(false),
|
||||
mesh_held: AtomicBool::new(false),
|
||||
near_loading: AtomicBool::new(false),
|
||||
});
|
||||
let worker = shared.clone();
|
||||
let (tx, results) = mpsc::channel();
|
||||
@@ -1158,7 +1184,8 @@ impl Lod {
|
||||
thread::Builder::new()
|
||||
.name("Far meshing".into())
|
||||
.spawn(move || {
|
||||
horizon_threads::behind_server();
|
||||
horizon_threads::far_terrain_current();
|
||||
let mut behind = true;
|
||||
let mut mesher = mesh::Mesher::default();
|
||||
loop {
|
||||
let job = {
|
||||
@@ -1189,6 +1216,7 @@ impl Lod {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
step_behind(&shared, &mut behind);
|
||||
let keep_finer = !job.finer.is_empty();
|
||||
let input = job.snapshot.mesh_input(job.key, job.finer);
|
||||
let columns = input.columns();
|
||||
@@ -1244,7 +1272,8 @@ impl Lod {
|
||||
.name("Far terrain".into())
|
||||
.spawn(move || {
|
||||
// Ticks and frames come first (horizon_threads).
|
||||
horizon_threads::behind_server();
|
||||
horizon_threads::far_terrain_current();
|
||||
let mut behind = true;
|
||||
let mut sampler = source.sampler();
|
||||
let count = sampling_threads(cores);
|
||||
let mut sampling = Vec::new();
|
||||
@@ -1258,7 +1287,8 @@ impl Lod {
|
||||
thread::Builder::new()
|
||||
.name("Far sampling".into())
|
||||
.spawn(move || {
|
||||
horizon_threads::behind_server();
|
||||
horizon_threads::far_terrain_current();
|
||||
let mut behind = true;
|
||||
loop {
|
||||
let key = {
|
||||
let mut jobs = shared.jobs.lock().unwrap();
|
||||
@@ -1281,6 +1311,7 @@ impl Lod {
|
||||
jobs = shared.wake.wait(jobs).unwrap();
|
||||
}
|
||||
};
|
||||
step_behind(&shared, &mut behind);
|
||||
let [x, _, z] = key.origin();
|
||||
sample.set_approximate(
|
||||
shared.approximate.load(Ordering::Relaxed),
|
||||
@@ -1346,6 +1377,7 @@ impl Lod {
|
||||
}
|
||||
};
|
||||
let Some(job) = job else { continue };
|
||||
step_behind(&worker, &mut behind);
|
||||
let done = match job {
|
||||
Job::Observe(key) => {
|
||||
let chunk = worker.observed.lock().unwrap().remove(&key);
|
||||
@@ -2091,6 +2123,7 @@ impl Lod {
|
||||
self.held_sample_at = std::time::Instant::now();
|
||||
}
|
||||
self.near_loading = loading;
|
||||
self.shared.near_loading.store(loading, Ordering::Relaxed);
|
||||
let held = self.held();
|
||||
if self.shared.mesh_held.swap(held, Ordering::Release) && !held {
|
||||
self.shared.mesh_wake.notify_all();
|
||||
@@ -2223,7 +2256,7 @@ impl Lod {
|
||||
/// Rebuilds the quadtree and the selection it implies. Only runs when the
|
||||
/// planned area changes (a chunk boundary), never per finished sample.
|
||||
fn replan(&mut self, camera: [i32; 2], detail: i32, distance: u32, elevation: i32) {
|
||||
self.plan = Plan::with_previous(
|
||||
let plan = Plan::with_previous(
|
||||
camera,
|
||||
detail,
|
||||
distance,
|
||||
@@ -2231,6 +2264,17 @@ impl Lod {
|
||||
elevation,
|
||||
self.reduced_detail,
|
||||
);
|
||||
// A tile that leaves the range before it was drawn stops being
|
||||
// unknown to its neighbours (`Snapshot::planned_leaf`): they remesh
|
||||
// with walls against it, as against any terrain out of range.
|
||||
let gone: Vec<_> = self
|
||||
.plan
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|key| !plan.nodes.contains(key) && !self.ready.contains_key(key))
|
||||
.copied()
|
||||
.collect();
|
||||
self.plan = plan;
|
||||
self.ready.retain(|key, _| self.plan.nodes.contains(key));
|
||||
// Queued tiles the view left are not sampled (a running one is
|
||||
// dropped when it finishes, `finish_sample`).
|
||||
@@ -2254,6 +2298,9 @@ impl Lod {
|
||||
for key in standins {
|
||||
self.refresh_path(key);
|
||||
}
|
||||
for key in gone {
|
||||
self.mark_side_neighbors_of(key);
|
||||
}
|
||||
}
|
||||
/// Recomputes every active tile after a plan change, then marks only the
|
||||
/// tiles whose selection changed (plus their neighbours).
|
||||
@@ -2559,6 +2606,8 @@ impl Lod {
|
||||
.collect(),
|
||||
saved: self.saved.clone(),
|
||||
reduced: self.reduced.clone(),
|
||||
nodes: self.plan.nodes.clone(),
|
||||
split: self.plan.split.clone(),
|
||||
});
|
||||
let covered = Arc::new(self.covered.clone());
|
||||
let mut queued = self.shared.mesh_jobs.lock().unwrap();
|
||||
@@ -2706,6 +2755,8 @@ impl Lod {
|
||||
.collect(),
|
||||
saved: self.saved.clone(),
|
||||
reduced: self.reduced.clone(),
|
||||
nodes: self.plan.nodes.clone(),
|
||||
split: self.plan.split.clone(),
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
@@ -2777,6 +2828,23 @@ impl Snapshot {
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Whether block column (wx, wz) lies in a planned tile that is not
|
||||
/// split, whatever drew it (nothing, here): the finest planned tile
|
||||
/// there. Past a split tile's out-of-range children, or past the plan,
|
||||
/// there is no terrain to wait for.
|
||||
fn planned_leaf(&self, wx: i32, wz: i32) -> bool {
|
||||
(0..=MAX_LEVEL)
|
||||
.map(|level| {
|
||||
let size = 16 << level;
|
||||
TileKey {
|
||||
x: wx.div_euclid(size),
|
||||
z: wz.div_euclid(size),
|
||||
level,
|
||||
}
|
||||
})
|
||||
.find(|key| self.nodes.contains(key))
|
||||
.is_some_and(|key| !self.split.contains(&key))
|
||||
}
|
||||
fn mesh_input(&self, key: TileKey, finer: Vec<TileKey>) -> MeshInput {
|
||||
let stride = key.size() / CELLS as i32;
|
||||
let [ox, _, oz] = key.origin();
|
||||
@@ -2800,9 +2868,12 @@ impl Snapshot {
|
||||
2 => (ox + offset, oz - 1),
|
||||
_ => (ox + offset, oz + key.size()),
|
||||
};
|
||||
let (column, neighbor_stride) = self
|
||||
.neighbor_source(wx, wz)
|
||||
.unwrap_or((ColumnSource::Empty, stride));
|
||||
// Nothing drawn across the edge: still to come when planned,
|
||||
// so no wall is drawn against it; empty past the plan.
|
||||
let (column, neighbor_stride, unknown) = match self.neighbor_source(wx, wz) {
|
||||
Some((column, stride)) => (column, stride, false),
|
||||
None => (ColumnSource::Empty, stride, self.planned_leaf(wx, wz)),
|
||||
};
|
||||
let coordinate = if direction < 2 { wz } else { wx };
|
||||
let width = (neighbor_stride - coordinate.rem_euclid(neighbor_stride))
|
||||
.min(key.size() - offset);
|
||||
@@ -2810,6 +2881,7 @@ impl Snapshot {
|
||||
start: offset,
|
||||
end: offset + width,
|
||||
column,
|
||||
unknown,
|
||||
});
|
||||
offset += width;
|
||||
}
|
||||
@@ -3148,6 +3220,27 @@ mod tests {
|
||||
assert!(after.nodes.len() < 16000);
|
||||
}
|
||||
#[test]
|
||||
fn a_split_tile_stays_split_a_little_past_the_detail_ring() {
|
||||
// Detail 40 puts the ring of full detail at 656 blocks, past where
|
||||
// level 2 tiles split by their width (8 * 64 = 512): the ring alone
|
||||
// splits this one, 640 blocks away.
|
||||
let tile = TileKey {
|
||||
x: 10,
|
||||
z: 0,
|
||||
level: 2,
|
||||
};
|
||||
let before = Plan::new([0, 0], 40, 256);
|
||||
assert!(before.split.contains(&tile));
|
||||
// 670 blocks: past the ring, inside 5% of it.
|
||||
let held = Plan::with_previous([-30, 0], 40, 256, &before, 0, false);
|
||||
assert!(held.split.contains(&tile));
|
||||
// Not held without having been split: it was never in the ring.
|
||||
assert!(!Plan::new([-30, 0], 40, 256).split.contains(&tile));
|
||||
// 700 blocks: past the margin.
|
||||
let gone = Plan::with_previous([-60, 0], 40, 256, &before, 0, false);
|
||||
assert!(!gone.split.contains(&tile));
|
||||
}
|
||||
#[test]
|
||||
fn reduced_material_votes_ignore_light_and_retain_exposure() {
|
||||
let mut columns: [Column; 256] = std::array::from_fn(|_| Column::default());
|
||||
for i in 0..16 {
|
||||
@@ -3185,49 +3278,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn open_water_and_its_floor_are_lit_as_the_seed_lights_them() {
|
||||
let span = |min, max, state, light| Span {
|
||||
min,
|
||||
max,
|
||||
state: BlockStateId(state),
|
||||
biome: 0,
|
||||
light,
|
||||
};
|
||||
// 0 air, 1 stone, 2 water, 3 lava.
|
||||
let kinds = [AIR, KEPT, KEPT | FLUID, KEPT | FLUID | 15 << 4];
|
||||
// A chunk's own light: a cave, the floor, water fading a level a block.
|
||||
let mut sea = Column {
|
||||
surface: None,
|
||||
spans: vec![
|
||||
span(30, 40, 1, 0),
|
||||
span(40, 48, 1, 0x10 | 2 << 12),
|
||||
span(48, 61, 2, 0x20),
|
||||
span(61, 62, 2, 0xe0),
|
||||
span(62, 63, 2, 0xf0),
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
sea.light_open_water(&kinds);
|
||||
assert_eq!(
|
||||
sea.spans,
|
||||
[
|
||||
span(30, 40, 1, 0),
|
||||
span(40, 48, 1, 0xf0 | 2 << 12),
|
||||
span(48, 63, 2, 0xf0)
|
||||
]
|
||||
);
|
||||
// Land, and lava, keep their light.
|
||||
for top in [1, 3] {
|
||||
let mut column = Column {
|
||||
surface: None,
|
||||
spans: vec![span(40, 50, 2, 0x20), span(50, 63, top, 0x80)].into(),
|
||||
};
|
||||
let before = column.spans.clone();
|
||||
column.light_open_water(&kinds);
|
||||
assert_eq!(column.spans, before);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn nearly_full_blocks_are_kept() {
|
||||
assert!(nearly_full("minecraft:powder_snow"));
|
||||
assert!(nearly_full("minecraft:dirt_path"));
|
||||
@@ -3262,7 +3312,7 @@ mod tests {
|
||||
assert_eq!(kelp.surface.unwrap().height, 40);
|
||||
}
|
||||
#[test]
|
||||
fn coarse_voxels_reduce_the_area_instead_of_inflating_one_leaf() {
|
||||
fn coarse_voxels_keep_what_any_column_holds_so_crowns_stay_on_trunks() {
|
||||
let span = |min, max, state| Span {
|
||||
min,
|
||||
max,
|
||||
@@ -3274,8 +3324,8 @@ mod tests {
|
||||
surface: None,
|
||||
spans: vec![span(0, 4, 1)].into(),
|
||||
});
|
||||
columns[0].spans.push(span(8, 10, 2));
|
||||
assert_eq!(Column::reduced(&columns, 0, 0, 4).spans, [span(0, 4, 1)]);
|
||||
// One trunk column under a crown over the whole footprint.
|
||||
columns[0].spans.push(span(4, 12, 2));
|
||||
for z in 0..4 {
|
||||
for x in 0..4 {
|
||||
columns[z * 16 + x].spans.push(span(12, 14, 3));
|
||||
@@ -3283,17 +3333,20 @@ mod tests {
|
||||
}
|
||||
assert_eq!(
|
||||
Column::reduced(&columns, 0, 0, 4).spans,
|
||||
[span(0, 4, 1), span(12, 14, 3)]
|
||||
[span(0, 4, 1), span(4, 12, 2), span(12, 14, 3)]
|
||||
);
|
||||
assert_eq!(Column::reduced(&columns, 0, 0, 1), columns[0]);
|
||||
let reduced = ReducedColumns::new(Arc::new(columns));
|
||||
assert_eq!(
|
||||
reduced.get(0, 0, 1).spans,
|
||||
[span(0, 4, 1), span(8, 10, 2), span(12, 14, 3)]
|
||||
reduced.get(0, 0, 4).spans,
|
||||
[span(0, 4, 1), span(4, 12, 2), span(12, 14, 3)]
|
||||
);
|
||||
assert_eq!(reduced.get(0, 0, 4).spans, [span(0, 4, 1), span(12, 14, 3)]);
|
||||
assert_eq!(reduced.get(4, 8, 4).spans, [span(0, 4, 1)]);
|
||||
assert_eq!(reduced.get(0, 0, 16).spans, [span(0, 4, 1)]);
|
||||
assert_eq!(
|
||||
reduced.get(0, 0, 16).spans,
|
||||
[span(0, 4, 1), span(4, 12, 2), span(12, 14, 3)],
|
||||
"a crown over one corner of a wide cell still sits on its trunk"
|
||||
);
|
||||
}
|
||||
/// Detail read before a refresh over its chunk is dropped (asked
|
||||
/// again); elsewhere, or read after, it is kept.
|
||||
@@ -3999,9 +4052,22 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn extra_sampling_threads_only_on_large_machines() {
|
||||
assert_eq!([1, 2, 4, 8, 11].map(sampling_threads), [1, 1, 1, 4, 5]);
|
||||
assert_eq!([12, 16, 64].map(sampling_threads), [6, 8, 8]);
|
||||
assert_eq!([2, 8, 12, 16].map(meshing_threads), [1, 3, 5, 7]);
|
||||
// Small machines are unchanged: a quarter of their cores, never more
|
||||
// than three, so the near chunks and the frame keep theirs.
|
||||
assert_eq!([1, 2, 4].map(sampling_threads), [1, 1, 1]);
|
||||
// From eight, two thirds rather than half, still capped at eight.
|
||||
assert_eq!([8, 11, 12].map(sampling_threads), [5, 7, 8]);
|
||||
assert_eq!([16, 64].map(sampling_threads), [8, 8]);
|
||||
assert_eq!([2, 8, 12, 16].map(meshing_threads), [1, 2, 3, 7]);
|
||||
// From four cores up the two pools together leave one for the frame
|
||||
// and the ticks. Below that both clamp to one and there is nothing
|
||||
// to spare, which is the existing behaviour.
|
||||
for cores in [4usize, 8, 11, 12, 16, 64] {
|
||||
assert!(
|
||||
sampling_threads(cores) + meshing_threads(cores) < cores,
|
||||
"{cores} cores leaves nothing for the frame"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Review point 3 on #4747: a result from a batch that is no longer the
|
||||
@@ -4060,6 +4126,87 @@ mod tests {
|
||||
assert_eq!(meshes.len(), 2, "a tile that left the view is dropped");
|
||||
}
|
||||
|
||||
/// A neighbour inside the plan that is not drawn yet is unknown, not
|
||||
/// empty, and its arrival remeshes the tile that faced it. One past the
|
||||
/// planned range is still empty.
|
||||
#[test]
|
||||
fn a_planned_neighbor_that_is_not_ready_is_unknown_not_empty() {
|
||||
let mut lod = Lod::scheduler_for_test();
|
||||
let a = TileKey {
|
||||
x: 0,
|
||||
z: 0,
|
||||
level: 0,
|
||||
};
|
||||
let b = TileKey {
|
||||
x: 1,
|
||||
z: 0,
|
||||
level: 0,
|
||||
};
|
||||
lod.plan = Plan::default();
|
||||
lod.plan.roots = vec![a, b];
|
||||
lod.plan.nodes.extend([a, b]);
|
||||
lod.finish_sample(a, vec![Column::default(); CELLS * CELLS]);
|
||||
assert!(lod.active.contains(&a));
|
||||
let sides = |lod: &Lod| {
|
||||
let input = lod.snapshot().mesh_input(a, Vec::new());
|
||||
[0, 1].map(|side| {
|
||||
input.neighbors[side]
|
||||
.iter()
|
||||
.map(|edge| (edge.unknown, matches!(edge.column, ColumnSource::Tile(..))))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
let [west, east] = sides(&lod);
|
||||
assert!(west.iter().all(|&edge| edge == (false, false)), "range end");
|
||||
assert!(east.iter().all(|&edge| edge == (true, false)), "planned");
|
||||
|
||||
// Its arrival draws the neighbour and remeshes the tile facing it.
|
||||
lod.dirty.clear();
|
||||
lod.finish_sample(b, vec![Column::default(); CELLS * CELLS]);
|
||||
assert!(lod.dirty.contains_key(&a), "the tile facing it is remeshed");
|
||||
let [_, east] = sides(&lod);
|
||||
assert!(east.iter().all(|&edge| edge == (false, true)), "drawn");
|
||||
|
||||
// Past a split tile's out-of-range child there is nothing to wait for.
|
||||
let parent = TileKey {
|
||||
x: 4,
|
||||
z: 4,
|
||||
level: 1,
|
||||
};
|
||||
lod.plan.nodes.insert(parent);
|
||||
lod.plan.split.insert(parent);
|
||||
lod.plan.nodes.extend(parent.children().into_iter().take(3));
|
||||
let snapshot = lod.snapshot();
|
||||
let [x, _, z] = parent.origin();
|
||||
assert!(snapshot.planned_leaf(x + 1, z + 1), "a planned leaf");
|
||||
assert!(!snapshot.planned_leaf(x + 17, z + 17), "no child planned");
|
||||
assert!(!snapshot.planned_leaf(x - 40, z), "outside the plan");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_planned_neighbor_leaving_the_range_unready_remeshes_the_tile_that_waited() {
|
||||
let mut lod = Lod::scheduler_for_test();
|
||||
let a = TileKey {
|
||||
x: 0,
|
||||
z: 0,
|
||||
level: 0,
|
||||
};
|
||||
let b = TileKey {
|
||||
x: 1,
|
||||
z: 0,
|
||||
level: 0,
|
||||
};
|
||||
lod.plan = Plan::default();
|
||||
lod.plan.roots = vec![a, b];
|
||||
lod.plan.nodes.extend([a, b]);
|
||||
lod.finish_sample(a, vec![Column::default(); CELLS * CELLS]);
|
||||
lod.dirty.clear();
|
||||
// A range of one chunk around (-10, 0) keeps `a` and drops `b`.
|
||||
lod.replan([-10, 0], 0, 1, 0);
|
||||
assert!(lod.plan.nodes.contains(&a) && !lod.plan.nodes.contains(&b));
|
||||
assert!(lod.dirty.contains_key(&a), "its east wall is drawn now");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn far_meshing_holds_while_small_machine_near_terrain_loads() {
|
||||
let mut lod = Lod::scheduler_for_test();
|
||||
|
||||
@@ -16,6 +16,9 @@ pub(super) struct EdgeColumn<'a> {
|
||||
pub start: i32,
|
||||
pub end: i32,
|
||||
pub column: &'a Column,
|
||||
/// The neighbour is planned but not drawn yet: its terrain is not known
|
||||
/// to be empty, so the sides facing it are left out, not drawn as walls.
|
||||
pub unknown: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -319,6 +322,7 @@ fn mesh_columns_with_neighbors(
|
||||
start: row as i32 * stride,
|
||||
end: (row as i32 + 1) * stride,
|
||||
column,
|
||||
unknown: false,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
@@ -347,13 +351,17 @@ fn heightmap(column: &Column, mut fluid: impl FnMut(&Span) -> bool) -> Column {
|
||||
..*span
|
||||
};
|
||||
let mut spans = Spans::with_capacity(2);
|
||||
match below.last() {
|
||||
Some(under) if fluid(top) => {
|
||||
spans.push(ground(under));
|
||||
spans.push(*top);
|
||||
}
|
||||
_ if fluid(top) => spans.push(*top),
|
||||
_ => spans.push(ground(top)),
|
||||
if !fluid(top) {
|
||||
spans.push(ground(top));
|
||||
} else if let Some(under) = below.iter().rev().find(|&span| !fluid(span)) {
|
||||
// Shaded water is a span per light level; the bed is under all of it.
|
||||
spans.push(ground(under));
|
||||
spans.push(Span {
|
||||
min: under.max,
|
||||
..*top
|
||||
});
|
||||
} else {
|
||||
spans.push(*top);
|
||||
}
|
||||
Column {
|
||||
surface: column.surface,
|
||||
@@ -396,6 +404,7 @@ fn mesh_columns_with_edges(
|
||||
start: edge.start,
|
||||
end: edge.end,
|
||||
column,
|
||||
unknown: edge.unknown,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
@@ -660,6 +669,9 @@ fn mesh_columns_with_edges(
|
||||
// A coarse face can meet several finer columns.
|
||||
// Clip each interval independently to avoid missing walls.
|
||||
for (i, edge) in neighbors[edge_side].iter().enumerate() {
|
||||
if edge.unknown {
|
||||
continue;
|
||||
}
|
||||
let start = u0.max(edge.start as f32);
|
||||
let end = u1.min(edge.end as f32);
|
||||
if start < end {
|
||||
@@ -785,6 +797,70 @@ mod refinement_tests {
|
||||
}
|
||||
assert_eq!(area, (parent.size() * parent.size() - 256) as f32);
|
||||
}
|
||||
/// A neighbour that is still to come draws no wall against it; an empty
|
||||
/// one (out of range) does.
|
||||
#[test]
|
||||
fn unknown_neighbor_leaves_its_side_out() {
|
||||
let key = TileKey {
|
||||
x: 0,
|
||||
z: 0,
|
||||
level: 0,
|
||||
};
|
||||
let columns = vec![
|
||||
Column {
|
||||
surface: None,
|
||||
spans: vec![Span {
|
||||
min: 0,
|
||||
max: 8,
|
||||
state: BlockStateId(1),
|
||||
biome: 0,
|
||||
light: 240,
|
||||
}]
|
||||
.into()
|
||||
};
|
||||
256
|
||||
];
|
||||
let columns: Vec<&Column> = columns.iter().collect();
|
||||
let empty = Column::default();
|
||||
let east_quads = |unknown: bool| {
|
||||
let edge = |column| EdgeColumn {
|
||||
start: 0,
|
||||
end: 16,
|
||||
column,
|
||||
unknown,
|
||||
};
|
||||
let neighbors = [Vec::new(), vec![edge(&empty)], Vec::new(), Vec::new()];
|
||||
let mesh = mesh_columns_with_edges(
|
||||
key,
|
||||
&columns,
|
||||
&FxHashSet::default(),
|
||||
None,
|
||||
&neighbors,
|
||||
&[],
|
||||
|_, _, _| Material {
|
||||
faces: [
|
||||
Some(Paint {
|
||||
layer: 1,
|
||||
light: 240,
|
||||
color: u32::MAX,
|
||||
translucent: false,
|
||||
}),
|
||||
None,
|
||||
],
|
||||
fluid: 0,
|
||||
opaque: true,
|
||||
},
|
||||
);
|
||||
mesh.solid
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.filter(|quad| quad.iter().all(|v| v.pos[0] == 16.0))
|
||||
.count()
|
||||
};
|
||||
assert!(east_quads(false) > 0, "a wall faces the empty neighbour");
|
||||
assert_eq!(east_quads(true), 0, "none faces the unknown one");
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile's columns with the gaps no open air reaches filled with the
|
||||
@@ -1442,6 +1518,7 @@ mod tests {
|
||||
start: 0,
|
||||
end: CELLS as i32,
|
||||
column: &hill_column,
|
||||
unknown: false,
|
||||
};
|
||||
let neighbors = [vec![hill()], vec![hill()], vec![hill()], vec![hill()]];
|
||||
let fill = |columns: &[Column]| -> Vec<Vec<Span>> {
|
||||
@@ -1583,6 +1660,7 @@ mod tests {
|
||||
start: 0,
|
||||
end: 32,
|
||||
column: &columns[0],
|
||||
unknown: false,
|
||||
}]
|
||||
});
|
||||
// One fine column is lower than both the coarse cell and its sibling.
|
||||
@@ -1596,16 +1674,19 @@ mod tests {
|
||||
start: 0,
|
||||
end: 1,
|
||||
column: &columns[0],
|
||||
unknown: false,
|
||||
},
|
||||
EdgeColumn {
|
||||
start: 1,
|
||||
end: 2,
|
||||
column: &low,
|
||||
unknown: false,
|
||||
},
|
||||
EdgeColumn {
|
||||
start: 2,
|
||||
end: 32,
|
||||
column: &columns[0],
|
||||
unknown: false,
|
||||
},
|
||||
];
|
||||
let columns: Vec<&Column> = columns.iter().collect();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
mod fluid;
|
||||
mod leaf_culling;
|
||||
mod lighting;
|
||||
pub mod packed;
|
||||
mod sides;
|
||||
use crate::connected::{Pick, TileLayer};
|
||||
use crate::{
|
||||
@@ -265,6 +266,12 @@ pub struct SectionMesh {
|
||||
/// model order before.
|
||||
pub sides: Sides,
|
||||
pub translucent: Vec<TerrainVertex>,
|
||||
/// `solid` in the compact layout (`packed`), when the renderer reads it and it is
|
||||
/// smaller; empty otherwise. Made by `sort_solid`.
|
||||
pub packed: Vec<[u32; 6]>,
|
||||
/// The tint of each solid quad as the mesher multiplied it (`packed::UNKNOWN_TINT` where
|
||||
/// it does not say), for `packed`; dropped once that is made.
|
||||
pub(crate) solid_tints: Vec<u32>,
|
||||
/// VisGraph: bit `a * 6 + b` is set when faces `a` and `b` (Direction
|
||||
/// order) are connected through non-opaque blocks.
|
||||
pub visibility: u64,
|
||||
@@ -290,6 +297,8 @@ impl Default for SectionMesh {
|
||||
solid: Vec::new(),
|
||||
sides: Sides::default(),
|
||||
translucent: Vec::new(),
|
||||
packed: Vec::new(),
|
||||
solid_tints: Vec::new(),
|
||||
visibility: ALL_VISIBLE,
|
||||
block_entities: Vec::new(),
|
||||
lights: Vec::new(),
|
||||
@@ -322,6 +331,8 @@ impl SectionMesh {
|
||||
pub fn clear(&mut self) {
|
||||
self.solid.clear();
|
||||
self.translucent.clear();
|
||||
self.packed.clear();
|
||||
self.solid_tints.clear();
|
||||
self.block_entities.clear();
|
||||
self.lights.clear();
|
||||
self.sprite_layers.clear();
|
||||
@@ -333,15 +344,27 @@ impl SectionMesh {
|
||||
pub(crate) fn capacity_bytes(&self) -> usize {
|
||||
self.solid.capacity() * std::mem::size_of::<TerrainVertex>()
|
||||
+ self.translucent.capacity() * std::mem::size_of::<TerrainVertex>()
|
||||
+ self.packed.capacity() * std::mem::size_of::<[u32; 6]>()
|
||||
+ self.solid_tints.capacity() * std::mem::size_of::<u32>()
|
||||
+ self.block_entities.capacity() * std::mem::size_of::<([i32; 3], u32)>()
|
||||
+ self.lights.capacity() * std::mem::size_of::<([i32; 3], u32)>()
|
||||
+ self.sprite_layers.capacity() * std::mem::size_of::<u16>()
|
||||
}
|
||||
/// Sorts the solid quads by the side they face, for the renderer to
|
||||
/// leave out those facing away from the eye (`sides`).
|
||||
/// Then lays them out compactly (`packed`) while the renderer reads that.
|
||||
pub fn sort_solid(&mut self) {
|
||||
self.sides = sides::sort(&mut self.solid);
|
||||
let quads = self.solid.len() / 4;
|
||||
if !self.solid_tints.is_empty() {
|
||||
self.solid_tints.resize(quads, packed::UNKNOWN_TINT);
|
||||
}
|
||||
self.sides = sides::sort_tinted(&mut self.solid, &mut self.solid_tints);
|
||||
self.bounds = self.compute_bounds();
|
||||
self.packed.clear();
|
||||
if packed::packing() && quads > 0 && self.solid_tints.len() == quads {
|
||||
self.packed = packed::layout(&self.solid, &self.solid_tints);
|
||||
}
|
||||
self.solid_tints.clear();
|
||||
}
|
||||
|
||||
/// Computes the tight axis-aligned bounding box [min, max] of actual terrain geometry vertices.
|
||||
@@ -1519,12 +1542,24 @@ impl Mesher<'_> {
|
||||
let other = &info[neighbor as usize];
|
||||
// LeavesBlock.skipRendering with opaque leaves.
|
||||
let opaque_leaves = !self.cutout_leaves && state.leaves && other.leaves;
|
||||
let hollow_leaf = self.leaf_culling == LeafCullingMode::Hollow
|
||||
&& state.leaves
|
||||
&& other.leaves
|
||||
&& leaf_culling::in_section(x + dx, y + dy, z + dz);
|
||||
let culled_leaf = match self.leaf_culling {
|
||||
LeafCullingMode::Hollow | LeafCullingMode::Hidden => {
|
||||
state.leaves
|
||||
&& other.leaves
|
||||
&& leaf_culling::in_section(x + dx, y + dy, z + dz)
|
||||
&& (self.leaf_culling == LeafCullingMode::Hollow
|
||||
|| leaf_culling::backed(
|
||||
pad,
|
||||
info,
|
||||
region,
|
||||
base_y,
|
||||
[x + 2 * dx, y + 2 * dy, z + 2 * dz],
|
||||
))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
visible[d as usize] = !opaque_leaves
|
||||
&& !hollow_leaf
|
||||
&& !culled_leaf
|
||||
&& self
|
||||
.visibility
|
||||
.should_render_face(
|
||||
@@ -1770,8 +1805,8 @@ impl Mesher<'_> {
|
||||
TileLayer::Skip | TileLayer::Default => own,
|
||||
};
|
||||
match pick {
|
||||
Pick::Keep => self.quad(out, solid_leaves, own, corners.map(|c| c.0)),
|
||||
Pick::Tile(t) => self.quad(out, solid_leaves, tile(t), corners.map(|c| c.0)),
|
||||
Pick::Keep => self.quad(out, solid_leaves, own, corners.map(|c| c.0), tint),
|
||||
Pick::Tile(t) => self.quad(out, solid_leaves, tile(t), corners.map(|c| c.0), tint),
|
||||
Pick::Quarters(tiles) => {
|
||||
let cuts = cuts(&corners);
|
||||
for s in cuts.windows(2) {
|
||||
@@ -1779,7 +1814,8 @@ impl Mesher<'_> {
|
||||
let piece = [(s[0], t[0]), (s[1], t[0]), (s[1], t[1]), (s[0], t[1])]
|
||||
.map(|(s, t)| bilinear(&corners, s, t));
|
||||
let quarter = quarter_of(&piece);
|
||||
self.quad(out, solid_leaves, tile(tiles[quarter]), piece.map(|c| c.0));
|
||||
let piece = piece.map(|c| c.0);
|
||||
self.quad(out, solid_leaves, tile(tiles[quarter]), piece, tint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1823,7 +1859,7 @@ impl Mesher<'_> {
|
||||
for t in cuts.windows(2) {
|
||||
let piece = [(s[0], t[0]), (s[1], t[0]), (s[1], t[1]), (s[0], t[1])]
|
||||
.map(|(s, t)| bilinear(&whole, s, t).0);
|
||||
self.quad_as(out, false, tile, piece, false);
|
||||
self.quad_as(out, false, tile, piece, false, tint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1836,8 +1872,9 @@ impl Mesher<'_> {
|
||||
solid_leaves: bool,
|
||||
tile: TileLayer,
|
||||
v: [TerrainVertex; 4],
|
||||
tint: Option<u32>,
|
||||
) {
|
||||
self.quad_as(out, solid_leaves, tile, v, true);
|
||||
self.quad_as(out, solid_leaves, tile, v, true, tint);
|
||||
}
|
||||
/// Whether [`Mesher::quad`] draws `tile` in the translucent pass.
|
||||
fn translucent(&self, tile: TileLayer) -> bool {
|
||||
@@ -1856,7 +1893,8 @@ impl Mesher<'_> {
|
||||
}
|
||||
}
|
||||
/// [`Mesher::quad`], with the sprite's own translucency (`translucent_layers`) counted only
|
||||
/// when `by_sprite`: an overlay's pass is its rule's.
|
||||
/// when `by_sprite`: an overlay's pass is its rule's. `tint`: what its grey levels were
|
||||
/// multiplied by, for the compact layout (`packed`).
|
||||
fn quad_as(
|
||||
&self,
|
||||
out: &mut SectionMesh,
|
||||
@@ -1864,6 +1902,7 @@ impl Mesher<'_> {
|
||||
tile: TileLayer,
|
||||
mut v: [TerrainVertex; 4],
|
||||
by_sprite: bool,
|
||||
tint: Option<u32>,
|
||||
) {
|
||||
let TileLayer::Layer {
|
||||
layer,
|
||||
@@ -1897,6 +1936,12 @@ impl Mesher<'_> {
|
||||
vertex.layer = layer;
|
||||
}
|
||||
target.extend(v);
|
||||
if !translucent {
|
||||
// Quads other code put in `solid` (lava) have no tint known.
|
||||
let quad = out.solid.len() / 4 - 1;
|
||||
out.solid_tints.resize(quad, packed::UNKNOWN_TINT);
|
||||
out.solid_tints.push(tint.unwrap_or(packed::WHITE));
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The cuts of a `ctm_compact` face, in quad parameters, without allocating: the first `.1`
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
//! Aggressive` runs a multi-source breadth-first search from leaves touching
|
||||
//! openings, solidifying leaves with a single open face to thin the canopy shell
|
||||
//! by one layer. `Hollow` directly drops faces shared between two leaves within
|
||||
//! the section, preserving boundaries and cavity faces.
|
||||
//! the section, preserving boundaries and cavity faces. `Hidden` drops only
|
||||
//! those whose faced leaf has another leaf or an opaque cube behind it
|
||||
//! ([`backed`]), so the layer seen through the canopy's outer holes stays.
|
||||
|
||||
use super::{B, Info, P, Padded, pi};
|
||||
use crate::{blocks::RenderShape, chunk::Chunk, graphics::LeafCullingMode};
|
||||
@@ -229,6 +231,21 @@ pub(super) fn in_section(x: i32, y: i32, z: i32) -> bool {
|
||||
(0..16).contains(&x) && (0..16).contains(&y) && (0..16).contains(&z)
|
||||
}
|
||||
|
||||
/// `Hidden`: whether the padded cell `[x, y, z]`, the one behind a faced leaf,
|
||||
/// is a leaf or a full opaque cube. A cell without real data never is.
|
||||
#[inline]
|
||||
pub(super) fn backed(
|
||||
pad: &Padded,
|
||||
info: &[Info],
|
||||
region: &[[Option<&Chunk>; 3]; 3],
|
||||
base_y: i32,
|
||||
[x, y, z]: [i32; 3],
|
||||
) -> bool {
|
||||
info.get(pad.blocks[pi(x, y, z)] as usize)
|
||||
.is_some_and(|state| state.leaves || state.opaque_cube)
|
||||
&& known(region, base_y, x, y, z)
|
||||
}
|
||||
|
||||
/// Whether the padded cell `(x, y, z)` has real block data. A missing chunk
|
||||
/// does not count: `Padded::fill` copies the centre's edge column into it, which
|
||||
/// would make a canopy look enclosed. A Y outside the column counts as unknown
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//! The compact layout of a section's solid quads in the arena: most of them are whole faces
|
||||
//! of a block's cube on the block grid, and those take one 24-byte record each instead of
|
||||
//! four 24-byte vertices. terrain.wgsl's `section_corner` expands a record into the very
|
||||
//! vertex words `TerrainVertex` would hold (`packed_vertex` here is the same on the CPU, and
|
||||
//! a record is kept only when it gives back the quad's four vertices bit for bit), so every
|
||||
//! pass draws the same picture from either layout.
|
||||
//!
|
||||
//! A mesh's solid range is then one record per quad in mesh order (so `Sides` and the draws
|
||||
//! count quads as before), padding to a multiple of 4 slots, and the quads no record fits as
|
||||
//! their four vertices: their record holds the distance to those (`FULL`). A record:
|
||||
//!
|
||||
//! - word 0: the tint (0xRRGGBB as the mesher multiplies it) and the colour's alpha byte;
|
||||
//! - word 1: each corner's grey level before the tint;
|
||||
//! - word 2: the UV rectangle in 1/32 of the sprite (u0, u1, v0, v1, 6 bits each) and, per
|
||||
//! corner, which edge of it its u and v take;
|
||||
//! - word 3: the layer, and corner 0's light (sky and block light over 4, 6 bits each);
|
||||
//! - word 4: corners 1 and 2's light;
|
||||
//! - word 5: corner 3's light, the face (block x, y, z, the axis it faces along, which side
|
||||
//! of the block, and where and which way its corners go round), and `FULL` clear.
|
||||
use super::TerrainVertex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
/// A solid quad's tint the mesher does not know (fluids): only untinted colours pack.
|
||||
pub const UNKNOWN_TINT: u32 = u32::MAX;
|
||||
/// The tint of an untinted quad, whose colour is its grey level.
|
||||
pub const WHITE: u32 = 0xFF_FFFF;
|
||||
/// Word 5's bit of a quad kept as four vertices; word 0 is then the slots from the record
|
||||
/// to its first vertex.
|
||||
pub const FULL: u32 = 1 << 31;
|
||||
/// The draws' base vertex of a compact mesh: `PACKED_BASE + 4 * slot + corner` is a corner of
|
||||
/// the quad whose record is at `slot`; below it, vertex indices name vertices.
|
||||
pub const PACKED_BASE: u32 = 1 << 28;
|
||||
|
||||
/// Whether meshes sorted from now on get the compact layout: set by the renderer while its
|
||||
/// passes read it (no shader pack).
|
||||
static PACK: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn pack_quads(on: bool) {
|
||||
PACK.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn packing() -> bool {
|
||||
PACK.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// The order corners go round a face: (du, dw) of each step.
|
||||
const ROUND: [[u32; 2]; 4] = [[0, 0], [1, 0], [1, 1], [0, 1]];
|
||||
|
||||
/// `quad`'s record, if one gives back its four vertices exactly. `tint`: what the mesher
|
||||
/// multiplied its grey levels by (`UNKNOWN_TINT` when it does not say).
|
||||
pub fn pack_quad(quad: &[TerrainVertex; 4], tint: u32) -> Option<[u32; 6]> {
|
||||
// Corners on the block grid of the section.
|
||||
let mut p = [[0u32; 3]; 4];
|
||||
for (to, v) in p.iter_mut().zip(quad) {
|
||||
for (c, &x) in to.iter_mut().zip(&v.pos) {
|
||||
if !(0.0..=16.0).contains(&x) || x.fract() != 0.0 {
|
||||
return None;
|
||||
}
|
||||
*c = x as u32;
|
||||
}
|
||||
}
|
||||
let axis = (0..3).find(|&a| p.iter().all(|c| c[a] == p[0][a]))?;
|
||||
let (u, w) = ((axis + 1) % 3, (axis + 2) % 3);
|
||||
let lo = [u, w].map(|k| p.iter().map(|c| c[k]).min().unwrap_or(0));
|
||||
if lo.iter().any(|&l| l > 15) {
|
||||
return None;
|
||||
}
|
||||
let mut steps = [0u32; 4];
|
||||
for (step, c) in steps.iter_mut().zip(&p) {
|
||||
let d = [c[u] - lo[0], c[w] - lo[1]];
|
||||
*step = ROUND.iter().position(|r| *r == d)? as u32;
|
||||
}
|
||||
let (start, dir) = (0..8u32).map(|n| (n & 3, n >> 2)).find(|&(start, dir)| {
|
||||
(0..4u32).all(|i| {
|
||||
let step = if dir == 1 { 4 - i } else { i };
|
||||
steps[i as usize] == (start + step) & 3
|
||||
})
|
||||
})?;
|
||||
let plane = p[0][axis];
|
||||
let mut block = [0u32; 3];
|
||||
block[axis] = plane.min(15);
|
||||
block[u] = lo[0];
|
||||
block[w] = lo[1];
|
||||
let side = plane - block[axis];
|
||||
let orient = block[0]
|
||||
| block[1] << 4
|
||||
| block[2] << 8
|
||||
| (axis as u32) << 12
|
||||
| side << 14
|
||||
| start << 15
|
||||
| dir << 17;
|
||||
// UVs in 1/32 of the sprite, at most two values each way.
|
||||
let mut ranges = [[u32::MAX, 0u32]; 2];
|
||||
let mut uvs = [[0u32; 2]; 4];
|
||||
for (uv, v) in uvs.iter_mut().zip(quad) {
|
||||
for k in 0..2 {
|
||||
let raw = u32::from(v.uv[k]);
|
||||
if raw & 1023 != 0 {
|
||||
return None;
|
||||
}
|
||||
uv[k] = raw >> 10;
|
||||
ranges[k] = [ranges[k][0].min(uv[k]), ranges[k][1].max(uv[k])];
|
||||
}
|
||||
}
|
||||
let mut select = 0u32;
|
||||
for (i, uv) in uvs.iter().enumerate() {
|
||||
for k in 0..2 {
|
||||
if uv[k] == ranges[k][1] && uv[k] != ranges[k][0] {
|
||||
select |= 1 << (2 * i + k);
|
||||
} else if uv[k] != ranges[k][0] {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let rect = ranges[0][0] | ranges[0][1] << 6 | ranges[1][0] << 12 | ranges[1][1] << 18;
|
||||
// Light in steps of 4 (smooth light averages four levels of 16).
|
||||
let mut light = [0u32; 4];
|
||||
for (l, v) in light.iter_mut().zip(quad) {
|
||||
let (sky, block) = (u32::from(v.light & 0xFF), u32::from(v.light >> 8));
|
||||
if sky & 3 != 0 || block & 3 != 0 {
|
||||
return None;
|
||||
}
|
||||
*l = sky >> 2 | (block >> 2) << 6;
|
||||
}
|
||||
// The colour: each channel the grey level times the tint's, over 255.
|
||||
let tint = if tint == UNKNOWN_TINT {
|
||||
WHITE
|
||||
} else {
|
||||
tint & WHITE
|
||||
};
|
||||
let alpha = quad[0].color & 0xFF00_0000;
|
||||
let mut grays = 0u32;
|
||||
for (i, v) in quad.iter().enumerate() {
|
||||
grays |= gray_of(v.color, tint)? << (8 * i);
|
||||
}
|
||||
let words = [
|
||||
tint | alpha,
|
||||
grays,
|
||||
rect | select << 24,
|
||||
u32::from(quad[0].layer) | light[0] << 16,
|
||||
light[1] | light[2] << 12,
|
||||
light[3] | orient << 12,
|
||||
];
|
||||
let exact = (0..4).all(|c| {
|
||||
let (a, b) = (packed_vertex(&words, c), quad[c]);
|
||||
bytemuck::bytes_of(&a) == bytemuck::bytes_of(&b)
|
||||
});
|
||||
exact.then_some(words)
|
||||
}
|
||||
|
||||
/// The tint's channels, as the mesher takes them for red, green and blue.
|
||||
fn channels(tint: u32) -> [u32; 3] {
|
||||
[tint >> 16 & 255, tint >> 8 & 255, tint & 255]
|
||||
}
|
||||
|
||||
/// A grey level that `tint` turns into `color`'s red, green and blue.
|
||||
fn gray_of(color: u32, tint: u32) -> Option<u32> {
|
||||
let want = [color & 255, color >> 8 & 255, color >> 16 & 255];
|
||||
let t = channels(tint);
|
||||
let gives = |g: u32| (0..3).all(|k| g * t[k] / 255 == want[k]);
|
||||
let k = (0..3).max_by_key(|&k| t[k]).unwrap_or(0);
|
||||
if t[k] == 0 {
|
||||
return gives(0).then_some(0);
|
||||
}
|
||||
let lo = (want[k] * 255).div_ceil(t[k]);
|
||||
let hi = ((want[k] * 255 + 254) / t[k]).min(255);
|
||||
(lo..=hi).find(|&g| gives(g))
|
||||
}
|
||||
|
||||
/// Corner `corner` of the quad `words` holds, as terrain.wgsl's `packed_corner` expands it.
|
||||
pub fn packed_vertex(words: &[u32; 6], corner: usize) -> TerrainVertex {
|
||||
let [w0, w1, w2, w3, w4, w5] = *words;
|
||||
let orient = w5 >> 12 & 0x3_FFFF;
|
||||
let block = [orient & 15, orient >> 4 & 15, orient >> 8 & 15];
|
||||
let axis = (orient >> 12 & 3) as usize;
|
||||
let side = orient >> 14 & 1;
|
||||
let (start, dir) = (orient >> 15 & 3, orient >> 17 & 1);
|
||||
let i = corner as u32;
|
||||
let step = (start + if dir == 1 { 4 - i } else { i }) & 3;
|
||||
let [du, dw] = ROUND[step as usize];
|
||||
let mut pos = block;
|
||||
pos[axis] += side;
|
||||
pos[(axis + 1) % 3] += du;
|
||||
pos[(axis + 2) % 3] += dw;
|
||||
let select = w2 >> 24;
|
||||
let us = select >> (2 * i) & 1;
|
||||
let vs = select >> (2 * i + 1) & 1;
|
||||
let uv = [w2 >> (6 * us) & 63, w2 >> (12 + 6 * vs) & 63].map(|k| (k << 10) as u16);
|
||||
let light = [w3 >> 16, w4, w4 >> 12, w5][corner] & 0xFFF;
|
||||
let light = ((light & 63) << 2 | (light >> 6 & 63) << 10) as u16;
|
||||
let g = w1 >> (8 * i) & 255;
|
||||
let [r, gr, b] = channels(w0).map(|t| g * t / 255);
|
||||
TerrainVertex {
|
||||
pos: pos.map(|c| c as f32),
|
||||
uv,
|
||||
layer: (w3 & 0xFFFF) as u16,
|
||||
light,
|
||||
color: r | gr << 8 | b << 16 | (w0 & 0xFF00_0000),
|
||||
}
|
||||
}
|
||||
|
||||
/// The compact layout of sorted solid quads with their tints (`SectionMesh::packed`), or
|
||||
/// nothing when it would be no smaller than their vertices.
|
||||
pub fn layout(solid: &[TerrainVertex], tints: &[u32]) -> Vec<[u32; 6]> {
|
||||
let quads = solid.as_chunks::<4>().0;
|
||||
let records: Vec<Option<[u32; 6]>> = quads
|
||||
.iter()
|
||||
.zip(tints)
|
||||
.map(|(q, &t)| pack_quad(q, t))
|
||||
.collect();
|
||||
let n = quads.len();
|
||||
let packed = records.iter().filter(|r| r.is_some()).count();
|
||||
let full = n - packed;
|
||||
let pad = if full > 0 {
|
||||
n.next_multiple_of(4) - n
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if n + pad + 4 * full >= 4 * n {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut out = Vec::with_capacity(n + pad + 4 * full);
|
||||
let mut next = n + pad;
|
||||
for (i, record) in records.iter().enumerate() {
|
||||
out.push(record.unwrap_or_else(|| {
|
||||
let at = next;
|
||||
next += 4;
|
||||
[(at - i) as u32, 0, 0, 0, 0, FULL]
|
||||
}));
|
||||
}
|
||||
out.resize(n + pad, [0; 6]);
|
||||
for (q, record) in quads.iter().zip(&records) {
|
||||
if record.is_none() {
|
||||
out.extend(
|
||||
q.iter()
|
||||
.map(|v| bytemuck::cast::<TerrainVertex, [u32; 6]>(*v)),
|
||||
);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Quads `layout` gave a record of their own.
|
||||
pub fn packed_quads(layout: &[[u32; 6]], quads: usize) -> usize {
|
||||
layout
|
||||
.iter()
|
||||
.take(quads)
|
||||
.filter(|w| w[5] & FULL == 0)
|
||||
.count()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const TINT: u32 = 0x7F_B238;
|
||||
|
||||
/// An opaque colour the mesher makes of grey level `g` times `tint`.
|
||||
fn tinted(g: u32, tint: u32) -> u32 {
|
||||
let [r, gr, b] = channels(tint).map(|t| g * t / 255);
|
||||
r | gr << 8 | b << 16 | 0xFF00_0000
|
||||
}
|
||||
|
||||
/// The top face of block (3, 15, 5), corners in `order`: tinted, smooth lit, with
|
||||
/// ambient occlusion darkening its corners differently.
|
||||
fn top(order: [usize; 4]) -> [TerrainVertex; 4] {
|
||||
let pos = [
|
||||
[3.0, 16.0, 5.0],
|
||||
[3.0, 16.0, 6.0],
|
||||
[4.0, 16.0, 6.0],
|
||||
[4.0, 16.0, 5.0],
|
||||
];
|
||||
let uv = [[0, 0], [16 << 10, 0], [16 << 10, 16 << 10], [0, 16 << 10]];
|
||||
let grays = [200, 180, 160, 200];
|
||||
order.map(|i| TerrainVertex {
|
||||
pos: pos[i],
|
||||
uv: uv[i],
|
||||
layer: 7,
|
||||
light: 0xF0 | 0x40 << 8,
|
||||
color: tinted(grays[i], TINT),
|
||||
})
|
||||
}
|
||||
|
||||
fn same(a: &TerrainVertex, b: &TerrainVertex) -> bool {
|
||||
bytemuck::bytes_of(a) == bytemuck::bytes_of(b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_whole_cube_face_takes_one_record_either_way_round() {
|
||||
for order in [[0, 1, 2, 3], [2, 3, 0, 1], [0, 3, 2, 1]] {
|
||||
let quad = top(order);
|
||||
let words = pack_quad(&quad, TINT).expect("a whole face on the block grid packs");
|
||||
for (corner, v) in quad.iter().enumerate() {
|
||||
assert!(
|
||||
same(&packed_vertex(&words, corner), v),
|
||||
"{order:?} corner {corner}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quads_off_the_grid_or_the_steps_keep_their_vertices() {
|
||||
// Half a block up: a slab's top.
|
||||
let mut slab = top([0, 1, 2, 3]);
|
||||
for v in &mut slab {
|
||||
v.pos[1] = 8.5;
|
||||
}
|
||||
assert!(pack_quad(&slab, TINT).is_none());
|
||||
// A UV between the 1/32 steps.
|
||||
let mut uv = top([0, 1, 2, 3]);
|
||||
uv[2].uv[0] += 1;
|
||||
assert!(pack_quad(&uv, TINT).is_none());
|
||||
// Light between the steps of 4.
|
||||
let mut light = top([0, 1, 2, 3]);
|
||||
light[1].light += 1;
|
||||
assert!(pack_quad(&light, TINT).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_quad_without_a_record_points_at_its_own_vertices() {
|
||||
let mut odd = top([0, 1, 2, 3]);
|
||||
for v in &mut odd {
|
||||
v.pos[1] = 8.5;
|
||||
}
|
||||
let mut solid: Vec<TerrainVertex> = Vec::new();
|
||||
for _ in 0..4 {
|
||||
solid.extend(top([0, 1, 2, 3]));
|
||||
}
|
||||
solid.extend(odd);
|
||||
let out = layout(&solid, &[TINT; 5]);
|
||||
// Five records, padded to eight slots, then the odd quad's four vertices.
|
||||
assert_eq!(out.len(), 12);
|
||||
assert_eq!(packed_quads(&out, 5), 4);
|
||||
assert_eq!(out[4][5], FULL);
|
||||
let at = 4 + out[4][0] as usize;
|
||||
for (slot, v) in out[at..at + 4].iter().zip(&odd) {
|
||||
assert!(same(&bytemuck::cast::<[u32; 6], TerrainVertex>(*slot), v));
|
||||
}
|
||||
// Nothing smaller to gain: no layout at all.
|
||||
assert!(layout(&odd, &[TINT]).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ fn slot(grouped: bool, n: usize) -> (usize, bool) {
|
||||
|
||||
/// Where each part of each bucket of a mesh's solid quads ends, and the
|
||||
/// planes that tell whether a bucket can face the eye.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Sides {
|
||||
/// The solid quads up to the end of each part of each bucket, in mesh
|
||||
/// order (`slot`).
|
||||
@@ -205,13 +205,21 @@ fn bucket(quad: &[TerrainVertex; 4]) -> usize {
|
||||
/// cutout quads are also ordered front to back within each axis-facing
|
||||
/// bucket; quads that tie keep the mesher's order (the quad's index ends its
|
||||
/// sort key), so coplanar overlays still draw over their base.
|
||||
pub fn sort(solid: &mut Vec<TerrainVertex>) -> Sides {
|
||||
sort_parts(solid, GROUPED.load(Ordering::Relaxed))
|
||||
#[cfg(test)]
|
||||
fn sort(solid: &mut Vec<TerrainVertex>) -> Sides {
|
||||
sort_tinted(solid, &mut Vec::new())
|
||||
}
|
||||
|
||||
/// `sort`, moving each quad's tint in `tints` (one per quad, or none) with it.
|
||||
pub fn sort_tinted(solid: &mut Vec<TerrainVertex>, tints: &mut Vec<u32>) -> Sides {
|
||||
let grouped = GROUPED.load(Ordering::Relaxed);
|
||||
SORT_SCRATCH.with_borrow_mut(|scratch| sort_with(solid, tints, grouped, scratch))
|
||||
}
|
||||
|
||||
/// `sort`, with all the opaque parts first when `grouped`.
|
||||
#[cfg(test)]
|
||||
fn sort_parts(solid: &mut Vec<TerrainVertex>, grouped: bool) -> Sides {
|
||||
SORT_SCRATCH.with_borrow_mut(|scratch| sort_with(solid, grouped, scratch))
|
||||
SORT_SCRATCH.with_borrow_mut(|scratch| sort_with(solid, &mut Vec::new(), grouped, scratch))
|
||||
}
|
||||
|
||||
/// `sort`'s buffers, kept by each meshing thread so sorting a mesh allocates
|
||||
@@ -222,6 +230,7 @@ struct SortScratch {
|
||||
slots: Vec<u8>,
|
||||
keys: Vec<u64>,
|
||||
sorted: Vec<[TerrainVertex; 4]>,
|
||||
tints: Vec<u32>,
|
||||
}
|
||||
thread_local! {
|
||||
static SORT_SCRATCH: std::cell::RefCell<SortScratch> = Default::default();
|
||||
@@ -233,9 +242,15 @@ fn total_order_key(v: f32) -> u32 {
|
||||
(bits ^ (((bits >> 31) as u32) >> 1) as i32) as u32 ^ 0x8000_0000
|
||||
}
|
||||
|
||||
fn sort_with(solid: &mut Vec<TerrainVertex>, grouped: bool, scratch: &mut SortScratch) -> Sides {
|
||||
fn sort_with(
|
||||
solid: &mut Vec<TerrainVertex>,
|
||||
tints: &mut Vec<u32>,
|
||||
grouped: bool,
|
||||
scratch: &mut SortScratch,
|
||||
) -> Sides {
|
||||
let (quads, rest) = solid.as_chunks::<4>();
|
||||
debug_assert!(rest.is_empty(), "solid vertices come in quads");
|
||||
let quads_len = quads.len();
|
||||
let cutout = |q: &[TerrainVertex; 4]| q[0].layer & OPAQUE_LAYER == 0;
|
||||
// Each quad's part, by its place in mesh order (`slot`).
|
||||
let position = |b: usize, cutout: bool| match grouped {
|
||||
@@ -297,6 +312,12 @@ fn sort_with(solid: &mut Vec<TerrainVertex>, grouped: bool, scratch: &mut SortSc
|
||||
sorted.extend(keys.iter().map(|&k| quads[(k & 0xFF_FFFF) as usize]));
|
||||
solid.clear();
|
||||
solid.extend_from_slice(sorted.as_flattened());
|
||||
if tints.len() == quads_len {
|
||||
let sorted_tints = &mut scratch.tints;
|
||||
sorted_tints.clear();
|
||||
sorted_tints.extend(keys.iter().map(|&k| tints[(k & 0xFF_FFFF) as usize]));
|
||||
std::mem::swap(tints, sorted_tints);
|
||||
}
|
||||
}
|
||||
Sides {
|
||||
ends: slot_ends,
|
||||
|
||||
@@ -99,9 +99,14 @@ pub(crate) fn prepare_with_models(
|
||||
.filter(|name| registry.default_state(name).is_some())
|
||||
.map(|name| name.strip_prefix("minecraft:").unwrap_or(name))
|
||||
.collect();
|
||||
let mut catalog =
|
||||
StateModels::from_facts_with_models(root, registry, &blocks, sprites, &facts, models)?;
|
||||
catalog.bake_standalone(root, sprites, models, standalone())?;
|
||||
let mut catalog = {
|
||||
let _step = crate::spans::startup::step("core.bake_states");
|
||||
StateModels::from_facts_with_models(root, registry, &blocks, sprites, &facts, models)?
|
||||
};
|
||||
{
|
||||
let _step = crate::spans::startup::step("core.bake_standalone");
|
||||
catalog.bake_standalone(root, sprites, models, standalone())?;
|
||||
}
|
||||
let mut placements = Vec::new();
|
||||
let mut states = Vec::new();
|
||||
for (index, (name, properties)) in GALLERY.iter().enumerate() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// the depth bounds stage, which sees the unfaded alpha.
|
||||
fn oit_cloud_color(in: Out, front: bool, fade: bool) -> vec4<f32> {
|
||||
let color = textureSample(atlas, atlas_sampler, in.uv) * select(in.back_color, in.color, front);
|
||||
let faded = 1.0 - linear_fog(in.spherical, 0.0, globals.fog_ends.y);
|
||||
let faded = 1.0 - linear_fog(in.fog_distance.x, 0.0, globals.fog_ends.y);
|
||||
return vec4(color.rgb, color.a * select(1.0, faded, fade));
|
||||
}
|
||||
@fragment fn fs_oit_cloud_bounds(in: Out, @builtin(front_facing) front: bool) -> @location(0) vec4<f32> {
|
||||
|
||||
@@ -263,6 +263,9 @@ static TABLE: LazyLock<Vec<Def>> = LazyLock::new(|| {
|
||||
for part in MODEL_PARTS {
|
||||
t.push(pb(&format!("modelPart_{part}"), true));
|
||||
}
|
||||
// Horizon's own: the outer skin layer drawn as slabs (Options → Skin
|
||||
// Customization), read by the renderer when a skin is resolved.
|
||||
t.push(pb("threeDSkinLayers", false));
|
||||
t
|
||||
});
|
||||
|
||||
|
||||
@@ -289,6 +289,7 @@ impl Opt {
|
||||
"options.modelPart.right_pants_leg",
|
||||
"options.modelPart.hat",
|
||||
][i],
|
||||
Opt::SkinLayers3d => "horizon.options.three_d_skin_layers",
|
||||
Opt::MainHand => "options.mainHand",
|
||||
Opt::AllowServerListing => "options.allowServerListing",
|
||||
Opt::RealmsNotifications => "options.realmsNotifications.button",
|
||||
@@ -729,6 +730,11 @@ impl Opt {
|
||||
} else {
|
||||
"options.key.hold"
|
||||
}),
|
||||
Opt::SkinLayers3d => tr(if s.three_d_skin_layers {
|
||||
"options.on"
|
||||
} else {
|
||||
"options.off"
|
||||
}),
|
||||
Opt::ToggleAttack => tr(if s.toggle_attack {
|
||||
"options.key.toggle"
|
||||
} else {
|
||||
@@ -1013,6 +1019,7 @@ impl Opt {
|
||||
Opt::ChunkBuilder => s.prioritize_chunk_updates = next(s.prioritize_chunk_updates, 3),
|
||||
Opt::ToggleCrouch => s.toggle_crouch = !s.toggle_crouch,
|
||||
Opt::ToggleSprint => s.toggle_sprint = !s.toggle_sprint,
|
||||
Opt::SkinLayers3d => s.three_d_skin_layers = !s.three_d_skin_layers,
|
||||
Opt::ToggleAttack => s.toggle_attack = !s.toggle_attack,
|
||||
Opt::ToggleUse => s.toggle_use = !s.toggle_use,
|
||||
Opt::AutoJump => s.auto_jump = !s.auto_jump,
|
||||
@@ -2612,6 +2619,8 @@ impl OptionsUi {
|
||||
Page::Skin => {
|
||||
// SkinCustomizationScreen: the seven layers, then Main Hand.
|
||||
let mut opts: Vec<Opt> = (0..MODEL_PART_KEYS.len()).map(Opt::ModelPart).collect();
|
||||
// Horizon's own: the outer layer as real pixels, not one box.
|
||||
opts.push(Opt::SkinLayers3d);
|
||||
opts.push(Opt::MainHand);
|
||||
pairs(&opts)
|
||||
}
|
||||
@@ -3595,6 +3604,12 @@ impl OptionsUi {
|
||||
let pointer_over = ui::hovered(cursor, wd.x, wd.y, wd.w, wd.h)
|
||||
&& (!wd.list || cursor.1 >= top && cursor.1 < bottom);
|
||||
let hover = pointer_over || is_focused;
|
||||
// The tooltip is the pointer's (`setTooltip`, drawn while hovered);
|
||||
// a keyboard-focused widget keeps it against the widget
|
||||
// (`MenuTooltipPositioner`). Focus a mouse click leaves behind
|
||||
// does not: the description goes when the mouse leaves, like
|
||||
// vanilla's.
|
||||
let tip_hover = pointer_over || (is_focused && self.focus_from_keyboard);
|
||||
// A pack's option: its label within the widget (the name shortened), and on hover
|
||||
// the whole label when it was, then what the pack says of it.
|
||||
let shader = s.shader_options.as_ref();
|
||||
@@ -3609,7 +3624,7 @@ impl OptionsUi {
|
||||
let fitted = shader.zip(option).map(|(o, name)| {
|
||||
o.option_label_within(name, wd.w - 8.0, |t| atlas.font.width_text(t))
|
||||
});
|
||||
if hover && let Some(o) = shader {
|
||||
if tip_hover && let Some(o) = shader {
|
||||
let comment = match (&wd.kind, option) {
|
||||
(_, Some(name)) => o.comment(&format!("option.{name}")),
|
||||
(
|
||||
@@ -3641,7 +3656,7 @@ impl OptionsUi {
|
||||
tooltip_rect = (!pointer_over).then_some([wd.x, wd.y, wd.w, wd.h]);
|
||||
}
|
||||
}
|
||||
if hover && let Some(tip) = tooltip_text(s, wd) {
|
||||
if tip_hover && let Some(tip) = tooltip_text(s, wd) {
|
||||
tooltip = Some(match &wd.kind {
|
||||
// The duplicate keybinds tooltip keeps its own lines.
|
||||
WKind::Button {
|
||||
@@ -3907,9 +3922,9 @@ impl OptionsUi {
|
||||
};
|
||||
return self.bind_mouse(s, button);
|
||||
}
|
||||
let actions = self
|
||||
.graphics
|
||||
.click(s, (x, y), size, framebuffer, drag, right);
|
||||
let actions =
|
||||
self.graphics
|
||||
.click(s, (x, y), size, framebuffer, drag, right, self.shift);
|
||||
return self.graphics_events(s, actions);
|
||||
}
|
||||
if self.page() == Page::Packs {
|
||||
@@ -4102,13 +4117,13 @@ impl OptionsUi {
|
||||
}
|
||||
Act::Done => events.extend(self.back(s)),
|
||||
Act::Cycle(opt) => {
|
||||
events.extend(self.cycle_option(s, opt, right, framebuffer));
|
||||
events.extend(self.cycle_option(s, opt, self.shift, framebuffer));
|
||||
}
|
||||
Act::Extra(item) => {
|
||||
if let Some(name) = item.key_name() {
|
||||
self.selected_key = Some(name);
|
||||
} else {
|
||||
item.cycle(&mut s.extras, right);
|
||||
item.cycle(&mut s.extras, self.shift);
|
||||
events.push(Event::Changed);
|
||||
}
|
||||
}
|
||||
@@ -6010,6 +6025,65 @@ mod tests {
|
||||
assert_eq!(s.file.fullscreen_resolution, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_clicked_extras_tooltip_leaves_with_the_pointer() {
|
||||
let atlas = atlas();
|
||||
let mut s = Settings::default();
|
||||
let mut ui = OptionsUi::default();
|
||||
ui.show(Page::Extras);
|
||||
let (widgets, _, _, _) = ui.layout(&s, 480.0, 480.0);
|
||||
let extra = widgets
|
||||
.iter()
|
||||
.find(|w| {
|
||||
matches!(
|
||||
&w.kind,
|
||||
WKind::Button {
|
||||
act: Act::Extra(item),
|
||||
..
|
||||
} if !matches!(item, crate::extras::Item::SortKey)
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
ui.click(
|
||||
&mut s,
|
||||
&atlas,
|
||||
extra.x + 1.0,
|
||||
extra.y + 1.0,
|
||||
(480.0, 480.0),
|
||||
[960, 540],
|
||||
false,
|
||||
false,
|
||||
);
|
||||
// The pointer is still on the clicked widget: it carries the
|
||||
// description (`setTooltip`, drawn while hovered).
|
||||
let mut on = HudBatch::new([480, 480]);
|
||||
ui.draw(
|
||||
&s,
|
||||
&mut on,
|
||||
&atlas,
|
||||
(extra.x + extra.w / 2.0, extra.y + extra.h / 2.0),
|
||||
);
|
||||
// The pointer moved off: the description goes with it. A mouse
|
||||
// focus a click left behind (the look-up keeps the click's widget,
|
||||
// `focus_from_keyboard` false) does not keep the tooltip against
|
||||
// the widget, like vanilla's `MenuTooltipPositioner` does for a
|
||||
// keyboard focus.
|
||||
let mut off = HudBatch::new([480, 480]);
|
||||
ui.draw(
|
||||
&s,
|
||||
&mut off,
|
||||
&atlas,
|
||||
(extra.x + extra.w + 40.0, extra.y + extra.h + 40.0),
|
||||
);
|
||||
assert!(
|
||||
off.vertices.len() < on.vertices.len(),
|
||||
"a click left {} tooltip vertices behind when the pointer left ({} vs {})",
|
||||
on.vertices.len() - off.vertices.len(),
|
||||
on.vertices.len(),
|
||||
off.vertices.len(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive_fullscreen_and_its_tooltips_follow_the_option() {
|
||||
let mut s = Settings::default();
|
||||
@@ -7853,4 +7927,146 @@ mod tests {
|
||||
assert!(events.contains(&Event::Changed));
|
||||
assert!(s.extras.music_off.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn options_cycle_button_cycles_forward_and_shift_click_cycles_backward_with_wrapping() {
|
||||
let atlas = atlas();
|
||||
let mut s = Settings::default();
|
||||
let mut ui = OptionsUi::default();
|
||||
ui.show(Page::Chat);
|
||||
let size = (480.0, 480.0);
|
||||
let (widgets, _, _, _) = ui.layout(&s, size.0, size.1);
|
||||
let button = widgets
|
||||
.iter()
|
||||
.find(|wd| {
|
||||
matches!(
|
||||
wd.kind,
|
||||
WKind::Button {
|
||||
active: true,
|
||||
act: Act::Cycle(Opt::ChatVisibility),
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
.expect("ChatVisibility must be a cycle button");
|
||||
let (x, y) = (button.x + 1.0, button.y + 1.0);
|
||||
|
||||
// Initial chat_visibility is 0
|
||||
assert_eq!(s.chat_visibility, 0);
|
||||
|
||||
// 1. Feature without shift (normal click): cycles forward (+1)
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 1);
|
||||
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 2);
|
||||
|
||||
// 3. Wrap-around forward (at max value, normal click wraps to min / 0)
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 0);
|
||||
|
||||
// 4. Wrap-around backward (at min / 0, shift-click wraps to max)
|
||||
ui.shift = true;
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 2);
|
||||
|
||||
// 2. Feature with shift: cycles backward (-1)
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 1);
|
||||
|
||||
assert_eq!(
|
||||
ui.click(&mut s, &atlas, x, y, size, [854, 480], false, false),
|
||||
vec![Event::ButtonClick, Event::Changed]
|
||||
);
|
||||
assert_eq!(s.chat_visibility, 0);
|
||||
ui.shift = false;
|
||||
|
||||
// Also test Opt::Particles on Page::Video
|
||||
ui.show(Page::Video);
|
||||
let (widgets, _, _, _) = ui.layout(&s, size.0, size.1);
|
||||
let button = widgets
|
||||
.iter()
|
||||
.find(|wd| {
|
||||
matches!(
|
||||
wd.kind,
|
||||
WKind::Button {
|
||||
active: true,
|
||||
act: Act::Cycle(Opt::Particles),
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
.expect("Particles must be a cycle button");
|
||||
let (px, py) = (button.x + 1.0, button.y + 1.0);
|
||||
|
||||
// Initial particles is 0
|
||||
assert_eq!(s.particles, 0);
|
||||
|
||||
// Forward
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 1);
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 2);
|
||||
// Wrap forward
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 0);
|
||||
|
||||
// Wrap backward with shift
|
||||
ui.shift = true;
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 2);
|
||||
// Backward with shift
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 1);
|
||||
ui.click(&mut s, &atlas, px, py, size, [854, 480], false, false);
|
||||
assert_eq!(s.particles, 0);
|
||||
ui.shift = false;
|
||||
|
||||
// Also verify Act::Extra on Page::Extras
|
||||
ui.show(Page::Extras);
|
||||
let (widgets, _, _, _) = ui.layout(&s, size.0, size.1);
|
||||
if let Some(extra_btn) = widgets.iter().find(|wd| {
|
||||
matches!(
|
||||
wd.kind,
|
||||
WKind::Button {
|
||||
active: true,
|
||||
act: Act::Extra(crate::extras::Item::CosmeticsOthers),
|
||||
..
|
||||
}
|
||||
)
|
||||
}) {
|
||||
let (ex, ey) = (extra_btn.x + 1.0, extra_btn.y + 1.0);
|
||||
use horizon_cosmetics::prefs::Others;
|
||||
assert_eq!(s.extras.cosmetics_others, Others::Shown);
|
||||
|
||||
// Forward
|
||||
ui.click(&mut s, &atlas, ex, ey, size, [854, 480], false, false);
|
||||
assert_eq!(s.extras.cosmetics_others, Others::NameTags);
|
||||
|
||||
// Backward with shift
|
||||
ui.shift = true;
|
||||
ui.click(&mut s, &atlas, ex, ey, size, [854, 480], false, false);
|
||||
assert_eq!(s.extras.cosmetics_others, Others::Shown);
|
||||
|
||||
// Wrap-around backward with shift
|
||||
ui.click(&mut s, &atlas, ex, ey, size, [854, 480], false, false);
|
||||
assert_eq!(s.extras.cosmetics_others, Others::Hidden);
|
||||
ui.shift = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ use horizon_core::{
|
||||
};
|
||||
use horizon_world::block_collision::BlockCollisionRegistry;
|
||||
use std::{
|
||||
array,
|
||||
collections::{HashMap, HashSet},
|
||||
path::Path,
|
||||
};
|
||||
@@ -47,6 +48,9 @@ pub struct ParticleOptions {
|
||||
/// `GeyserParticleOptions.waterBlocks` and `GeyserBaseParticleOptions`'
|
||||
/// `waterBlocks` and `burstImpulseBase` (0 where the type has none).
|
||||
pub geyser: Option<(i32, f32)>,
|
||||
/// `VibrationParticleOption`: the listener the signal flies to and its
|
||||
/// arrival time in ticks (`ParticleEngine::vibration` is the same particle).
|
||||
pub vibration: Option<([f64; 3], i32)>,
|
||||
/// A native mod's own particle type, by its namespaced name (`mymod:spark`):
|
||||
/// `SimpleParticleType` plus the provider `particles/<name>.json` names.
|
||||
pub mod_particle: Option<String>,
|
||||
@@ -62,6 +66,7 @@ impl ParticleOptions {
|
||||
trail: None,
|
||||
item: None,
|
||||
geyser: None,
|
||||
vibration: None,
|
||||
mod_particle: None,
|
||||
}
|
||||
}
|
||||
@@ -87,6 +92,13 @@ impl ParticleOptions {
|
||||
..Self::simple(ty)
|
||||
}
|
||||
}
|
||||
/// `VibrationParticleOption(destination, arrivalInTicks)`.
|
||||
pub fn vibration(dest: [f64; 3], ticks: i32) -> Self {
|
||||
Self {
|
||||
vibration: Some((dest, ticks)),
|
||||
..Self::simple(ParticleType::Vibration)
|
||||
}
|
||||
}
|
||||
/// `ItemParticleOption`: the stack's layer particle sprites (`BreakingItemParticle`).
|
||||
pub fn item(layers: Vec<String>) -> Self {
|
||||
Self {
|
||||
@@ -516,7 +528,7 @@ pub fn breaking_position(
|
||||
}
|
||||
}
|
||||
// Vanilla consumes all three doubles before replacing the hit-face coordinate.
|
||||
let mut p = std::array::from_fn(|i| {
|
||||
let mut p = array::from_fn(|i| {
|
||||
f64::from(pos[i])
|
||||
+ random.next_double() * (bounds[i + 3] - bounds[i] - f64::from(0.2f32))
|
||||
+ f64::from(0.1f32)
|
||||
@@ -648,18 +660,16 @@ pub fn destroy_positions(
|
||||
mut emit: impl FnMut([f64; 3], [f64; 3]),
|
||||
) {
|
||||
for b in boxes {
|
||||
let widths: [f64; 3] = std::array::from_fn(|i| (b[i + 3] - b[i]).min(1.0));
|
||||
let widths: [f64; 3] = array::from_fn(|i| (b[i + 3] - b[i]).min(1.0));
|
||||
let counts: [usize; 3] = widths.map(|w| ((w / 0.25).ceil() as usize).max(2));
|
||||
for x in 0..counts[0] {
|
||||
for y in 0..counts[1] {
|
||||
for z in 0..counts[2] {
|
||||
let cells = [x, y, z];
|
||||
let relative: [f64; 3] =
|
||||
std::array::from_fn(|i| (cells[i] as f64 + 0.5) / counts[i] as f64);
|
||||
array::from_fn(|i| (cells[i] as f64 + 0.5) / counts[i] as f64);
|
||||
emit(
|
||||
std::array::from_fn(|i| {
|
||||
f64::from(pos[i]) + (relative[i] * widths[i] + b[i])
|
||||
}),
|
||||
array::from_fn(|i| f64::from(pos[i]) + (relative[i] * widths[i] + b[i])),
|
||||
relative.map(|v| v - 0.5),
|
||||
);
|
||||
}
|
||||
@@ -740,7 +750,7 @@ pub fn on_block_faces_between(
|
||||
let count = random.next_int_bound(max - min + 1).unwrap_or(0) + min;
|
||||
for _ in 0..count {
|
||||
// getRandomSpeedRanges, then spawnParticleOnFace with step factor 0.55.
|
||||
let speed: [f64; 3] = std::array::from_fn(|_| next_double_between(random, -0.5, 0.5));
|
||||
let speed: [f64; 3] = array::from_fn(|_| next_double_between(random, -0.5, 0.5));
|
||||
let mut p = [0.0; 3];
|
||||
let mut v = [0.0; 3];
|
||||
for i in 0..3 {
|
||||
@@ -769,12 +779,12 @@ pub fn along_axis(
|
||||
) {
|
||||
let count = random.next_int_bound(max - min + 1).unwrap_or(0) + min;
|
||||
for _ in 0..count {
|
||||
let p: [f64; 3] = std::array::from_fn(|i| {
|
||||
let p: [f64; 3] = array::from_fn(|i| {
|
||||
f64::from(pos[i])
|
||||
+ 0.5
|
||||
+ next_double_between(random, -1.0, 1.0) * if i == axis { 0.5 } else { 0.125 }
|
||||
});
|
||||
let v: [f64; 3] = std::array::from_fn(|i| {
|
||||
let v: [f64; 3] = array::from_fn(|i| {
|
||||
if i == axis {
|
||||
next_double_between(random, -1.0, 1.0)
|
||||
} else {
|
||||
@@ -1257,6 +1267,113 @@ impl ParticleLightCache {
|
||||
}
|
||||
}
|
||||
|
||||
const PARTICLE_SECTION_CACHE_SIZE: usize = 128;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum SectionFrustumResult {
|
||||
Outside,
|
||||
Intersects,
|
||||
Inside,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CachedSectionFrustum {
|
||||
section: [i32; 3],
|
||||
radius_quarters: i32,
|
||||
result: SectionFrustumResult,
|
||||
}
|
||||
|
||||
struct ParticleSectionFrustumCache {
|
||||
entries: [Option<CachedSectionFrustum>; PARTICLE_SECTION_CACHE_SIZE],
|
||||
}
|
||||
|
||||
impl Default for ParticleSectionFrustumCache {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: [None; PARTICLE_SECTION_CACHE_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticleSectionFrustumCache {
|
||||
fn result(
|
||||
&mut self,
|
||||
world: [f64; 3],
|
||||
camera: DVec3,
|
||||
radius: f64,
|
||||
frustum: &crate::gfx::Frustum,
|
||||
) -> SectionFrustumResult {
|
||||
let Some(section) = world_section(world) else {
|
||||
return SectionFrustumResult::Intersects;
|
||||
};
|
||||
if !radius.is_finite() || radius < 0.0 || radius * 4.0 > f64::from(i32::MAX) {
|
||||
return SectionFrustumResult::Intersects;
|
||||
}
|
||||
let radius_quarters = (radius * 4.0).ceil() as i32;
|
||||
let hash = (section[0] as u32).wrapping_mul(73_856_093)
|
||||
^ (section[1] as u32).wrapping_mul(19_349_663)
|
||||
^ (section[2] as u32).wrapping_mul(83_492_791)
|
||||
^ (radius_quarters as u32).wrapping_mul(2_654_435_761);
|
||||
let slot = hash as usize & (PARTICLE_SECTION_CACHE_SIZE - 1);
|
||||
if let Some(entry) = self.entries[slot]
|
||||
&& entry.section == section
|
||||
&& entry.radius_quarters == radius_quarters
|
||||
{
|
||||
return entry.result;
|
||||
}
|
||||
|
||||
let pad = f64::from(radius_quarters) * 0.25;
|
||||
let camera = camera.to_array();
|
||||
let min = array::from_fn(|axis| f64::from(section[axis]) * 16.0 - pad - camera[axis]);
|
||||
let max = array::from_fn(|axis| min[axis] + 16.0 + 2.0 * pad);
|
||||
let result = if min.into_iter().chain(max).any(|v| !v.is_finite()) {
|
||||
SectionFrustumResult::Intersects
|
||||
} else {
|
||||
let min = FVec3::from_array(min.map(|v| v as f32));
|
||||
let max = FVec3::from_array(max.map(|v| v as f32));
|
||||
if !min.is_finite() || !max.is_finite() {
|
||||
SectionFrustumResult::Intersects
|
||||
} else if frustum.contains(min, max) {
|
||||
SectionFrustumResult::Inside
|
||||
} else if frustum.intersects(min, max) {
|
||||
SectionFrustumResult::Intersects
|
||||
} else {
|
||||
SectionFrustumResult::Outside
|
||||
}
|
||||
};
|
||||
self.entries[slot] = Some(CachedSectionFrustum {
|
||||
section,
|
||||
radius_quarters,
|
||||
result,
|
||||
});
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn world_section(position: [f64; 3]) -> Option<[i32; 3]> {
|
||||
let mut block = [0; 3];
|
||||
for axis in 0..3 {
|
||||
let value = position[axis].floor();
|
||||
if !value.is_finite() || value < f64::from(i32::MIN) || value > f64::from(i32::MAX) {
|
||||
return None;
|
||||
}
|
||||
block[axis] = value as i32;
|
||||
}
|
||||
Some([block[0] >> 4, block[1] >> 4, block[2] >> 4])
|
||||
}
|
||||
|
||||
fn below_half_pixel(radius: f64, distance_squared: f64, pixel_focal_length: f32) -> bool {
|
||||
let projected_limit = 4.0 * radius * f64::from(pixel_focal_length);
|
||||
radius.is_finite()
|
||||
&& radius >= 0.0
|
||||
&& distance_squared.is_finite()
|
||||
&& distance_squared > 0.0
|
||||
&& pixel_focal_length.is_finite()
|
||||
&& pixel_focal_length > 0.0
|
||||
&& projected_limit.is_finite()
|
||||
&& projected_limit * projected_limit < distance_squared
|
||||
}
|
||||
|
||||
/// `ClientExplosionTracker.ExplosionInfo`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct TrackedExplosion {
|
||||
@@ -1435,15 +1552,14 @@ impl ParticleEngine {
|
||||
self.particles.clear();
|
||||
self.pending.clear();
|
||||
}
|
||||
/// A vibration signal flying from `from` to `to` in `ticks`
|
||||
/// (`VibrationParticleOption`).
|
||||
pub fn vibration(&mut self, from: [f64; 3], to: [f64; 3], ticks: i32) {
|
||||
/// `VibrationSignalParticle`'s body: the signal leaves `from` aimed at the
|
||||
/// listener at `to` and lands there in `ticks`.
|
||||
fn vibration_particle(from: [f64; 3], to: [f64; 3], ticks: i32) -> Particle {
|
||||
let mut q = Particle::blank(ParticleType::Vibration, from);
|
||||
q.size = 0.3;
|
||||
q.lifetime = ticks;
|
||||
q.has_physics = false;
|
||||
q.light = Light::Block15;
|
||||
q.set = self.set_of(ParticleType::Vibration);
|
||||
let d = [from[0] - to[0], from[1] - to[1], from[2] - to[2]];
|
||||
let rot = d[0].atan2(d[2]) as f32;
|
||||
let pitch = d[1].atan2((d[0] * d[0] + d[2] * d[2]).sqrt()) as f32;
|
||||
@@ -1454,6 +1570,14 @@ impl ParticleEngine {
|
||||
pitch,
|
||||
pitch_o: pitch,
|
||||
};
|
||||
q
|
||||
}
|
||||
/// A vibration signal flying from `from` to `to` in `ticks`
|
||||
/// (`VibrationParticleOption`), as the client's own vibration system
|
||||
/// (`VibrationSystem.Ticker`) spawns one.
|
||||
pub fn vibration(&mut self, from: [f64; 3], to: [f64; 3], ticks: i32) {
|
||||
let mut q = Self::vibration_particle(from, to, ticks);
|
||||
q.set = self.set_of(ParticleType::Vibration);
|
||||
self.pending.push(q);
|
||||
}
|
||||
/// `ParticleEngine.add` (via `particlesToAdd`, applied on the next tick).
|
||||
@@ -1533,7 +1657,7 @@ impl ParticleEngine {
|
||||
for _ in 0..128 {
|
||||
let r = &mut self.level_random;
|
||||
let t = r.next_double();
|
||||
let velocity = std::array::from_fn(|_| f64::from((r.next_float() - 0.5) * 0.2));
|
||||
let velocity = array::from_fn(|_| f64::from((r.next_float() - 0.5) * 0.2));
|
||||
let position = [
|
||||
to[0] + t * (from[0] - to[0]) + (r.next_double() - 0.5) + 0.5,
|
||||
to[1] + t * (from[1] - to[1]) + r.next_double() - 0.5,
|
||||
@@ -1561,7 +1685,7 @@ impl ParticleEngine {
|
||||
for _ in 0..128 {
|
||||
let r = &mut self.level_random;
|
||||
let t = r.next_double();
|
||||
let velocity = std::array::from_fn(|_| f64::from((r.next_float() - 0.5) * 0.2));
|
||||
let velocity = array::from_fn(|_| f64::from((r.next_float() - 0.5) * 0.2));
|
||||
// Mth.lerp(t, to, from).
|
||||
let position = [
|
||||
to[0] + t * (from[0] - to[0]) + (r.next_double() - 0.5) * WIDTH * 2.0,
|
||||
@@ -2758,8 +2882,16 @@ impl ParticleEngine {
|
||||
}
|
||||
q
|
||||
}
|
||||
// Vibration signals are created by `vibration`, aimed at a listener.
|
||||
T::Vibration => return None,
|
||||
// VibrationSignalParticle.Provider: `VibrationParticleOption` carries
|
||||
// the listener the signal flies to and its arrival time; the sprite
|
||||
// is the set's own draw from the engine's random.
|
||||
T::Vibration => {
|
||||
let (dest, ticks) = o.vibration?;
|
||||
let mut q = Self::vibration_particle(p, dest, ticks);
|
||||
q.set = set;
|
||||
q.sprite = self.sprite_from_set(ty);
|
||||
q
|
||||
}
|
||||
// ShriekParticle.
|
||||
T::Shriek => {
|
||||
let mut q = Particle::moving(r, ty, p, [0.0; 3]);
|
||||
@@ -4365,8 +4497,7 @@ impl ParticleEngine {
|
||||
.filter(|p| p.rgba[3] > 0.0)
|
||||
.filter_map(|p| {
|
||||
let (_, rgb, reach) = shining.iter().find(|(ty, ..)| *ty == p.ty)?;
|
||||
let at =
|
||||
std::array::from_fn(|i| p.previous[i] + (p.position[i] - p.previous[i]) * f);
|
||||
let at = array::from_fn(|i| p.previous[i] + (p.position[i] - p.previous[i]) * f);
|
||||
Some((at, *rgb, *reach, p.rgba[3].clamp(0.0, 1.0)))
|
||||
})
|
||||
.collect()
|
||||
@@ -4381,11 +4512,70 @@ impl ParticleEngine {
|
||||
partial: f32,
|
||||
light_at: &dyn Fn([i32; 3]) -> u8,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
self.render_inner::<false>(
|
||||
camera,
|
||||
yaw,
|
||||
pitch,
|
||||
partial,
|
||||
0.0,
|
||||
None,
|
||||
&mut |_, _| false,
|
||||
light_at,
|
||||
out,
|
||||
);
|
||||
}
|
||||
|
||||
/// Render particles after rejecting view-frustum misses and any particles
|
||||
/// the caller proves hidden behind opaque terrain.
|
||||
pub fn render_culled(
|
||||
&self,
|
||||
camera: DVec3,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
partial: f32,
|
||||
pixel_focal_length: f32,
|
||||
culling_enabled: bool,
|
||||
frustum: Option<&crate::gfx::Frustum>,
|
||||
mut occluded: impl FnMut([f64; 3], [f64; 3]) -> bool,
|
||||
light_at: &dyn Fn([i32; 3]) -> u8,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
if !culling_enabled {
|
||||
self.render(camera, yaw, pitch, partial, light_at, out);
|
||||
return;
|
||||
}
|
||||
self.render_inner::<true>(
|
||||
camera,
|
||||
yaw,
|
||||
pitch,
|
||||
partial,
|
||||
pixel_focal_length,
|
||||
frustum,
|
||||
&mut occluded,
|
||||
light_at,
|
||||
out,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_inner<const CULL: bool>(
|
||||
&self,
|
||||
camera: DVec3,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
partial: f32,
|
||||
pixel_focal_length: f32,
|
||||
frustum: Option<&crate::gfx::Frustum>,
|
||||
occluded: &mut dyn FnMut([f64; 3], [f64; 3]) -> bool,
|
||||
light_at: &dyn Fn([i32; 3]) -> u8,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
// Camera.setRotation: rotationYXZ(PI - yRot, -xRot, 0).
|
||||
let base = Quat::from_rotation_y(std::f32::consts::PI - yaw.to_radians())
|
||||
* Quat::from_rotation_x(-pitch.to_radians());
|
||||
let mut light_cache = ParticleLightCache::default();
|
||||
let mut section_cache =
|
||||
(CULL && frustum.is_some()).then(ParticleSectionFrustumCache::default);
|
||||
for p in &self.particles {
|
||||
if p.no_render {
|
||||
continue;
|
||||
@@ -4397,6 +4587,39 @@ impl ParticleEngine {
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if CULL && matches!(p.behaviour, Behaviour::Shriek { delay } if delay > 0) {
|
||||
continue;
|
||||
}
|
||||
let culled_geometry = if CULL {
|
||||
let a = f64::from(partial);
|
||||
let world = array::from_fn(|i| p.previous[i] + (p.position[i] - p.previous[i]) * a);
|
||||
let center = (DVec3::from_array(world) - camera).as_vec3();
|
||||
let size = p.quad_size(partial);
|
||||
// Every generated quad vertex is sqrt(2) * size from its center,
|
||||
// including the rotated two-quad particle types.
|
||||
let radius = f64::from(size.abs()) * std::f64::consts::SQRT_2 + 0.05;
|
||||
if let (Some(frustum), Some(section_cache)) = (frustum, section_cache.as_mut()) {
|
||||
match section_cache.result(world, camera, radius, frustum) {
|
||||
SectionFrustumResult::Outside => continue,
|
||||
SectionFrustumResult::Intersects
|
||||
if !frustum.intersects_sphere(center, radius as f32) =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SectionFrustumResult::Inside | SectionFrustumResult::Intersects => {}
|
||||
}
|
||||
}
|
||||
let distance_squared = f64::from(center.length_squared());
|
||||
if below_half_pixel(radius, distance_squared, pixel_focal_length) {
|
||||
continue;
|
||||
}
|
||||
if occluded(world.map(|v| v - radius), world.map(|v| v + radius)) {
|
||||
continue;
|
||||
}
|
||||
Some((center, size))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// `SubmitNodeCollection.submitQuadParticleGroup`: the translucent
|
||||
// layer draws after translucent terrain, so a crumb of stained
|
||||
// glass cannot hide the glass behind it (`getLayer`).
|
||||
@@ -4412,19 +4635,20 @@ impl ParticleEngine {
|
||||
},
|
||||
};
|
||||
let Some(r) = rect else { continue };
|
||||
let a = f64::from(partial);
|
||||
let pos = DVec3::new(
|
||||
p.previous[0] + (p.position[0] - p.previous[0]) * a,
|
||||
p.previous[1] + (p.position[1] - p.previous[1]) * a,
|
||||
p.previous[2] + (p.position[2] - p.previous[2]) * a,
|
||||
) - camera;
|
||||
let center = pos.as_vec3();
|
||||
let (center, size) = culled_geometry.unwrap_or_else(|| {
|
||||
let a = f64::from(partial);
|
||||
let pos = DVec3::new(
|
||||
p.previous[0] + (p.position[0] - p.previous[0]) * a,
|
||||
p.previous[1] + (p.position[1] - p.previous[1]) * a,
|
||||
p.previous[2] + (p.position[2] - p.previous[2]) * a,
|
||||
) - camera;
|
||||
(pos.as_vec3(), p.quad_size(partial))
|
||||
});
|
||||
let rotation = if p.roll != 0.0 {
|
||||
base * Quat::from_rotation_z(p.o_roll + (p.roll - p.o_roll) * partial)
|
||||
} else {
|
||||
base
|
||||
};
|
||||
let size = p.quad_size(partial);
|
||||
let mut alpha = p.rgba[3];
|
||||
// OverlayParticle.extract: the flash fades as it pulses.
|
||||
if p.ty == ParticleType::Flash {
|
||||
@@ -4438,7 +4662,7 @@ impl ParticleEngine {
|
||||
match p.behaviour {
|
||||
// ShriekParticle.extract: two tilted quads, fading out.
|
||||
Behaviour::Shriek { delay } => {
|
||||
if delay > 0 {
|
||||
if !CULL && delay > 0 {
|
||||
continue;
|
||||
}
|
||||
alpha = 1.0 - ((p.age as f32 + partial) / p.lifetime as f32).clamp(0.0, 1.0);
|
||||
@@ -4481,7 +4705,7 @@ impl ParticleEngine {
|
||||
// DustColorTransitionParticle.lerpColors.
|
||||
Behaviour::Transition { from, to } => {
|
||||
let t = (p.age as f32 + partial) / (p.lifetime as f32 + 1.0);
|
||||
rgb = std::array::from_fn(|i| from[i] + (to[i] - from[i]) * t);
|
||||
rgb = array::from_fn(|i| from[i] + (to[i] - from[i]) * t);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -4616,6 +4840,86 @@ mod tests {
|
||||
assert_eq!(reads.get(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subpixel_culling_uses_the_frame_projection_and_fails_open() {
|
||||
assert!(below_half_pixel(0.01, 41.0_f64.powi(2), 1_000.0));
|
||||
assert!(!below_half_pixel(0.01, 40.0_f64.powi(2), 1_000.0));
|
||||
assert!(!below_half_pixel(0.01, 41.0_f64.powi(2), 0.0));
|
||||
assert!(!below_half_pixel(0.01, f64::NAN, 1_000.0));
|
||||
assert!(!below_half_pixel(f64::NAN, 41.0_f64.powi(2), 1_000.0));
|
||||
|
||||
for distance in [0.25_f32, 1.0, 8.0, 40.0, 128.0] {
|
||||
for radius in [0.01, 0.05, 0.2] {
|
||||
let center = FVec3::new(distance, distance * 0.3, -distance * 0.8);
|
||||
let old = 2.0 * radius * 900.0 / f64::from(center.length()) < 0.5;
|
||||
assert_eq!(
|
||||
below_half_pixel(radius, f64::from(center.length_squared()), 900.0),
|
||||
old
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_culled_render_matches_the_regular_particle_path() {
|
||||
let mut engine = ParticleEngine::new(Path::new(""), &HashMap::new(), &HashSet::new());
|
||||
let sprite = "test:smoke".to_owned();
|
||||
engine.atlas.insert(
|
||||
sprite.clone(),
|
||||
(
|
||||
Rect {
|
||||
u0: 0.0,
|
||||
v0: 0.0,
|
||||
u1: 1.0,
|
||||
v1: 1.0,
|
||||
w: 16,
|
||||
h: 16,
|
||||
},
|
||||
false,
|
||||
),
|
||||
);
|
||||
let mut random = LegacyRandom::new(7);
|
||||
let mut particle =
|
||||
Particle::moving(&mut random, ParticleType::Smoke, [0.0, 0.0, -3.0], [0.0; 3]);
|
||||
particle.sprite = Sprite::Named(sprite);
|
||||
engine.particles.push(particle);
|
||||
|
||||
let light_at = |_: [i32; 3]| 0xf0;
|
||||
let mut regular = Vec::new();
|
||||
engine.render(DVec3::ZERO, 0.0, 0.0, 0.5, &light_at, &mut regular);
|
||||
let mut disabled = Vec::new();
|
||||
engine.render_culled(
|
||||
DVec3::ZERO,
|
||||
0.0,
|
||||
0.0,
|
||||
0.5,
|
||||
900.0,
|
||||
false,
|
||||
None,
|
||||
|_, _| panic!("disabled culling must not query particle visibility"),
|
||||
&light_at,
|
||||
&mut disabled,
|
||||
);
|
||||
|
||||
let attributes = |vertices: &[Vertex]| {
|
||||
vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
(
|
||||
vertex.pos,
|
||||
vertex.uv,
|
||||
vertex.layer,
|
||||
vertex.color,
|
||||
vertex.light,
|
||||
vertex.normal,
|
||||
vertex.glint_uv,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(attributes(&disabled), attributes(®ular));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mod_particles_json_becomes_its_sprite_set() {
|
||||
let dir =
|
||||
@@ -5176,6 +5480,41 @@ mod tests {
|
||||
}
|
||||
assert_eq!(engine.pending.len(), 1010);
|
||||
}
|
||||
/// `VibrationSignalParticle.Provider.createParticle`: the provider path
|
||||
/// the client's packet handler reaches (`add_with`) makes the signal from
|
||||
/// the option's destination and arrival time, where it used to refuse it.
|
||||
#[test]
|
||||
fn a_vibration_option_makes_the_signal_its_provider_draws() {
|
||||
let mut engine = ParticleEngine::new(Path::new(""), &HashMap::new(), &HashSet::new());
|
||||
let options = ParticleOptions::vibration([10.5, 70.5, -3.5], 20);
|
||||
// Within the camera's 32 blocks, so only the provider decides.
|
||||
engine.add_with(&options, [0.5, 1.5, 0.5], [0.0; 3], false, false);
|
||||
assert_eq!(engine.made().get("vibration"), Some(&1));
|
||||
let q = engine.pending.first().expect("the signal is made");
|
||||
assert_eq!(q.ty, ParticleType::Vibration);
|
||||
assert_eq!(q.lifetime, 20);
|
||||
assert!((q.size - 0.3).abs() < 1e-6);
|
||||
assert!(!q.has_physics);
|
||||
match &q.behaviour {
|
||||
Behaviour::Vibration {
|
||||
dest, rot, pitch, ..
|
||||
} => {
|
||||
assert_eq!(*dest, [10.5, 70.5, -3.5]);
|
||||
// The constructor aims it from where it starts at the listener.
|
||||
let d: [f64; 3] = [0.5 - 10.5, 1.5 - 70.5, 0.5 + 3.5];
|
||||
assert!((*rot - d[0].atan2(d[2]) as f32).abs() < 1e-6);
|
||||
let flat = (d[0] * d[0] + d[2] * d[2]).sqrt();
|
||||
assert!((*pitch - d[1].atan2(flat) as f32).abs() < 1e-6);
|
||||
}
|
||||
_ => panic!("a vibration signal"),
|
||||
}
|
||||
// Only `VibrationParticleOption` carries a listener; a bare type has none.
|
||||
let bare = ParticleOptions::simple(ParticleType::Vibration);
|
||||
assert!(
|
||||
engine.make(&bare, [0.0; 3], [0.0; 3], 1).is_none(),
|
||||
"no destination, no signal"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn particle_names_round_trip() {
|
||||
for &ty in ParticleType::ALL {
|
||||
|
||||
@@ -490,7 +490,7 @@ pub struct Globals {
|
||||
|
||||
impl Globals {
|
||||
/// The std140 block: `ivec3, float, vec3, float, vec2, int, int`.
|
||||
fn bytes(&self, size: [u32; 2], menu_blur_radius: i32) -> [u8; 48] {
|
||||
pub(crate) fn bytes(&self, size: [u32; 2], menu_blur_radius: i32) -> [u8; 48] {
|
||||
let mut out = Vec::with_capacity(48);
|
||||
for v in self.camera_block_pos {
|
||||
out.extend_from_slice(&v.to_le_bytes());
|
||||
|
||||
@@ -144,6 +144,9 @@ pub struct Settings {
|
||||
pub discrete_scroll: bool,
|
||||
pub toggle_crouch: bool,
|
||||
pub toggle_sprint: bool,
|
||||
/// 3D skin layers (`threeDSkinLayers`): each painted texel of a player's
|
||||
/// outer layer drawn as a slab (`crate::...`, Options → Skin).
|
||||
pub three_d_skin_layers: bool,
|
||||
pub auto_jump: bool,
|
||||
pub language: String,
|
||||
pub force_unicode: bool,
|
||||
@@ -248,6 +251,7 @@ impl Default for Settings {
|
||||
allow_cursor_changes: true,
|
||||
toggle_crouch: false,
|
||||
toggle_sprint: false,
|
||||
three_d_skin_layers: false,
|
||||
auto_jump: false,
|
||||
language: "en_us".into(),
|
||||
force_unicode: false,
|
||||
@@ -459,6 +463,7 @@ impl Settings {
|
||||
allow_cursor_changes: f.bool("allowCursorChanges"),
|
||||
toggle_crouch: f.bool("toggleCrouch"),
|
||||
toggle_sprint: f.bool("toggleSprint"),
|
||||
three_d_skin_layers: f.bool("threeDSkinLayers"),
|
||||
toggle_attack: f.bool("toggleAttack"),
|
||||
toggle_use: f.bool("toggleUse"),
|
||||
sprint_window: f.int("sprintWindow"),
|
||||
@@ -570,6 +575,7 @@ impl Settings {
|
||||
put_b(&mut f, "allowCursorChanges", self.allow_cursor_changes);
|
||||
put_b(&mut f, "toggleCrouch", self.toggle_crouch);
|
||||
put_b(&mut f, "toggleSprint", self.toggle_sprint);
|
||||
put_b(&mut f, "threeDSkinLayers", self.three_d_skin_layers);
|
||||
put_b(&mut f, "toggleAttack", self.toggle_attack);
|
||||
put_b(&mut f, "toggleUse", self.toggle_use);
|
||||
put_i(&mut f, "sprintWindow", self.sprint_window);
|
||||
@@ -808,6 +814,8 @@ pub enum Opt {
|
||||
PadKeyboard,
|
||||
/// `options.modelPart.*`: one skin layer (index into `options_file::MODEL_PARTS`).
|
||||
ModelPart(usize),
|
||||
/// 3D skin layers: the player's outer layer as extruded slabs.
|
||||
SkinLayers3d,
|
||||
MainHand,
|
||||
AllowServerListing,
|
||||
RealmsNotifications,
|
||||
|
||||
@@ -814,6 +814,17 @@ pub struct Downloader {
|
||||
/// without end; the disk cache holds everything it drops.
|
||||
const MAX_SKIN_IMAGES: usize = 1024;
|
||||
impl Downloader {
|
||||
/// The decoded 64×64 RGBA image an atlas location holds, when it is
|
||||
/// still in memory (3D skin layers builds its geometry from it, and the
|
||||
/// disk cache backs whatever was dropped).
|
||||
pub fn image(&self, location: &str) -> Option<&[u8]> {
|
||||
self.images.get(location).map(Vec::as_slice)
|
||||
}
|
||||
/// Keeps a decoded image under `location` for the images read back later
|
||||
/// (a launcher's offline skin, which skips `queue`).
|
||||
pub fn remember_image(&mut self, location: &str, rgba: &[u8]) {
|
||||
self.remember(location.to_owned(), rgba.to_vec());
|
||||
}
|
||||
pub fn new(root: std::path::PathBuf) -> Self {
|
||||
let (send, done) = std::sync::mpsc::channel();
|
||||
Self {
|
||||
|
||||
@@ -18,8 +18,15 @@ thread_local! {
|
||||
static HERE: Cell<bool> = const { Cell::new(false) };
|
||||
/// How many timed spans the thread is inside: the next span's [`Span::depth`].
|
||||
static DEPTH: Cell<u8> = const { Cell::new(0) };
|
||||
/// What this thread's slowest steps were busy with since it last took them ([`note`]).
|
||||
static NOTES: RefCell<Vec<(f32, String)>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// The shortest step a [`note`] keeps, in milliseconds.
|
||||
pub const NOTE_MS: f32 = 10.0;
|
||||
/// The notes a thread keeps between two [`take_notes`]: its slowest steps.
|
||||
const NOTES_KEPT: usize = 3;
|
||||
|
||||
/// A named timing: wall milliseconds and the thread's CPU milliseconds in it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Span {
|
||||
@@ -83,6 +90,36 @@ pub fn time<T>(name: &'static str, f: impl FnOnce() -> T) -> T {
|
||||
});
|
||||
out
|
||||
}
|
||||
/// Notes that one step inside a span took `ms` milliseconds doing `what` (one player's
|
||||
/// packets, say), when this thread records spans and the step took at least [`NOTE_MS`]: a
|
||||
/// span says where a slow tick went, its notes whose doing it was. Only the slowest few are
|
||||
/// kept, and `what` is built only for a step that is.
|
||||
pub fn note(ms: f32, what: impl FnOnce() -> String) {
|
||||
if ms < NOTE_MS || !recording_here() {
|
||||
return;
|
||||
}
|
||||
NOTES.with(|n| {
|
||||
let mut n = n.borrow_mut();
|
||||
if n.len() >= NOTES_KEPT {
|
||||
let Some((i, _)) = n
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (kept, _))| *kept < ms)
|
||||
.min_by(|a, b| a.1.0.total_cmp(&b.1.0))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
n.swap_remove(i);
|
||||
}
|
||||
n.push((ms, what()));
|
||||
});
|
||||
}
|
||||
/// This thread's notes ([`note`]), slowest first; clears them.
|
||||
pub fn take_notes() -> Vec<(f32, String)> {
|
||||
let mut notes = NOTES.with(|n| std::mem::take(&mut *n.borrow_mut()));
|
||||
notes.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||
notes
|
||||
}
|
||||
/// Consecutive steps of one function timed as spans: each [`Laps::lap`] records the time since
|
||||
/// the previous one (or since [`laps`]) under its name. Nothing is read while spans are off.
|
||||
pub struct Laps(Option<(Instant, Option<Duration>)>);
|
||||
@@ -404,6 +441,41 @@ pub mod startup {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Span;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
/// Spans on for every thread ([`super::enable`]) is process-wide: a test that turns them on
|
||||
/// would make another, run beside it, record what it expects left out. Those tests take
|
||||
/// this lock.
|
||||
fn spans_switch() -> MutexGuard<'static, ()> {
|
||||
static SWITCH: Mutex<()> = Mutex::new(());
|
||||
SWITCH.lock().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
/// A slow tick's notes are its slowest steps, slowest first; short steps and steps on a
|
||||
/// thread that records no spans leave none.
|
||||
#[test]
|
||||
fn notes_keep_the_slowest_steps() {
|
||||
let _switch = spans_switch();
|
||||
super::record_here(false);
|
||||
super::note(500.0, || "unrecorded".into());
|
||||
assert!(super::take_notes().is_empty());
|
||||
super::record_here(true);
|
||||
for (ms, what) in [
|
||||
(12.0, "a"),
|
||||
(900.0, "b"),
|
||||
(3.0, "short"),
|
||||
(40.0, "c"),
|
||||
(11.0, "d"),
|
||||
(60.0, "e"),
|
||||
] {
|
||||
super::note(ms, || what.into());
|
||||
}
|
||||
let notes: Vec<(f32, String)> = super::take_notes();
|
||||
super::record_here(false);
|
||||
let names: Vec<&str> = notes.iter().map(|(_, w)| w.as_str()).collect();
|
||||
assert_eq!(names, ["b", "e", "c"]);
|
||||
assert!(super::take_notes().is_empty(), "taking them clears them");
|
||||
}
|
||||
|
||||
/// Spans of one name add up; a span the thread spent mostly waiting
|
||||
/// shows its CPU time after the wall time, short ones are left out.
|
||||
@@ -515,6 +587,7 @@ mod tests {
|
||||
/// Laps record the steps between them while spans are on, and nothing while off.
|
||||
#[test]
|
||||
fn laps_time_consecutive_steps() {
|
||||
let _switch = spans_switch();
|
||||
let _ = super::take();
|
||||
let mut off = super::laps();
|
||||
off.lap("unseen");
|
||||
|
||||
@@ -1224,6 +1224,14 @@ impl Streaming {
|
||||
world.layout().sections() as i32,
|
||||
);
|
||||
}
|
||||
// `ChunkTaskPriorityQueue.release` for each of them (`ChunkTaskDispatcher`'s
|
||||
// `release(col, true)` from the chunk's unload): the jobs still queued for a
|
||||
// column go with it instead of running for a level they no longer belong to.
|
||||
if !gone.is_empty()
|
||||
&& let Ok(mut queue) = self.shared.queue.lock()
|
||||
{
|
||||
release_columns(&mut queue, gone);
|
||||
}
|
||||
let mut chunks = Vec::with_capacity(gone.len());
|
||||
for &(x, z) in gone {
|
||||
self.lit.remove(&(x, z));
|
||||
@@ -2879,6 +2887,25 @@ fn rank_again(queue: &mut BinaryHeap<Queued>, distance: impl Fn(i32, i32) -> i64
|
||||
}
|
||||
*queue = BinaryHeap::from(jobs);
|
||||
}
|
||||
/// `ChunkTaskPriorityQueue.release(pos, clearQueue=true)`: the jobs queued for the
|
||||
/// `gone` columns are dropped, so a view that moved or shrank does not wait behind the
|
||||
/// generation of the chunks it left. A job without a column (a save, meshes to free)
|
||||
/// stays, as vanilla keeps the tasks of a chunk that is still loaded. Returns how many
|
||||
/// jobs went.
|
||||
fn release_columns(queue: &mut BinaryHeap<Queued>, gone: &FxHashSet<(i32, i32)>) -> usize {
|
||||
if queue.is_empty() || gone.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let queued = std::mem::take(queue).into_vec();
|
||||
let before = queued.len();
|
||||
let kept: Vec<Queued> = queued
|
||||
.into_iter()
|
||||
.filter(|job| !job.at.is_some_and(|at| gone.contains(&at)))
|
||||
.collect();
|
||||
let released = before - kept.len();
|
||||
*queue = BinaryHeap::from(kept);
|
||||
released
|
||||
}
|
||||
/// Whether the 3x3 a light job for (x, z) reads is all there: every
|
||||
/// neighbour a finished column, or a decorated proto chunk past the area
|
||||
/// (the last FULL ring lights against neighbours at FEATURES, as
|
||||
@@ -3310,6 +3337,59 @@ mod tests {
|
||||
assert_eq!(queue.pop().and_then(|q| q.at), Some((20, 0)));
|
||||
}
|
||||
|
||||
/// `ChunkTaskPriorityQueue.release`: a column that unloaded keeps no queued job, so
|
||||
/// a teleport or a smaller render distance does not spend its workers on the world
|
||||
/// it left; the other columns' jobs and the jobs without a column (saves, meshes to
|
||||
/// free) stay, in the same order.
|
||||
#[test]
|
||||
fn a_released_column_keeps_no_queued_job() {
|
||||
let generate = |x, z, priority, order| Queued {
|
||||
priority,
|
||||
at: Some((x, z)),
|
||||
bias: 0,
|
||||
order,
|
||||
job: Job::Generate(x, z),
|
||||
};
|
||||
let save = |order| Queued {
|
||||
priority: i64::MIN,
|
||||
at: None,
|
||||
bias: 0,
|
||||
order,
|
||||
job: Job::Save(Vec::new()),
|
||||
};
|
||||
let queued = || {
|
||||
BinaryHeap::from(vec![
|
||||
generate(0, 0, 0, 1),
|
||||
generate(0, 0, 4, 2),
|
||||
generate(1, 0, 8, 3),
|
||||
generate(9, 9, 12, 4),
|
||||
save(5),
|
||||
])
|
||||
};
|
||||
let pops = |mut queue: BinaryHeap<Queued>| {
|
||||
std::iter::from_fn(move || queue.pop())
|
||||
.map(|q| q.at)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
// Nothing goes for a column that is still loaded, or for nothing at all.
|
||||
let mut queue = queued();
|
||||
assert_eq!(release_columns(&mut queue, &FxHashSet::default()), 0);
|
||||
assert_eq!(
|
||||
release_columns(&mut queue, &[(20, 20)].into_iter().collect()),
|
||||
0
|
||||
);
|
||||
assert_eq!(queue.len(), 5);
|
||||
// Both of (0, 0)'s jobs go, its neighbour's and the save stay.
|
||||
let gone: FxHashSet<(i32, i32)> = [(0, 0)].into_iter().collect();
|
||||
assert_eq!(release_columns(&mut queue, &gone), 2);
|
||||
assert_eq!(pops(queue), [None, Some((1, 0)), Some((9, 9))]);
|
||||
// Every column leaving at once: only the jobs without one are left.
|
||||
let mut queue = queued();
|
||||
let all: FxHashSet<(i32, i32)> = [(0, 0), (1, 0), (9, 9)].into_iter().collect();
|
||||
assert_eq!(release_columns(&mut queue, &all), 4);
|
||||
assert_eq!(pops(queue), [None]);
|
||||
}
|
||||
|
||||
/// The light and mesh stages submit bounded work, the nearest first:
|
||||
/// what exceeds the room stays dirty, to be chosen again — from the
|
||||
/// current centre — as capacity frees. Submitted whole, the queue once
|
||||
|
||||
@@ -32,13 +32,14 @@ struct Globals {
|
||||
// triangle's volume with the camera; then each rim vertex's normal against the apex.
|
||||
sunrise_fan: array<vec4<f32>, 32>,
|
||||
// x: Options.textureFiltering (0 none, 1 RGSS, 2 anisotropic), y: the sprite arrays' width
|
||||
// in texels (vanilla's TextureSize).
|
||||
// in texels (vanilla's TextureSize), z: the mip level at which a sprite is one texel when
|
||||
// the views reach it, else 0 (`rotated_grid`).
|
||||
filtering: vec4<f32>,
|
||||
// HDR output's tier windows (hdr.rs `Hdr::windows`): x..w low to very bright,
|
||||
// [0] where the lift starts, [1] where it is full.
|
||||
hdr_windows: array<vec4<f32>, 2>,
|
||||
// HDR lighting: x how strongly the sky lights the world now (`Sky::sky_light_factor`),
|
||||
// y what the sky and fog get: the same, but none under water.
|
||||
// y what the sky and fog get: the same, but none under water; z 1 with HDR output on.
|
||||
hdr_light: vec4<f32>,
|
||||
}
|
||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||
@@ -124,12 +125,34 @@ fn sample_rgss(uv: vec2<f32>, layer: u32, pixel: vec2<f32>, du: vec2<f32>, dv: v
|
||||
let max_texel = max(texel_screen.x, texel_screen.y);
|
||||
let min_pixel = min(pixel.x, pixel.y);
|
||||
let blend = smoothstep(min_pixel, min_pixel * 2.0, max_texel);
|
||||
// Only the side of the mix that has weight is sampled: near texels (blend 0) take
|
||||
// sample_nearest's one tap alone, far ones (blend 1) the rotated grid alone.
|
||||
if blend <= 0.0 {
|
||||
return sample_nearest(uv, layer, pixel, du, dv, texel_screen, tiled);
|
||||
}
|
||||
let rgss = rotated_grid(uv, layer, pixel, du, dv);
|
||||
if blend >= 1.0 {
|
||||
return rgss;
|
||||
}
|
||||
return mix(sample_nearest(uv, layer, pixel, du, dv, texel_screen, tiled), rgss, blend);
|
||||
}
|
||||
fn rotated_grid(uv: vec2<f32>, layer: u32, pixel: vec2<f32>, du: vec2<f32>,
|
||||
dv: vec2<f32>) -> vec4<f32> {
|
||||
let min_pixel = min(pixel.x, pixel.y);
|
||||
let du_len = length(du);
|
||||
let dv_len = length(dv);
|
||||
let effective = sqrt(min(du_len, dv_len) * max(du_len, dv_len));
|
||||
let exact = max(0.0, log2(effective / min_pixel));
|
||||
let low = floor(exact);
|
||||
let mip_blend = fract(exact);
|
||||
// From this level on the sprite is a single texel (0: the views stop before one is), and
|
||||
// the sampler clamps any level past it to it: there every tap reads the same value, so
|
||||
// one tap gives the four's mean. Distant tiles sit there.
|
||||
let single = globals.filtering.z;
|
||||
if single > 0.0 && low >= single {
|
||||
return sprite_level(uv, layer, low);
|
||||
}
|
||||
let high_single = single > 0.0 && low + 1.0 >= single;
|
||||
var offsets = array<vec2<f32>, 4>(
|
||||
vec2(0.125, 0.375), vec2(-0.125, -0.375), vec2(0.375, -0.125), vec2(-0.375, 0.125));
|
||||
var sum_low = vec4(0.0);
|
||||
@@ -137,10 +160,14 @@ fn sample_rgss(uv: vec2<f32>, layer: u32, pixel: vec2<f32>, du: vec2<f32>, dv: v
|
||||
for (var i = 0; i < 4; i++) {
|
||||
let p = uv + offsets[i] * pixel;
|
||||
sum_low += sprite_level(p, layer, low);
|
||||
sum_high += sprite_level(p, layer, low + 1.0);
|
||||
if !high_single {
|
||||
sum_high += sprite_level(p, layer, low + 1.0);
|
||||
}
|
||||
}
|
||||
let rgss = mix(sum_low * 0.25, sum_high * 0.25, mip_blend);
|
||||
return mix(sample_nearest(uv, layer, pixel, du, dv, texel_screen, tiled), rgss, blend);
|
||||
if high_single {
|
||||
sum_high = 4.0 * sprite_level(uv, layer, low + 1.0);
|
||||
}
|
||||
return mix(sum_low * 0.25, sum_high * 0.25, mip_blend);
|
||||
}
|
||||
// Distant terrain tiles (lod/mesh.rs) count their UVs in blocks past 1: each block takes the
|
||||
// sprite again, as separate faces would under the clamped sampler. Sprite UVs in [0, 1] stay.
|
||||
@@ -148,10 +175,13 @@ fn tile_uv(uv: vec2<f32>) -> vec2<f32> {
|
||||
return uv - max(ceil(uv) - 1.0, vec2(0.0));
|
||||
}
|
||||
fn sprite(tiled: vec2<f32>, layer: u32) -> vec4<f32> {
|
||||
// terrain.fsh's two sampling paths: sampleRGSS for RGSS, sampleNearest otherwise.
|
||||
// The gradients come from the unwrapped UV, so the mip level holds across block seams.
|
||||
let du = dpdx(tiled);
|
||||
let dv = dpdy(tiled);
|
||||
return sprite_at(tiled, layer, dpdx(tiled), dpdy(tiled));
|
||||
}
|
||||
// `sprite` with the unwrapped UV's screen gradients given: the visibility buffer's resolve
|
||||
// (visbuffer.wgsl) works them out from the quad, as its neighbours may be other quads.
|
||||
fn sprite_at(tiled: vec2<f32>, layer: u32, du: vec2<f32>, dv: vec2<f32>) -> vec4<f32> {
|
||||
// terrain.fsh's two sampling paths: sampleRGSS for RGSS, sampleNearest otherwise.
|
||||
let uv = tile_uv(tiled);
|
||||
// The wrap shifts a face's UVs by whole blocks, so half of one tells a tile's face from a
|
||||
// block's (whose UVs stay inside its sprite).
|
||||
@@ -320,8 +350,10 @@ fn fade_code(w: f32) -> u32 {
|
||||
// (`mesher::Part`): first the quads with no cutout that are not fading, through
|
||||
// `fs_opaque`, which never discards, so a tiled GPU (Apple's) keeps hiding the pixels
|
||||
// behind them; then the rest through `fs_solid`. One discard anywhere in a pipeline's shader turned that hiding off for
|
||||
// all the terrain it drew. A quad the pass leaves out is decided before any of the
|
||||
// vertex's work and lies outside the clip volume: it makes no pixel.
|
||||
// all the terrain it drew. The draw lists give each pass only its own part; a mesh that
|
||||
// was not sorted (`Sides::unsorted`) gives both passes all of its quads, so each pass
|
||||
// still leaves out the other's: decided before any of the vertex's work, outside the
|
||||
// clip volume, it makes no pixel.
|
||||
fn opaque_quad(layer: u32, w: f32) -> bool {
|
||||
return (layer & 0x8000u) != 0u && fade_code(w) == 0u;
|
||||
}
|
||||
@@ -366,6 +398,134 @@ fn left_out() -> TerrainOut {
|
||||
return terrain_vertex(position, terrain_uv(uv, origin.w), layer, color, light, origin.xyz,
|
||||
origin.w);
|
||||
}
|
||||
// Solid section draws that read their vertices from the arena page (gfx.rs's `pulled`
|
||||
// pipelines; `mesher/packed.rs`): a draw's vertex indices below PACKED_BASE name vertices
|
||||
// (`TerrainVertex`, six words, as `TERRAIN_ATTRIBUTES` reads them), from PACKED_BASE on a
|
||||
// corner of the quad whose record is at that slot (`PACKED_BASE + 4 * slot + corner`).
|
||||
// Either way they come out as the six words of the vertex, so the work after is the same.
|
||||
// Binding 19: visbuffer.wgsl, in the same module, takes 2 to 18 of its own group 1.
|
||||
@group(1) @binding(19) var<storage, read> arena_page: array<vec2<u32>>;
|
||||
const PACKED_BASE: u32 = 0x10000000u;
|
||||
// A vertex's words: position bits, then UV, layer with light, colour.
|
||||
struct ArenaCorner {
|
||||
p: vec3<u32>,
|
||||
r: vec3<u32>,
|
||||
}
|
||||
fn arena_vertex(v: u32) -> ArenaCorner {
|
||||
let a = arena_page[v * 3u];
|
||||
let b = arena_page[v * 3u + 1u];
|
||||
let c = arena_page[v * 3u + 2u];
|
||||
return ArenaCorner(vec3(a.x, a.y, b.x), vec3(b.y, c.x, c.y));
|
||||
}
|
||||
// `packed::packed_vertex`: corner `corner` of the record whose words are lo (0 to 2) and
|
||||
// hi (3 to 5).
|
||||
fn packed_corner(lo: vec3<u32>, hi: vec3<u32>, corner: u32) -> ArenaCorner {
|
||||
let orient = (hi.z >> 12u) & 0x3ffffu;
|
||||
let block = vec3(orient & 15u, (orient >> 4u) & 15u, (orient >> 8u) & 15u);
|
||||
let axis = (orient >> 12u) & 3u;
|
||||
let side = (orient >> 14u) & 1u;
|
||||
let start = (orient >> 15u) & 3u;
|
||||
let dir = (orient >> 17u) & 1u;
|
||||
let step = (start + select(corner, 4u - corner, dir == 1u)) & 3u;
|
||||
let du = select(0u, 1u, step == 1u || step == 2u);
|
||||
let dw = select(0u, 1u, step >= 2u);
|
||||
var pos = block;
|
||||
pos[axis] = block[axis] + side;
|
||||
pos[(axis + 1u) % 3u] = block[(axis + 1u) % 3u] + du;
|
||||
pos[(axis + 2u) % 3u] = block[(axis + 2u) % 3u] + dw;
|
||||
let sel = lo.z >> 24u;
|
||||
let us = (sel >> (2u * corner)) & 1u;
|
||||
let vs = (sel >> (2u * corner + 1u)) & 1u;
|
||||
let u = (lo.z >> (6u * us)) & 63u;
|
||||
let v = (lo.z >> (12u + 6u * vs)) & 63u;
|
||||
var light = hi.z;
|
||||
if corner == 0u {
|
||||
light = hi.x >> 16u;
|
||||
} else if corner == 1u {
|
||||
light = hi.y;
|
||||
} else if corner == 2u {
|
||||
light = hi.y >> 12u;
|
||||
}
|
||||
light &= 0xfffu;
|
||||
let packed_light = ((light & 63u) << 2u) | (((light >> 6u) & 63u) << 10u);
|
||||
let g = (lo.y >> (8u * corner)) & 255u;
|
||||
let t = lo.x;
|
||||
let color = (g * ((t >> 16u) & 255u) / 255u) | ((g * ((t >> 8u) & 255u) / 255u) << 8u)
|
||||
| ((g * (t & 255u) / 255u) << 16u) | (t & 0xff000000u);
|
||||
return ArenaCorner(bitcast<vec3<u32>>(vec3<f32>(pos)),
|
||||
vec3((u << 10u) | (v << 26u), (hi.x & 0xffffu) | (packed_light << 16u), color));
|
||||
}
|
||||
// A solid draw's vertex `vertex`, and the slot its block is found by (`block_slots`, 256
|
||||
// slots a block): the vertex's own, or its quad's record's. `packed`: it has a record.
|
||||
struct SectionCorner {
|
||||
c: ArenaCorner,
|
||||
slot: u32,
|
||||
packed: bool,
|
||||
}
|
||||
fn section_corner(vertex: u32) -> SectionCorner {
|
||||
if vertex < PACKED_BASE {
|
||||
return SectionCorner(arena_vertex(vertex), vertex, false);
|
||||
}
|
||||
let slot = (vertex - PACKED_BASE) >> 2u;
|
||||
let corner = vertex & 3u;
|
||||
let a = arena_page[slot * 3u];
|
||||
let b = arena_page[slot * 3u + 1u];
|
||||
let c = arena_page[slot * 3u + 2u];
|
||||
// `packed::FULL`: the quad's four vertices, word 0 slots on.
|
||||
if (c.y & 0x80000000u) != 0u {
|
||||
return SectionCorner(arena_vertex(slot + a.x + corner), slot, true);
|
||||
}
|
||||
return SectionCorner(packed_corner(vec3(a.x, a.y, b.x), vec3(b.y, c.x, c.y), corner), slot,
|
||||
true);
|
||||
}
|
||||
// The colour as the vertex fetch converts a Unorm8x4 attribute, bit for bit (Apple's M1:
|
||||
// times 1/255; `unpack4x8unorm` and the correctly rounded c / 255 each differ from it for
|
||||
// some bytes, and moved a few pixels' colour or alpha cutout).
|
||||
fn unorm8x4(c: u32) -> vec4<f32> {
|
||||
return vec4<f32>(vec4(c & 255u, (c >> 8u) & 255u, (c >> 16u) & 255u, c >> 24u))
|
||||
* (1.0 / 255.0);
|
||||
}
|
||||
fn corner_layer(c: ArenaCorner) -> u32 {
|
||||
return c.r.y & 0xffffu;
|
||||
}
|
||||
fn corner_vertex(c: ArenaCorner, origin: vec4<f32>) -> TerrainOut {
|
||||
let uv = vec2(c.r.x & 0xffffu, c.r.x >> 16u);
|
||||
let light = vec2((c.r.y >> 16u) & 0xffu, c.r.y >> 24u);
|
||||
return terrain_vertex(bitcast<vec3<f32>>(c.p), terrain_uv(uv, origin.w), corner_layer(c),
|
||||
unorm8x4(c.r.z), light, origin.xyz, origin.w);
|
||||
}
|
||||
@vertex fn vs_section(@builtin(vertex_index) vertex: u32,
|
||||
@location(5) origin: vec4<f32>) -> TerrainOut {
|
||||
return corner_vertex(section_corner(vertex).c, origin);
|
||||
}
|
||||
@vertex fn vs_section_opaque(@builtin(vertex_index) vertex: u32,
|
||||
@location(5) origin: vec4<f32>) -> TerrainOut {
|
||||
let s = section_corner(vertex);
|
||||
if !opaque_quad(corner_layer(s.c), origin.w) { return left_out(); }
|
||||
return corner_vertex(s.c, origin);
|
||||
}
|
||||
@vertex fn vs_section_cutout(@builtin(vertex_index) vertex: u32,
|
||||
@location(5) origin: vec4<f32>) -> TerrainOut {
|
||||
let s = section_corner(vertex);
|
||||
if opaque_quad(corner_layer(s.c), origin.w) { return left_out(); }
|
||||
return corner_vertex(s.c, origin);
|
||||
}
|
||||
@vertex fn vs_section_flat(@builtin(vertex_index) vertex: u32) -> TerrainOut {
|
||||
let s = section_corner(vertex);
|
||||
return corner_vertex(s.c, slot_origins[block_slots[s.slot >> 8u]]);
|
||||
}
|
||||
@vertex fn vs_section_flat_opaque(@builtin(vertex_index) vertex: u32) -> TerrainOut {
|
||||
let s = section_corner(vertex);
|
||||
let origin = slot_origins[block_slots[s.slot >> 8u]];
|
||||
if !opaque_quad(corner_layer(s.c), origin.w) { return left_out(); }
|
||||
return corner_vertex(s.c, origin);
|
||||
}
|
||||
@vertex fn vs_section_flat_cutout(@builtin(vertex_index) vertex: u32) -> TerrainOut {
|
||||
let s = section_corner(vertex);
|
||||
let origin = slot_origins[block_slots[s.slot >> 8u]];
|
||||
if opaque_quad(corner_layer(s.c), origin.w) { return left_out(); }
|
||||
return corner_vertex(s.c, origin);
|
||||
}
|
||||
// HDR lighting: how far a surface's light carries it above paper white, 0 to 1. Only the
|
||||
// strongest light does, so dark stays as dark as in SDR; sky light by how the face meets the sun.
|
||||
fn hdr_scene(block: f32, sky: f32, facing: f32) -> f32 {
|
||||
@@ -448,7 +608,9 @@ fn hdr_mark(tier: u32, y: f32) -> f32 {
|
||||
}
|
||||
// `fs_solid` for the opaque pass (`opaque_quad`): no fade, no cutout, no discard.
|
||||
@fragment fn fs_opaque(in: TerrainOut) -> @location(0) vec4<f32> {
|
||||
hdr_normal = normalize(cross(dpdy(in.local), dpdx(in.local)));
|
||||
if globals.hdr_light.z != 0.0 {
|
||||
hdr_normal = normalize(cross(dpdy(in.local), dpdx(in.local)));
|
||||
}
|
||||
let texel = sprite(in.uv, in.layer & 0x7fffu);
|
||||
var color = texel * in.color;
|
||||
color = mix(globals.fog_color * vec4(1.0, 1.0, 1.0, color.a), color, in.visibility);
|
||||
@@ -463,7 +625,9 @@ fn hdr_mark(tier: u32, y: f32) -> f32 {
|
||||
fn solid_color(in: TerrainOut) -> vec4<f32> {
|
||||
let dx = dpdx(in.local);
|
||||
let dy = dpdy(in.local);
|
||||
hdr_normal = normalize(cross(dy, dx));
|
||||
if globals.hdr_light.z != 0.0 {
|
||||
hdr_normal = normalize(cross(dy, dx));
|
||||
}
|
||||
if faded(in.fade, in.local, dx, dy) { discard; }
|
||||
// Bit 15 of the layer marks opaque leaves (forceOpaque): no alpha cutout.
|
||||
let texel = sprite(in.uv, in.layer & 0x7fffu);
|
||||
@@ -475,6 +639,11 @@ fn solid_color(in: TerrainOut) -> vec4<f32> {
|
||||
}
|
||||
// The rest of a solid pixel's colour once it is kept: emission and fog.
|
||||
fn solid_finish(in: TerrainOut, texel: vec4<f32>, color: vec4<f32>) -> vec4<f32> {
|
||||
return solid_finish_at(in, texel, color, dpdx(in.uv), dpdy(in.uv));
|
||||
}
|
||||
// `solid_finish` with the UV's screen gradients given (`sprite_at`).
|
||||
fn solid_finish_at(in: TerrainOut, texel: vec4<f32>, color: vec4<f32>, du: vec2<f32>,
|
||||
dv: vec2<f32>) -> vec4<f32> {
|
||||
// Below 0.75, alpha tells HDR output (hdr.wgsl) a light source's pixel
|
||||
// shines, at which tier (mesher::hdr_tier) and how much (hdr_mark), by the
|
||||
// texel's own brightness, so its light level and face shading don't change
|
||||
@@ -482,27 +651,31 @@ fn solid_finish(in: TerrainOut, texel: vec4<f32>, color: vec4<f32>) -> vec4<f32>
|
||||
// parts, or all of it. Nothing else reads the frame's alpha.
|
||||
// Fog turns a far surface into the fog's colour: it takes the fog's light with it, or a
|
||||
// fully fogged mountain would stand out from the fog behind it.
|
||||
let fogged = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w)) * globals.fog_color.a;
|
||||
var emissive = hdr_scene_alpha(mix(hdr_scene(in.scene.x, in.scene.y, hdr_facing()),
|
||||
globals.hdr_light.y, fogged));
|
||||
if in.emission > 0u {
|
||||
// A light source's own face shines as one or stays as drawn, without the lighting.
|
||||
emissive = 1.0;
|
||||
let whole = in.emission > 4u;
|
||||
let tier = select(in.emission, in.emission - 4u, whole);
|
||||
let l = in.layer & 0x7fffu;
|
||||
let t = textureLoad(emission_thresholds, vec2(i32(l % SPRITE_SPLIT), i32(l / SPRITE_SPLIT)), 0);
|
||||
let y = brightness(texel.rgb);
|
||||
// All over means all over: the sprite's range is one frame's, and an animated sprite's
|
||||
// other frames dip below it. Fog hides a light source as it hides everything else.
|
||||
if whole || y >= t.r {
|
||||
emissive = hdr_mark(tier, y * (1.0 - fogged));
|
||||
// Without HDR output nothing reads the alpha, so its lighting is not worked out.
|
||||
var emissive = 1.0;
|
||||
if globals.hdr_light.z != 0.0 {
|
||||
let fogged = max(linear_fog(in.spherical, globals.fog.x, globals.fog.y),
|
||||
linear_fog(in.cylindrical, globals.fog.z, globals.fog.w)) * globals.fog_color.a;
|
||||
emissive = hdr_scene_alpha(mix(hdr_scene(in.scene.x, in.scene.y, hdr_facing()),
|
||||
globals.hdr_light.y, fogged));
|
||||
if in.emission > 0u {
|
||||
// A light source's own face shines as one or stays as drawn, without the lighting.
|
||||
emissive = 1.0;
|
||||
let whole = in.emission > 4u;
|
||||
let tier = select(in.emission, in.emission - 4u, whole);
|
||||
let l = in.layer & 0x7fffu;
|
||||
let t = textureLoad(emission_thresholds, vec2(i32(l % SPRITE_SPLIT), i32(l / SPRITE_SPLIT)), 0);
|
||||
let y = brightness(texel.rgb);
|
||||
// All over means all over: the sprite's range is one frame's, and an animated sprite's
|
||||
// other frames dip below it. Fog hides a light source as it hides everything else.
|
||||
if whole || y >= t.r {
|
||||
emissive = hdr_mark(tier, y * (1.0 - fogged));
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.emissive_layer != 0xffffffffu {
|
||||
// The high bit: an OptiFine overlay, drawn cutout (opaque where alpha >= 0.1).
|
||||
var mask = sprite(in.uv, in.emissive_layer & 0x7fffffffu);
|
||||
var mask = sprite_at(in.uv, in.emissive_layer & 0x7fffffffu, du, dv);
|
||||
if (in.emissive_layer & 0x80000000u) != 0u {
|
||||
mask.a = select(0.0, 1.0, mask.a >= 0.1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
|
||||
// The visibility buffer for solid terrain (gfx/visbuffer.rs, `HORIZON_VISBUFFER=1`), after
|
||||
// terrain.wgsl in one module. Pass 1 rasterises the flat solid draws writing depth and, per
|
||||
// pixel, the quad it shows: its arena page and its quad in that page (`vis_id`). Pass 2, one draw for up to 16 pages, runs the shading of `fs_opaque`/`solid_color`
|
||||
// once per pixel from that ID: it reads the quad's corners from its page as the vertex shaders
|
||||
// do, runs `terrain_vertex` on the
|
||||
// triangle the pixel falls in, interpolates its outputs with perspective-correct barycentrics
|
||||
// and works out the screen gradients `dpdx`/`dpdy` would give, so the texel, mip level, light
|
||||
// and fog come out as in the normal path.
|
||||
|
||||
// Bit 31: terrain is here; bit 30: the quad has a record (`mesher/packed.rs`); bits 24 to 29:
|
||||
// the arena page (`visbuffer::MAX_PAGES`); bits 0 to 23: the quad's record's slot in the page,
|
||||
// else its first vertex over 4 (a page holds at most `arena::PAGE_BYTES` / 24 bytes =
|
||||
// 11 184 810 slots, under 2^24).
|
||||
fn vis_id(page: u32, s: SectionCorner) -> u32 {
|
||||
let at = 0x80000000u | ((page & 0x3fu) << 24u);
|
||||
if s.packed {
|
||||
return at | 0x40000000u | s.slot;
|
||||
}
|
||||
return at | (s.slot >> 2u);
|
||||
}
|
||||
|
||||
// `sprite_at` (terrain.wgsl's `sample_rgss` over `sample_nearest`), sampling only what its
|
||||
// blends keep: where RGSS has not faded in (the texels span two pixels or more) the nearest
|
||||
// texel alone, where it has fully, RGSS alone, and its upper mip level only where it blends one
|
||||
// in. A blend of 0 or 1 gives exactly the one side, so the result is `sprite_at`'s; near
|
||||
// faces take one tap instead of nine.
|
||||
fn vis_sprite(tiled: vec2<f32>, layer: u32, du: vec2<f32>, dv: vec2<f32>) -> vec4<f32> {
|
||||
let uv = tile_uv(tiled);
|
||||
let tiled_face = any(tiled - uv >= vec2(0.5));
|
||||
let pixel = vec2(1.0 / globals.filtering.y);
|
||||
let texel_screen = sqrt(du * du + dv * dv);
|
||||
if u32(globals.filtering.x) != 1u {
|
||||
return sample_nearest(uv, layer, pixel, du, dv, texel_screen, tiled_face);
|
||||
}
|
||||
let max_texel = max(texel_screen.x, texel_screen.y);
|
||||
let min_pixel = min(pixel.x, pixel.y);
|
||||
let blend = smoothstep(min_pixel, min_pixel * 2.0, max_texel);
|
||||
var nearest = vec4(0.0);
|
||||
if blend < 1.0 {
|
||||
nearest = sample_nearest(uv, layer, pixel, du, dv, texel_screen, tiled_face);
|
||||
}
|
||||
if blend <= 0.0 {
|
||||
return nearest;
|
||||
}
|
||||
let du_len = length(du);
|
||||
let dv_len = length(dv);
|
||||
let effective = sqrt(min(du_len, dv_len) * max(du_len, dv_len));
|
||||
let exact = max(0.0, log2(effective / min_pixel));
|
||||
let low = floor(exact);
|
||||
let mip_blend = fract(exact);
|
||||
var offsets = array<vec2<f32>, 4>(
|
||||
vec2(0.125, 0.375), vec2(-0.125, -0.375), vec2(0.375, -0.125), vec2(-0.375, 0.125));
|
||||
var sum_low = vec4(0.0);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
sum_low += sprite_level(uv + offsets[i] * pixel, layer, low);
|
||||
}
|
||||
var rgss = sum_low * 0.25;
|
||||
if mip_blend > 0.0 {
|
||||
var sum_high = vec4(0.0);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
sum_high += sprite_level(uv + offsets[i] * pixel, layer, low + 1.0);
|
||||
}
|
||||
rgss = mix(rgss, sum_high * 0.25, mip_blend);
|
||||
}
|
||||
return mix(nearest, rgss, blend);
|
||||
}
|
||||
|
||||
struct VisIdOut {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
@location(0) @interpolate(flat) id: u32,
|
||||
}
|
||||
// The quads with no cutout that are not fading (`opaque_quad`): no discard, so a tiled GPU
|
||||
// keeps hiding what lies behind them. `page`: the draw's instance, its arena page
|
||||
// (`Flat::draw_paged`).
|
||||
@vertex fn vs_vis_opaque(
|
||||
@builtin(vertex_index) vertex: u32, @builtin(instance_index) page: u32
|
||||
) -> VisIdOut {
|
||||
var out: VisIdOut;
|
||||
let s = section_corner(vertex);
|
||||
let origin = slot_origins[block_slots[s.slot >> 8u]];
|
||||
if !opaque_quad(corner_layer(s.c), origin.w) {
|
||||
out.clip = vec4(2.0, 2.0, 2.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
let position = bitcast<vec3<f32>>(s.c.p);
|
||||
out.clip = fade_depth(globals.view_proj * vec4(position + origin.xyz, 1.0), origin.w);
|
||||
out.id = vis_id(page, s);
|
||||
return out;
|
||||
}
|
||||
@fragment fn fs_vis_id(in: VisIdOut) -> @location(0) u32 {
|
||||
return in.id;
|
||||
}
|
||||
|
||||
struct VisCutoutOut {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
@location(0) @interpolate(flat) id: u32,
|
||||
@location(1) uv: vec2<f32>,
|
||||
@location(2) @interpolate(flat) layer: u32,
|
||||
// The vertex colour's alpha (`terrain_vertex`'s `color.a`: the lightmap's).
|
||||
@location(3) alpha: f32,
|
||||
@location(4) @interpolate(flat) fade: u32,
|
||||
@location(5) local: vec3<f32>,
|
||||
}
|
||||
// The other quads: `solid_color`'s fade dither and alpha cutout, with no shading.
|
||||
@vertex fn vs_vis_cutout(
|
||||
@builtin(vertex_index) vertex: u32, @builtin(instance_index) page: u32
|
||||
) -> VisCutoutOut {
|
||||
var out: VisCutoutOut;
|
||||
let s = section_corner(vertex);
|
||||
let origin = slot_origins[block_slots[s.slot >> 8u]];
|
||||
let layer = corner_layer(s.c);
|
||||
if opaque_quad(layer, origin.w) {
|
||||
out.clip = vec4(2.0, 2.0, 2.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
let position = bitcast<vec3<f32>>(s.c.p);
|
||||
let uv = vec2(s.c.r.x & 0xffffu, s.c.r.x >> 16u);
|
||||
let light = vec2((s.c.r.y >> 16u) & 0xffu, s.c.r.y >> 24u);
|
||||
let pos = position + origin.xyz;
|
||||
out.clip = fade_depth(globals.view_proj * vec4(pos, 1.0), origin.w);
|
||||
out.id = vis_id(page, s);
|
||||
out.uv = terrain_uv(uv, origin.w);
|
||||
out.layer = layer;
|
||||
let block = max(f32(light.y), dynamic_block_light(pos));
|
||||
out.alpha = sample_lightmap(f32(light.x) / 16.0, block / 16.0).a;
|
||||
out.fade = fade_code(origin.w);
|
||||
out.local = position;
|
||||
return out;
|
||||
}
|
||||
@fragment fn fs_vis_cutout(in: VisCutoutOut) -> @location(0) u32 {
|
||||
let dx = dpdx(in.local);
|
||||
let dy = dpdy(in.local);
|
||||
if faded(in.fade, in.local, dx, dy) { discard; }
|
||||
let alpha = vis_sprite(in.uv, in.layer & 0x7fffu, dpdx(in.uv), dpdy(in.uv)).a * in.alpha;
|
||||
if alpha < 0.5 && (in.layer & 0x8000u) == 0u { discard; }
|
||||
return in.id;
|
||||
}
|
||||
|
||||
// Pass 2. Up to 16 arena pages (mesher.rs's TerrainVertex, 24 bytes: position, UV as two u16,
|
||||
// layer and light as u16 each, RGBA8 colour), a quad's 96 bytes as six vec4s; at bindings 0 and
|
||||
// 1 the block tables of all pages, page after page, `VIS_BLOCKS_PER_PAGE` entries each
|
||||
// (`Visbuffer::resolve` copies them from the flat draws' own), and the origins by section id
|
||||
// (terrain.wgsl's flat draws); pass 1's IDs.
|
||||
override VIS_BLOCKS_PER_PAGE: u32 = 1u;
|
||||
@group(1) @binding(2) var<storage, read> vis_page_0: array<vec4<u32>>;
|
||||
@group(1) @binding(3) var<storage, read> vis_page_1: array<vec4<u32>>;
|
||||
@group(1) @binding(4) var<storage, read> vis_page_2: array<vec4<u32>>;
|
||||
@group(1) @binding(5) var<storage, read> vis_page_3: array<vec4<u32>>;
|
||||
@group(1) @binding(6) var<storage, read> vis_page_4: array<vec4<u32>>;
|
||||
@group(1) @binding(7) var<storage, read> vis_page_5: array<vec4<u32>>;
|
||||
@group(1) @binding(8) var<storage, read> vis_page_6: array<vec4<u32>>;
|
||||
@group(1) @binding(9) var<storage, read> vis_page_7: array<vec4<u32>>;
|
||||
@group(1) @binding(10) var<storage, read> vis_page_8: array<vec4<u32>>;
|
||||
@group(1) @binding(11) var<storage, read> vis_page_9: array<vec4<u32>>;
|
||||
@group(1) @binding(12) var<storage, read> vis_page_10: array<vec4<u32>>;
|
||||
@group(1) @binding(13) var<storage, read> vis_page_11: array<vec4<u32>>;
|
||||
@group(1) @binding(14) var<storage, read> vis_page_12: array<vec4<u32>>;
|
||||
@group(1) @binding(15) var<storage, read> vis_page_13: array<vec4<u32>>;
|
||||
@group(1) @binding(16) var<storage, read> vis_page_14: array<vec4<u32>>;
|
||||
@group(1) @binding(17) var<storage, read> vis_page_15: array<vec4<u32>>;
|
||||
@group(1) @binding(18) var vis_ids: texture_2d<u32>;
|
||||
|
||||
// A quad's 96 bytes, six 16-byte words from `i` on, of the page in `slot`: one switch for
|
||||
// all of them, so a thread group whose pixels show several pages walks its cases once.
|
||||
struct VisQuad {
|
||||
q: array<vec4<u32>, 6>,
|
||||
}
|
||||
fn vis_quad(slot: u32, i: u32) -> VisQuad {
|
||||
switch slot {
|
||||
case 0u: {
|
||||
return VisQuad(array(vis_page_0[i], vis_page_0[i + 1u], vis_page_0[i + 2u],
|
||||
vis_page_0[i + 3u], vis_page_0[i + 4u], vis_page_0[i + 5u]));
|
||||
}
|
||||
case 1u: {
|
||||
return VisQuad(array(vis_page_1[i], vis_page_1[i + 1u], vis_page_1[i + 2u],
|
||||
vis_page_1[i + 3u], vis_page_1[i + 4u], vis_page_1[i + 5u]));
|
||||
}
|
||||
case 2u: {
|
||||
return VisQuad(array(vis_page_2[i], vis_page_2[i + 1u], vis_page_2[i + 2u],
|
||||
vis_page_2[i + 3u], vis_page_2[i + 4u], vis_page_2[i + 5u]));
|
||||
}
|
||||
case 3u: {
|
||||
return VisQuad(array(vis_page_3[i], vis_page_3[i + 1u], vis_page_3[i + 2u],
|
||||
vis_page_3[i + 3u], vis_page_3[i + 4u], vis_page_3[i + 5u]));
|
||||
}
|
||||
case 4u: {
|
||||
return VisQuad(array(vis_page_4[i], vis_page_4[i + 1u], vis_page_4[i + 2u],
|
||||
vis_page_4[i + 3u], vis_page_4[i + 4u], vis_page_4[i + 5u]));
|
||||
}
|
||||
case 5u: {
|
||||
return VisQuad(array(vis_page_5[i], vis_page_5[i + 1u], vis_page_5[i + 2u],
|
||||
vis_page_5[i + 3u], vis_page_5[i + 4u], vis_page_5[i + 5u]));
|
||||
}
|
||||
case 6u: {
|
||||
return VisQuad(array(vis_page_6[i], vis_page_6[i + 1u], vis_page_6[i + 2u],
|
||||
vis_page_6[i + 3u], vis_page_6[i + 4u], vis_page_6[i + 5u]));
|
||||
}
|
||||
case 7u: {
|
||||
return VisQuad(array(vis_page_7[i], vis_page_7[i + 1u], vis_page_7[i + 2u],
|
||||
vis_page_7[i + 3u], vis_page_7[i + 4u], vis_page_7[i + 5u]));
|
||||
}
|
||||
case 8u: {
|
||||
return VisQuad(array(vis_page_8[i], vis_page_8[i + 1u], vis_page_8[i + 2u],
|
||||
vis_page_8[i + 3u], vis_page_8[i + 4u], vis_page_8[i + 5u]));
|
||||
}
|
||||
case 9u: {
|
||||
return VisQuad(array(vis_page_9[i], vis_page_9[i + 1u], vis_page_9[i + 2u],
|
||||
vis_page_9[i + 3u], vis_page_9[i + 4u], vis_page_9[i + 5u]));
|
||||
}
|
||||
case 10u: {
|
||||
return VisQuad(array(vis_page_10[i], vis_page_10[i + 1u], vis_page_10[i + 2u],
|
||||
vis_page_10[i + 3u], vis_page_10[i + 4u], vis_page_10[i + 5u]));
|
||||
}
|
||||
case 11u: {
|
||||
return VisQuad(array(vis_page_11[i], vis_page_11[i + 1u], vis_page_11[i + 2u],
|
||||
vis_page_11[i + 3u], vis_page_11[i + 4u], vis_page_11[i + 5u]));
|
||||
}
|
||||
case 12u: {
|
||||
return VisQuad(array(vis_page_12[i], vis_page_12[i + 1u], vis_page_12[i + 2u],
|
||||
vis_page_12[i + 3u], vis_page_12[i + 4u], vis_page_12[i + 5u]));
|
||||
}
|
||||
case 13u: {
|
||||
return VisQuad(array(vis_page_13[i], vis_page_13[i + 1u], vis_page_13[i + 2u],
|
||||
vis_page_13[i + 3u], vis_page_13[i + 4u], vis_page_13[i + 5u]));
|
||||
}
|
||||
case 14u: {
|
||||
return VisQuad(array(vis_page_14[i], vis_page_14[i + 1u], vis_page_14[i + 2u],
|
||||
vis_page_14[i + 3u], vis_page_14[i + 4u], vis_page_14[i + 5u]));
|
||||
}
|
||||
default: {
|
||||
return VisQuad(array(vis_page_15[i], vis_page_15[i + 1u], vis_page_15[i + 2u],
|
||||
vis_page_15[i + 3u], vis_page_15[i + 4u], vis_page_15[i + 5u]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VisResolveOut {
|
||||
@builtin(position) clip: vec4<f32>,
|
||||
// The group of 16 pages this draw resolves: its instance (`Visbuffer::resolve`).
|
||||
@location(0) @interpolate(flat) chunk: u32,
|
||||
}
|
||||
@vertex fn vs_vis_resolve(@builtin(vertex_index) i: u32,
|
||||
@builtin(instance_index) chunk: u32) -> VisResolveOut {
|
||||
let p = vec2(f32(i32(i & 1u) * 4 - 1), f32(i32(i >> 1u) * 4 - 1));
|
||||
var out: VisResolveOut;
|
||||
out.clip = vec4(p, 0.0, 1.0);
|
||||
out.chunk = chunk;
|
||||
return out;
|
||||
}
|
||||
|
||||
// A framebuffer position's NDC (the world pass draws to the whole target).
|
||||
fn vis_ndc(at: vec2<f32>, size: vec2<f32>) -> vec2<f32> {
|
||||
return vec2(at.x / size.x * 2.0 - 1.0, 1.0 - at.y / size.y * 2.0);
|
||||
}
|
||||
|
||||
// Perspective-correct barycentrics of the point at NDC `p` on the triangle with clip-space
|
||||
// corners a, b, c (x, y, w): homogeneous 2D rasterisation (Olano and Greer). With M the matrix
|
||||
// of the corners as columns, the weights are M^-1 (p, 1) normalised, and the rows of M^-1 are
|
||||
// the corners' cross products over the determinant, which the normalisation cancels. Corners
|
||||
// behind the camera need no clipping. The rows are given (`vis_rows`), as three points share
|
||||
// them.
|
||||
fn vis_bary(rows: mat3x3<f32>, p: vec2<f32>) -> vec3<f32> {
|
||||
let l = vec3(p, 1.0) * rows;
|
||||
let sum = l.x + l.y + l.z;
|
||||
if abs(sum) < 1e-30 {
|
||||
return vec3(-1.0);
|
||||
}
|
||||
return l / sum;
|
||||
}
|
||||
fn vis_rows(a: vec3<f32>, b: vec3<f32>, c: vec3<f32>) -> mat3x3<f32> {
|
||||
return mat3x3(cross(b, c), cross(c, a), cross(a, b));
|
||||
}
|
||||
|
||||
// A corner's `terrain_vertex` from its six words: position, UV, layer and light, colour (the
|
||||
// vertex attributes `TERRAIN_ATTRIBUTES` in gfx.rs give: Float32x3, Uint16x2, Uint16, Uint8x2
|
||||
// with sky in the low byte, Unorm8x4).
|
||||
fn vis_corner(position: vec3<u32>, rest: vec3<u32>, origin: vec4<f32>) -> TerrainOut {
|
||||
let uv = vec2(rest.x & 0xffffu, rest.x >> 16u);
|
||||
let light = vec2((rest.y >> 16u) & 0xffu, rest.y >> 24u);
|
||||
return terrain_vertex(bitcast<vec3<f32>>(position), terrain_uv(uv, origin.w),
|
||||
rest.y & 0xffffu, unpack4x8unorm(rest.z), light, origin.xyz, origin.w);
|
||||
}
|
||||
|
||||
@fragment fn fs_vis_resolve(in: VisResolveOut) -> @location(0) vec4<f32> {
|
||||
let pixel = vec2<i32>(floor(in.clip.xy));
|
||||
let id = textureLoad(vis_ids, pixel, 0).x;
|
||||
let page = (id >> 24u) & 0x3fu;
|
||||
if (id & 0x80000000u) == 0u || page / 16u != in.chunk {
|
||||
discard;
|
||||
}
|
||||
let slot = page % 16u;
|
||||
let index = id & 0xffffffu;
|
||||
// The quad's first vertex (four of them from a multiple of 4) or its record.
|
||||
var first = 4u * index;
|
||||
var record = false;
|
||||
var lo = vec3(0u);
|
||||
var hi = vec3(0u);
|
||||
if (id & 0x40000000u) != 0u {
|
||||
first = index;
|
||||
// The record's six words from word 6 * index: two 16-byte words from the one it
|
||||
// starts in, at its start or its middle.
|
||||
let w = vis_quad(slot, (6u * index) >> 2u).q;
|
||||
let middle = ((6u * index) & 3u) != 0u;
|
||||
lo = select(w[0].xyz, vec3(w[0].zw, w[1].x), middle);
|
||||
hi = select(vec3(w[0].w, w[1].xy), w[1].yzw, middle);
|
||||
record = (hi.z & 0x80000000u) == 0u;
|
||||
if !record {
|
||||
// `packed::FULL`: its four vertices, word 0 slots on.
|
||||
first = index + lo.x;
|
||||
}
|
||||
}
|
||||
// The quad's block (256 slots: 64 quads of vertices, 256 records) names its section, as
|
||||
// for `vs_section_flat`.
|
||||
let block = select(index >> 6u, index >> 8u, (id & 0x40000000u) != 0u);
|
||||
let origin = slot_origins[block_slots[page * VIS_BLOCKS_PER_PAGE + block]];
|
||||
var p0: vec3<u32>;
|
||||
var r0: vec3<u32>;
|
||||
var p1: vec3<u32>;
|
||||
var r1: vec3<u32>;
|
||||
var p2: vec3<u32>;
|
||||
var r2: vec3<u32>;
|
||||
var p3: vec3<u32>;
|
||||
var r3: vec3<u32>;
|
||||
if record {
|
||||
let c0 = packed_corner(lo, hi, 0u);
|
||||
let c1 = packed_corner(lo, hi, 1u);
|
||||
let c2 = packed_corner(lo, hi, 2u);
|
||||
let c3 = packed_corner(lo, hi, 3u);
|
||||
p0 = c0.p;
|
||||
r0 = c0.r;
|
||||
p1 = c1.p;
|
||||
r1 = c1.r;
|
||||
p2 = c2.p;
|
||||
r2 = c2.r;
|
||||
p3 = c3.p;
|
||||
r3 = c3.r;
|
||||
} else {
|
||||
// Corner k is words 6k to 6k + 5 of the quad's 24.
|
||||
let words = vis_quad(slot, 6u * (first >> 2u)).q;
|
||||
let q0 = words[0];
|
||||
let q1 = words[1];
|
||||
let q2 = words[2];
|
||||
let q3 = words[3];
|
||||
let q4 = words[4];
|
||||
let q5 = words[5];
|
||||
p0 = q0.xyz;
|
||||
r0 = vec3(q0.w, q1.x, q1.y);
|
||||
p1 = vec3(q1.z, q1.w, q2.x);
|
||||
r1 = q2.yzw;
|
||||
p2 = q3.xyz;
|
||||
r2 = vec3(q3.w, q4.x, q4.y);
|
||||
p3 = vec3(q4.z, q4.w, q5.x);
|
||||
r3 = q5.yzw;
|
||||
}
|
||||
let c0 = (globals.view_proj * vec4(bitcast<vec3<f32>>(p0) + origin.xyz, 1.0)).xyw;
|
||||
let c1 = (globals.view_proj * vec4(bitcast<vec3<f32>>(p1) + origin.xyz, 1.0)).xyw;
|
||||
let c2 = (globals.view_proj * vec4(bitcast<vec3<f32>>(p2) + origin.xyz, 1.0)).xyw;
|
||||
let c3 = (globals.view_proj * vec4(bitcast<vec3<f32>>(p3) + origin.xyz, 1.0)).xyw;
|
||||
// The quad's triangles are 0 1 2 and 0 2 3 (`Renderer::quad_indices`, flatten.wgsl),
|
||||
// either side of the diagonal 0 2. Corner 1's weight in 0 1 2 is `vis_bary`'s row
|
||||
// cross(c2, c0) over the sum of the rows, whose sign is that of the triangle's determinant
|
||||
// (the sum is one over the point's w), dot(c1, cross(c2, c0)): the pixel lies in 0 1 2 when
|
||||
// the two agree. A degenerate 0 1 2 (no area, no pixels) leaves it to 0 2 3.
|
||||
let size = vec2<f32>(textureDimensions(vis_ids));
|
||||
let centre = in.clip.xy;
|
||||
let p = vis_ndc(centre, size);
|
||||
let diagonal = cross(c2, c0);
|
||||
let det = dot(c1, diagonal);
|
||||
let second = dot(vec3(p, 1.0), diagonal) * det < 0.0 || det == 0.0;
|
||||
let rows = vis_rows(c0, select(c1, c2, second), select(c2, c3, second));
|
||||
let b = vis_bary(rows, p);
|
||||
// dpdxFine and dpdyFine: the difference to the other pixel of this one's 2x2 quad in its
|
||||
// row and column, odd minus even, over the same triangle's plane.
|
||||
let odd = vec2<f32>(pixel & vec2(1));
|
||||
let toward = vec2(1.0) - 2.0 * odd;
|
||||
let bx = vis_bary(rows, vis_ndc(centre + vec2(toward.x, 0.0), size));
|
||||
let by = vis_bary(rows, vis_ndc(centre + vec2(0.0, toward.y), size));
|
||||
// `terrain_vertex` on the triangle's corners, one at a time into the sums, so that only
|
||||
// one corner's outputs are held at once; the first is the provoking one, whose flat
|
||||
// outputs (layer, emission, fade, emissive layer) the rasteriser keeps.
|
||||
var t = vis_corner(p0, r0, origin);
|
||||
let uv0 = t.uv;
|
||||
t.uv = b.x * uv0;
|
||||
var uv_x = bx.x * uv0;
|
||||
var uv_y = by.x * uv0;
|
||||
t.color = b.x * t.color;
|
||||
t.spherical = b.x * t.spherical;
|
||||
t.cylindrical = b.x * t.cylindrical;
|
||||
t.visibility = b.x * t.visibility;
|
||||
let t1 = vis_corner(select(p1, p2, second), select(r1, r2, second), origin);
|
||||
t.uv += b.y * t1.uv;
|
||||
uv_x += bx.y * t1.uv;
|
||||
uv_y += by.y * t1.uv;
|
||||
t.color += b.y * t1.color;
|
||||
t.spherical += b.y * t1.spherical;
|
||||
t.cylindrical += b.y * t1.cylindrical;
|
||||
t.visibility += b.y * t1.visibility;
|
||||
let t2 = vis_corner(select(p2, p3, second), select(r2, r3, second), origin);
|
||||
t.uv += b.z * t2.uv;
|
||||
uv_x += bx.z * t2.uv;
|
||||
uv_y += by.z * t2.uv;
|
||||
t.color += b.z * t2.color;
|
||||
t.spherical += b.z * t2.spherical;
|
||||
t.cylindrical += b.z * t2.cylindrical;
|
||||
t.visibility += b.z * t2.visibility;
|
||||
let du = (uv_x - t.uv) * toward.x;
|
||||
let dv = (uv_y - t.uv) * toward.y;
|
||||
// HDR output alone reads the light levels and the face's normal (as in `fs_opaque`):
|
||||
// `terrain_vertex` gives them as the block light over 16 and the sky light over 16, and the
|
||||
// section-relative position.
|
||||
if globals.hdr_light.z != 0.0 {
|
||||
let local = array(bitcast<vec3<f32>>(p0), bitcast<vec3<f32>>(select(p1, p2, second)),
|
||||
bitcast<vec3<f32>>(select(p2, p3, second)));
|
||||
// `t.scene` is still the first corner's.
|
||||
t.scene = b.x * t.scene + b.y * t1.scene + b.z * t2.scene;
|
||||
t.local = b.x * local[0] + b.y * local[1] + b.z * local[2];
|
||||
let dlx = (bx.x * local[0] + bx.y * local[1] + bx.z * local[2] - t.local) * toward.x;
|
||||
let dly = (by.x * local[0] + by.y * local[1] + by.z * local[2] - t.local) * toward.y;
|
||||
hdr_normal = normalize(cross(dly, dlx));
|
||||
}
|
||||
// `fs_opaque` and `solid_color` past their discards, which pass 1 already made.
|
||||
let texel = vis_sprite(t.uv, t.layer & 0x7fffu, du, dv);
|
||||
var color = texel * t.color;
|
||||
color = mix(globals.fog_color * vec4(1.0, 1.0, 1.0, color.a), color, t.visibility);
|
||||
return solid_finish_at(t, texel, color, du, dv);
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
{
|
||||
"horizon.options.controller": "المراقب المالي",
|
||||
"horizon.options.vibration": "الاهتزاز",
|
||||
"horizon.options.adaptive_triggers": "المشغلات التكيفية",
|
||||
"horizon.options.lightbar": "الشريط الضوئي",
|
||||
"horizon.options.look_sensitivity": "حساسية النظر",
|
||||
"horizon.options.invert_x": "عكس محور النظر X",
|
||||
"horizon.options.invert_y": "عكس محور النظر Y",
|
||||
"horizon.options.sneak_button": "زر التسلل",
|
||||
"horizon.options.move_deadzone": "المنطقة الميتة للحركة",
|
||||
"horizon.options.look_deadzone": "المنطقة الميتة للنظر",
|
||||
"horizon.options.button_glyphs": "الحروف الرسومية للأزرار",
|
||||
"horizon.options.menu_navigation": "التنقل في القائمة",
|
||||
"horizon.options.on_screen_keyboard": "لوحة المفاتيح على الشاشة",
|
||||
"horizon.options.chat_delay": "تأخير الدردشة",
|
||||
"horizon.options.lightbar.health": "الصحة",
|
||||
"horizon.options.lightbar.dimension": "البعد",
|
||||
"horizon.options.glyphs.auto": "تلقائي",
|
||||
"horizon.options.glyphs.xbox": "اكس بوكس",
|
||||
"horizon.options.glyphs.playstation": "بلاي ستيشن",
|
||||
"horizon.options.glyphs.nintendo": "نينتندو",
|
||||
"horizon.options.menu_focus": "التركيز",
|
||||
"horizon.options.menu_pointer": "المؤشر",
|
||||
"horizon.options.keyboard.auto": "تلقائي",
|
||||
"horizon.options.keyboard.steam": "البخار",
|
||||
"horizon.options.unavailable": "هذه الميزة غير متوفرة بعد في Horizon.",
|
||||
"horizon.options.search": "إعدادات البحث...",
|
||||
"horizon.pages.mods": "تعديل",
|
||||
"horizon.pages.shader_packs": "حزم شادر",
|
||||
"horizon.pages.shader_options": "خيارات التظليل",
|
||||
"horizon.pages.controller": "إعدادات وحدة التحكم",
|
||||
"horizon.pages.controller_binds": "ربط وحدة التحكم",
|
||||
"horizon.pages.extras": "إضافات Horizon",
|
||||
"horizon.pages.settings": "إعدادات Horizon",
|
||||
"horizon.pages.agent": "وكيل منظمة العفو الدولية",
|
||||
"horizon.split.leave": "غادر",
|
||||
"horizon.pages.split_settings": "إعدادات تقسيم الشاشة",
|
||||
"horizon.pages.split_profiles": "من يلعب؟",
|
||||
"horizon.pages.split_setup": "إعداد تقسيم الشاشة",
|
||||
"horizon.pages.music": "موسيقى",
|
||||
"horizon.pages.music.menu": "القائمة",
|
||||
"horizon.pages.music.overworld": "العالم الخارجي",
|
||||
"horizon.pages.music.creative": "إبداعي",
|
||||
"horizon.pages.music.underwater": "تحت الماء",
|
||||
"horizon.pages.music.end": "النهاية",
|
||||
"horizon.pages.music.boss": "رئيس",
|
||||
"horizon.pages.music.credits": "الاعتمادات",
|
||||
"horizon.pages.music.overworld_biomes": "المناطق الأحيائية في العالم الخارجي",
|
||||
"horizon.pages.music.nether_biomes": "المناطق الأحيائية السفلى",
|
||||
"horizon.pages.music.end_biomes": "نهاية المناطق الأحيائية",
|
||||
"horizon.pages.music.other": "موسيقى أخرى",
|
||||
"horizon.pages.music.empty": "لا توجد مقطوعات موسيقية: لا يتم تحميل أصوات اللعبة.",
|
||||
"horizon.pages.music.tooltip": "المسارات المضمنة، حسب الموقف: العب واحدة الآن، أو قم بإيقاف تشغيل أي منها حتى تتوقف اللعبة عن اختيارها.",
|
||||
"horizon.pages.settings.tooltip": "إعدادات Horizon الخاصة: الرسومات ووحدات التحكم والإضافات والتعديلات.",
|
||||
"horizon.pages.graphics.tooltip": "الرسومات ببساطة: كل إعداد يوضح تكلفته.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "كل إعدادات الرسومات، بما في ذلك Horizon، وتكلفة كل منها.",
|
||||
"horizon.pages.controller.tooltip": "وحدات التحكم: الأزرار والعصي والاهتزاز والتنقل في القائمة.",
|
||||
"horizon.pages.extras.tooltip": "الإضافات الاختيارية، كلها متوقفة بشكل افتراضي: تظل اللعبة عادية حتى تقوم بتشغيلها.",
|
||||
"horizon.pages.agent.tooltip": "اسمح لوكيل الذكاء الاصطناعي الخاص بك باللعب: عملاء Horizon-ctl وMCP على هذا الكمبيوتر.",
|
||||
"horizon.pages.split_profiles.tooltip": "من يلعب: ملف تعريف محفوظ في مجلد اللعبة هذا.",
|
||||
"horizon.pages.split_setup.tooltip": "النوافذ والشاشات ومن يلعب أين.",
|
||||
"horizon.pages.shader_packs.tooltip": "حزم Shader من مجلد Shaderpacks: الخاصة بشركة Horizon وتلك الموجودة في تنسيق التظليل/. واحد يعمل في وقت واحد.",
|
||||
"horizon.pages.mods.tooltip": "التعديلات المعدة لـ Horizon، من مجلد Horizon-mods. يعمل كل منها في وضع الحماية ويظل مغلقًا حتى تقوم بتشغيله.",
|
||||
"horizon.pages.open.graphics": "الرسومات...",
|
||||
"horizon.pages.open.graphics_advanced": "رسومات متقدمة...",
|
||||
"horizon.pages.open.shader_packs": "حزم التظليل...",
|
||||
"horizon.pages.open.controller": "إعدادات وحدة التحكم...",
|
||||
"horizon.pages.open.split_setup": "شاشة مقسمة...",
|
||||
"horizon.pages.open.extras": "إضافات...",
|
||||
"horizon.pages.open.mods": "تعديل...",
|
||||
"horizon.pages.open.agent": "وكيل الذكاء الاصطناعي...",
|
||||
"horizon.pages.open.horizon_graphics": "رسومات Horizon...",
|
||||
"horizon.pages.open.controller_binds": "ربط وحدة التحكم...",
|
||||
"horizon.pages.open.horizon_settings": "إعدادات Horizon...",
|
||||
"horizon.pages.open.music": "موسيقى...",
|
||||
"horizon.mods.empty": "لا توجد تعديلات: ضع ملفات .hmod في مجلد Horizon-mods",
|
||||
"horizon.mods.stopped": " (متوقف)",
|
||||
"horizon.mods.not_built": " (لم يتم بناؤه)",
|
||||
"horizon.mods.dev": " (ديف)",
|
||||
"horizon.mods.settings": "الإعدادات",
|
||||
"horizon.mods.in_world": " (اترك العالم ليتغير)",
|
||||
"horizon.mods.open_folder": "افتح مجلد التعديل",
|
||||
"horizon.shader_packs.empty": "لا توجد حزم تظليل: ضعها في مجلدshaderpacks",
|
||||
"horizon.shader_options.open": "خيارات التظليل...",
|
||||
"horizon.shader_options.profile": "الملف الشخصي",
|
||||
"horizon.shader_options.loading": "قراءة خيارات حزمة التظليل...",
|
||||
"horizon.agent.copy_details": "نسخ تفاصيل الاتصال",
|
||||
"horizon.multiplayer.join": "انضم",
|
||||
"horizon.multiplayer.cancel_reconnecting": "إلغاء إعادة الاتصال",
|
||||
"horizon.multiplayer.join_friend": "انضم إلى صديق",
|
||||
"horizon.multiplayer.invite_code": "رمز الدعوة",
|
||||
"horizon.multiplayer.relay": "التتابع (اختياري)",
|
||||
"Preset": "محددة مسبقا",
|
||||
"LOD Distance": "مسافة اللد",
|
||||
"Far Detail": "تفاصيل بعيدة",
|
||||
"Cave Culling": "إعدام الكهف",
|
||||
"Entity Culling": "إعدام الكيان",
|
||||
"Chunk Updates": "تحديثات القطعة",
|
||||
"Upload Budget": "تحميل الميزانية",
|
||||
"Sky": "السماء",
|
||||
"Fog": "ضباب",
|
||||
"Weather": "الطقس",
|
||||
"Animated Textures": "القوام المتحركة",
|
||||
"Present Mode": "الوضع الحالي",
|
||||
"Frame Pacing": "سرعة الإطار",
|
||||
"Worker Threads": "خيوط العمال",
|
||||
"Resolution Scale": "مقياس القرار",
|
||||
"Anti-aliasing": "مكافحة التعرج",
|
||||
"Upscaling": "الارتقاء",
|
||||
"Paper White": "ورقة بيضاء",
|
||||
"Peak Brightness": "ذروة السطوع",
|
||||
"Black Level": "المستوى الأسود",
|
||||
"UI Brightness": "سطوع واجهة المستخدم",
|
||||
"Battery Mode": "وضع البطارية",
|
||||
"Graphics API": "واجهة برمجة تطبيقات الرسومات",
|
||||
"Translucent Batching": "الخلط الشفاف",
|
||||
"Performance Overlay": "تراكب الأداء",
|
||||
"Full Detail Distance": "مسافة التفاصيل الكاملة",
|
||||
"On": "على",
|
||||
"Off": "إيقاف",
|
||||
"Unlimited": "غير محدود",
|
||||
"Auto": "تلقائي",
|
||||
"Custom": "مخصص",
|
||||
"Performance": "الأداء",
|
||||
"Balanced": "متوازن",
|
||||
"Quality": "الجودة",
|
||||
"Ultra": "الترا",
|
||||
"Ultra Performance": "أداء فائق",
|
||||
"Native": "أصلي",
|
||||
"Full": "كامل",
|
||||
"Reduced": "مخفض",
|
||||
"Efficient": "كفاءة",
|
||||
"Precise": "دقيق",
|
||||
"Auto (VSync)": "تلقائي (VSync)",
|
||||
"FIFO": "يصرف أولاً",
|
||||
"Mailbox": "صندوق البريد",
|
||||
"Immediate": "فوري",
|
||||
"Vulkan": "فولكان",
|
||||
"DirectX 12": "دايركت اكس 12",
|
||||
"OpenGL": "برنامج OpenGL",
|
||||
"FPS and Graph": "FPS والرسم البياني",
|
||||
"FPS": "إطارا في الثانية",
|
||||
"TAA": "تأ",
|
||||
"FXAA": "فكسا",
|
||||
"No cost": "لا تكلفة",
|
||||
"Low cost": "تكلفة منخفضة",
|
||||
"Medium cost": "تكلفة متوسطة",
|
||||
"High cost": "تكلفة عالية",
|
||||
"Saves time": "يوفر الوقت",
|
||||
"HDR": "تقرير التنمية البشرية",
|
||||
"Graphics": "الرسومات",
|
||||
"Done": "تم",
|
||||
"This feature is not yet available in Horizon.": "هذه الميزة غير متوفرة بعد في Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "لا توفر شاشتك أو نظامك مخرجات HDR.",
|
||||
"Turn HDR on to use it.": "قم بتشغيل HDR لاستخدامه.",
|
||||
"Your GPU or graphics API doesn't support it.": "وحدة معالجة الرسومات (GPU) أو واجهة برمجة تطبيقات الرسومات الخاصة بك لا تدعمها.",
|
||||
"%s chunks": "%s قطع",
|
||||
"%s per frame": "%s لكل إطار",
|
||||
"%s MiB/frame": "%s MiB/الإطار",
|
||||
"%s KiB/frame": "%s كيلو بايت/الإطار",
|
||||
"Auto (%s)": "تلقائي (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "نطاق التضاريس كامل التفاصيل. يمكن لمسافة LOD أن تمتد إلى عوالم محلية أبعد.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "التضاريس البعيدة الخشنة في العوالم المحلية. تتوقف وحدات معالجة الرسومات المدمجة عند 128 قطعة.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "تم تقليل الرسم على التضاريس البعيدة: بشكل أسرع على وحدة معالجة الرسومات المدمجة هذه.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "يتم تقليل الرسوم على التضاريس البعيدة بشكل أكثر خشونة، أما بالنسبة لوحدات معالجة الرسومات فإنها تبطئ.",
|
||||
"Field of view. Wider shows more terrain at once.": "مجال الرؤية. يُظهر العرض الأوسع المزيد من التضاريس في وقت واحد.",
|
||||
"Caps frames per second; a cap saves power and heat.": "قبعات الإطارات في الثانية الواحدة؛ غطاء يوفر الطاقة والحرارة.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "في انتظار تحديث الشاشة: بدون تمزق، وقليل من زمن الوصول.",
|
||||
"Size of menus and the HUD.": "حجم القوائم وHUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "يضبط الخيارات أدناه معًا. تغيير واحد يجعله مخصصًا.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "الحمم البركانية والنار وأشعة الشمس تتألق أكثر من اللون الأبيض. يحتاج إلى شاشة HDR.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "اللون الأسود الداكن لشاشتك. 0 لـ OLED؛ تتوهج شاشات LCD قليلاً.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "كيف تحصل على النقاط البارزة الساطعة. تلقائي: ذروة تقارير النظام الخاص بك.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "كيف تبدو الورق والقوائم البيضاء الزاهية. تلقائي: سطح المكتب الخاص بك باللون الأبيض.",
|
||||
"How bright the HUD and menus are, against paper white.": "مدى سطوع شاشة HUD والقوائم مقابل الورق الأبيض.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "إلى أي مدى يستمر الغوغاء والمحاصيل والآلات في العمل. وقت وحدة المعالجة المركزية.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "إلى أي مدى يتم رسم الغوغاء والعناصر، كنسبة من المعدل الطبيعي.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "يتخطى التضاريس المخفية خلف الأقسام المغلقة. قبالة تكاليف الكثير.",
|
||||
"Hides entities behind opaque blocks.": "يخفي الكيانات خلف الكتل غير الشفافة.",
|
||||
"No entity occlusion culling.": "لا يوجد إعدام انسداد الكيان.",
|
||||
"CPU occlusion test runs this frame.": "يقوم اختبار انسداد وحدة المعالجة المركزية بتشغيل هذا الإطار.",
|
||||
"Occlusion checks run on a worker thread.": "يتم تشغيل عمليات فحص الانسداد على مؤشر ترابط عامل.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "تم تحميل المقاطع المتشابكة لكل إطار. الجزء السفلي أكثر سلاسة أثناء التحميل.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "ظلال ناعمة في الزوايا. تكاليف بناء قطعة قطعة فقط.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "نسخ أصغر من أنسجة الكتل للتضاريس البعيدة: أقل وميضًا.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "يمزج ألوان العشب والماء عبر المناطق الأحيائية عند بناء القطع.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "طبقة السحابة: معطلة، مسطحة (سريعة) أو ثلاثية الأبعاد (فاخرة).",
|
||||
"How far clouds reach, in chunks.": "إلى أي مدى تصل السحب، في قطع.",
|
||||
"Draws the sky, sun, moon and stars.": "يرسم السماء والشمس والقمر والنجوم.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "ضباب المسافة. يظهر Off حافة العالم المحمل.",
|
||||
"Draws rain and snow.": "يرسم المطر والثلج.",
|
||||
"How far rain and snow fall around you.": "إلى أي مدى تسقط الأمطار والثلوج من حولك.",
|
||||
"How many particles effects spawn.": "كم عدد الجسيمات التي تفرخ الآثار.",
|
||||
"Round shadows under mobs and items.": "ظلال مستديرة تحت الغوغاء والعناصر.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "الأوراق بها فجوات شفافة. قبالة يرسمهم الصلبة.",
|
||||
"Sorts water and glass more exactly behind each other.": "يقوم بفرز الماء والزجاج بشكل أكثر دقة خلف بعضهما البعض.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "أي قطعة سيتم إعادة بنائها تنتظر الإطار: لا شيء، قريب أو خاص بك.",
|
||||
"Animates water, lava, fire and other moving textures.": "ينعش الماء والحمم والنار وغيرها من القوام المتحرك.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "بكسل العالم لكل بكسل الشاشة. أقل من 100% أسرع على وحدة معالجة الرسومات.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "ينعم الحواف الخشنة. FXAA: أكثر ليونة. TAA: قد تتبع الأشياء الأكثر سلاسة وتحريكًا.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "يجعل العالم أصغر حجمًا ويعيد بنائه باستخدام TAA. أسرع وأكثر ليونة.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "إخراج HDR على شاشة HDR: تتألق الحمم البركانية والنار وأشعة الشمس بشكل أكثر سطوعًا.",
|
||||
"How frames reach the screen while VSync is off.": "كيفية وصول الإطارات إلى الشاشة أثناء إيقاف تشغيل VSync.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "ينام بكفاءة حتى الإطار التالي؛ يدور بدقة، مثل الفانيليا.",
|
||||
"Fills the screen (borderless).": "يملأ الشاشة (بلا حدود).",
|
||||
"Brightens dark places.": "يضيء الأماكن الداكنة.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "يخفض معدل الإطارات عند تصغيره أو في وضع الخمول أيضًا.",
|
||||
"Sways the view while walking.": "يتأرجح المنظر أثناء المشي.",
|
||||
"Fades newly loaded chunks in.": "يتلاشى القطع المحملة حديثًا.",
|
||||
"Darkens the screen's edges.": "لتغميق حواف الشاشة.",
|
||||
"Shows when the world saves.": "يظهر عندما ينقذ العالم.",
|
||||
"Shows attack cooldown.": "يظهر تباطؤ الهجوم.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 إطارًا في الثانية (30 في القوائم أو غير مركزة)، نصف العمال: طاقة أقل.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "الخيوط التي تولد القطع الخفيفة والشبكية. البداية التالية.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan، ثم DirectX 12 (Windows) أو OpenGL (Linux) حيث يفشل. البداية التالية.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "الماء والزجاج في مخزن مؤقت واحد: عدد أقل من مكالمات السحب، والمزيد من VRAM. البداية التالية.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "يتم إرسال بيانات الشبكة إلى وحدة معالجة الرسومات لكل إطار. الجزء السفلي أكثر سلاسة أثناء التحميل.",
|
||||
"Frame rate and frame times over the game, with a graph.": "معدل الإطارات وأوقات الإطار خلال اللعبة، مع رسم بياني.",
|
||||
"horizon.multiplayer.finding_friend": "البحث عن عالم صديقك...",
|
||||
"horizon.multiplayer.not_signed_in": "هذا الخادم في وضع الاتصال بالإنترنت. قم بتسجيل الدخول باستخدام حساب Microsoft الخاص بك في Horizon Launcher للانضمام إليه.",
|
||||
"horizon.multiplayer.invalid_address": "عنوان الخادم غير صالح: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "تواصل مباشرة مع صديقك..",
|
||||
"horizon.multiplayer.connected_friend_relay": "متصل بصديقك من خلال التتابع...",
|
||||
"General": "عام",
|
||||
"Advanced": "متقدم",
|
||||
"Undo": "تراجع",
|
||||
"Search options": "خيارات البحث",
|
||||
"Shortcut": "الاختصار",
|
||||
"Distance": "المسافة",
|
||||
"Display": "عرض",
|
||||
"Interface": "واجهة",
|
||||
"Sky and weather": "السماء والطقس",
|
||||
"Lighting and entities": "الإضاءة والكيانات",
|
||||
"Textures": "القوام",
|
||||
"Image": "صورة",
|
||||
"Chunk loading": "تحميل قطعة",
|
||||
"Culling": "إعدام",
|
||||
"Power": "القوة",
|
||||
"Frames": "إطارات",
|
||||
"Overlay": "تراكب",
|
||||
"Output": "الإخراج",
|
||||
"Brightness": "السطوع",
|
||||
"How far you see, the window and the HUD.": "إلى أي مدى ترى النافذة وشاشة HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "السماء والطقس والإضاءة والقوام والصورة.",
|
||||
"Chunk loading, culling and power use.": "تحميل القطع وإعدامها واستخدام الطاقة.",
|
||||
"How frames reach the screen, and the performance overlay.": "كيفية وصول الإطارات إلى الشاشة وتراكب الأداء.",
|
||||
"Brighter-than-white output on an HDR display.": "إخراج أكثر سطوعًا من اللون الأبيض على شاشة HDR.",
|
||||
"Results for \"%s\"": "نتائج ل \"%s\"",
|
||||
"No settings match.": "لا توجد إعدادات متطابقة.",
|
||||
"horizon.title.not_affiliated": "لا ينتمي إلى Mojang أو Microsoft",
|
||||
"horizon.extras.header.lighting": "الإضاءة",
|
||||
"horizon.extras.header.third_person": "شخص ثالث",
|
||||
"horizon.extras.header.particles": "الجسيمات",
|
||||
"horizon.extras.header.inventory": "المخزون",
|
||||
"horizon.extras.header.chat": "الدردشة",
|
||||
"horizon.extras.header.multiplayer": "متعددة اللاعبين",
|
||||
"horizon.extras.header.sound": "الصوت",
|
||||
"horizon.extras.header.menus": "القوائم",
|
||||
"horizon.extras.fullbright": "فولبرايت",
|
||||
"horizon.extras.dynamic_lights": "أضواء ديناميكية",
|
||||
"horizon.extras.own_name_tag": "علامة الاسم الخاصة",
|
||||
"horizon.extras.hide_block_particles": "إخفاء جزيئات كسر الكتلة",
|
||||
"horizon.extras.auto_reconnect": "إعادة الاتصال التلقائي",
|
||||
"horizon.extras.ui_sound_reserve": "احتفظ بقنوات لأصوات واجهة المستخدم",
|
||||
"horizon.extras.music_page": "صفحة الموسيقى",
|
||||
"horizon.extras.mouse_tweaks": "الضوابط بتعديل الماوس",
|
||||
"horizon.extras.mouse_tweaks_wheel": "عجلة التمرير تنقل العناصر",
|
||||
"horizon.extras.sort": "أزرار الفرز",
|
||||
"horizon.extras.sort_key": "مفتاح الفرز",
|
||||
"horizon.extras.no_ai": "لا يوجد ذكاء اصطناعي",
|
||||
"horizon.extras.agent_control": "مراقبة الوكيل",
|
||||
"horizon.extras.discord_presence": "وجود الخلاف الغني",
|
||||
"horizon.extras.splitscreen": "تقسيم الشاشة",
|
||||
"horizon.extras.chat_animation": "دردشة سلسة",
|
||||
"horizon.extras.chat_indicator": "مؤشر الرسالة",
|
||||
"horizon.extras.wheel_cycles_settings": "عجلة التمرير تغير الإعدادات",
|
||||
"horizon.extras.pack_browser": "متصفح حزمة الموارد",
|
||||
"horizon.extras.fullbright.tooltip": "إظهار الكتل والكيانات في الضوء الكامل، حتى في المناطق المظلمة.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "تم حمل المشاعل ومصادر الضوء الأخرى أو إسقاطها أو إشعال النار فيها على الكتل المحيطة بها.",
|
||||
"horizon.extras.own_name_tag.tooltip": "يظهر اسمك فوق رأسك بصيغة الغائب، كما يراه اللاعبون الآخرون. يكون مخفيًا عندما يخفي الخادم أسماء فريقك أو عندما تكون غير مرئي.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "يخفي جزيئات الحطام التي تنفجر من كتلة مكسورة وتتفتت من تلك التي قمت بتعدينها. وتبقى الجزيئات الأخرى.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "إعادة الانضمام إلى الخادم بعد انقطاع الاتصال أو إعادة التشغيل، وليس بعد ركلة أو حظر أبدًا.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "تترك الأصوات في العالم 16 قناة صوتية من أصل 247 قناة مجانية، لذا فإن طوفانًا منها (انفجار كبير) لا يمكنه إسكات نقرات القائمة.",
|
||||
"horizon.extras.music_page.tooltip": "تحصل الموسيقى والأصوات على صفحة موسيقى...: قم بتشغيل أي مسار مضمن الآن، أو قم بإيقاف تشغيله حتى تتوقف اللعبة عن اختياره. لا شيء يتغير حتى تقوم بإيقاف تشغيل المسار.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "يؤدي السحب الأيمن إلى وضع عنصر واحد في كل فتحة، بينما يقوم السحب الأيسر بتجميع العنصر المحفوظ، بينما يقوم Shift + السحب الأيسر بنقل كل فتحة.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "يرسل الجزء السفلي عنصرًا واحدًا من الفتحة إلى الجانب الآخر، بينما يقوم الجزء العلوي بإحضار عنصر واحد مرة أخرى.",
|
||||
"horizon.extras.sort.tooltip": "يقوم بفرز المخزون والصناديق والبراميل وصناديق النهاية وصناديق الشولكر بترتيب علامات تبويب إبداعية، ودمج الأكوام.",
|
||||
"horizon.extras.sort_key.tooltip": "فرز الجزء الموجود تحت المؤشر. انقر، ثم اضغط على مفتاح؛ الهروب يفككه.",
|
||||
"horizon.extras.no_ai.tooltip": "يخفي ويوقف تشغيل كل ميزات الذكاء الاصطناعي: التحكم بالوكيل، Horizon-ctl وخوادم MCP. مفتاح المشغل أيضًا.",
|
||||
"horizon.extras.agent_control.tooltip": "يمكن لوكيل الذكاء الاصطناعي الخاص بك لعب هذه اللعبة من خلال Horizon-ctl أو عميل MCP، على هذا الكمبيوتر فقط.",
|
||||
"horizon.extras.discord_presence.tooltip": "يعرض Playing Horizon على ملف تعريف Discord الخاص بك أثناء تشغيل Discord: القوائم، أو اللاعب الفردي أو المتعدد، والبعد ووقت اللعب. لا يوجد أبدًا اسم عالمي أو عنوان خادم.",
|
||||
"Calibrate...": "معايرة...",
|
||||
"Calibrate Anyway...": "معايرة على أية حال...",
|
||||
"Use Automatic Setup": "استخدم الإعداد التلقائي",
|
||||
"Shortcut: %s": "الاختصار: %s",
|
||||
"Next": "التالي",
|
||||
"HDR Calibration %s/%s: %s": "معايرة HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "لتعيين الصور الثلاثة أدناه بملء الشاشة، صورة اختبارية واحدة في كل مرة.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "المفتاح الذي يفتح هذه الشاشة من اللعبة. غير ملزمة في البداية.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "لم توضح شاشتك مدى سطوعها: قم بالمعايرة لإعدادها.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "تم إعداده بالفعل للعرض الخاص بك. قم بالمعايرة فقط إذا كان تقرير HDR يبدو خاطئًا.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "يعيد ذروة السطوع والورق الأبيض إلى إعداد شاشتك.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "قم بإعداد العرض الخاص بك. اتركه على الوضع التلقائي ما لم يبدو خاطئًا.",
|
||||
"Lower it until the logo just disappears into the black.": "قم بخفضه حتى يختفي الشعار باللون الأسود.",
|
||||
"Raise it until the logo just disappears into the bright square.": "ارفعه حتى يختفي الشعار في المربع المشرق.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "اجعل البطاقة بيضاء اللون بحيث تكون مريحة عند النظر إليها لفترة طويلة، في الضوء الذي تلعب فيه.",
|
||||
"horizon.splitscreen.profiles.forget": "ننسى",
|
||||
"horizon.splitscreen.profiles.create": "إنشاء",
|
||||
"horizon.splitscreen.profiles.you": " - أنت",
|
||||
"horizon.splitscreen.profiles.forget_named": "ننسى %s",
|
||||
"horizon.splitscreen.profiles.previous": "السابق",
|
||||
"horizon.splitscreen.profiles.next": "التالي",
|
||||
"horizon.splitscreen.profiles.new_button": "ملف تعريف جديد...",
|
||||
"horizon.splitscreen.profiles.forget_title": "هل نسيت الملف الشخصي؟",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "فإنه يترك قائمة هذا المثيل.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "يتم الاحتفاظ بالتقدم العالمي والإعدادات.",
|
||||
"horizon.splitscreen.profiles.new_title": "ملف تعريف جديد",
|
||||
"horizon.splitscreen.profiles.name_hint": "الاسم (من 3 إلى 16 حرفًا ورقمًا _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "تبديل الملف الشخصي",
|
||||
"horizon.splitscreen.profiles.offline_note": "تنتمي الملفات الشخصية غير المتصلة بالإنترنت إلى هذه الحالة",
|
||||
"horizon.splitscreen.profiles.move": "تحرك حيث تلعب",
|
||||
"horizon.splitscreen.profiles.move_place": "تحرك حيث تلعب: %s",
|
||||
"horizon.friends.title": "العب مع الأصدقاء",
|
||||
"horizon.friends.hint": "يقوم الأصدقاء بلصق الرمز بعد زر الأصدقاء على شاشة العنوان، أو في الاتصال المباشر، أو على صفحة التشغيل الخاصة بالمشغل.",
|
||||
"horizon.friends.copied": "منقول",
|
||||
"horizon.friends.copy_code": "نسخ الرمز",
|
||||
"horizon.friends.stop_sharing": "توقف عن المشاركة",
|
||||
"horizon.friends.none_yet": "لا يوجد أصدقاء هنا بعد.",
|
||||
"horizon.friends.here_now": "هنا الآن: %s",
|
||||
"horizon.friends.share_intro": "شارك هذا العالم خلف رمز الدعوة: فهو يفتح على شبكة LAN، وينضم الأصدقاء على الإنترنت باستخدام الرمز. لا يوجد خادم خاص بنا متورط.",
|
||||
"horizon.friends.start_sharing": "ابدأ المشاركة",
|
||||
"horizon.friends.troubleshooting": "استكشاف الأخطاء وإصلاحها...",
|
||||
"horizon.friends.stopped": "تم إيقاف مشاركة رمز الدعوة.",
|
||||
"horizon.friends.short_code_changed": "استمر شخص ما في تجربة رمز قصير خاطئ، لذلك تغير.",
|
||||
"horizon.friends.wrong_code": "حاول شخص ما رمزا خاطئا.",
|
||||
"horizon.friends.code_ready": "رمز الدعوة الخاص بك جاهز: قائمة الألعاب، العب مع الأصدقاء. انسخها وأرسلها إلى أصدقائك.",
|
||||
"horizon.friends.share_failed": "لا يمكن مشاركة العالم: %s",
|
||||
"horizon.friends.finding_route": "التعرف على كيفية وصول الأصدقاء إليك...",
|
||||
"horizon.osk.shift": "التحول",
|
||||
"horizon.osk.space": "الفضاء",
|
||||
"horizon.osk.delete": "حذف",
|
||||
"horizon.osk.type": "اكتب",
|
||||
"horizon.osk.symbols": "الرموز",
|
||||
"horizon.osk.close": "إغلاق",
|
||||
"horizon.extras.chat_animation.tooltip": "ينزلق خط الدردشة الجديد من اليسار بينما يتلاشى للداخل، بدلاً من أن يظهر كاملاً.",
|
||||
"horizon.extras.chat_indicator.tooltip": "الشريط الملون الموجود على يسار خط الدردشة.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "عجلة التمرير فوق زر في الإعدادات تغير قيمته، كما هو الحال في الفانيليا. مع إيقاف التشغيل، تقوم العجلة بتمرير القائمة فقط.",
|
||||
"horizon.extras.pack_browser.tooltip": "تحديد حزم الموارد يحصل على زر \"إضافة\" الذي يبحث في Modrinth ويقوم بتثبيت حزمة في مجلد الحزم الخاص بك. مع إيقاف التشغيل، لن تتصل اللعبة أبدًا بـ Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "في عالم ما، يمكن لوحدة تحكم أخرى الانضمام كلاعب جديد: اضغط على A عندما يُطلب منك ذلك، ثم اختر ملفًا شخصيًا ومكان اللعب.",
|
||||
"horizon.splitscreen.setup.monitor": "مراقب %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "شاشة",
|
||||
"horizon.splitscreen.setup.window": "نافذة %s",
|
||||
"horizon.splitscreen.setup.new_window": "نافذة جديدة",
|
||||
"horizon.splitscreen.setup.fill_monitor": "املأ %s",
|
||||
"horizon.splitscreen.setup.done": "تم",
|
||||
"horizon.splitscreen.setup.on": "على",
|
||||
"horizon.splitscreen.setup.off": "إيقاف",
|
||||
"horizon.splitscreen.setup.player": "اللاعب %s",
|
||||
"horizon.splitscreen.setup.revert": "العودة",
|
||||
"horizon.splitscreen.setup.apply": "تطبيق",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "لا أحد يلعب في النافذة 1: إنه يخفي",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "لا أحد يلعب في النافذة %s: يتم إغلاقها عند تطبيق",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "لا أحد يلعب في النافذة %s: لن يتم فتحها",
|
||||
"horizon.splitscreen.setup.warning.on_main": "النافذة %s تملأ شاشة النافذة 1: يلعب لاعبوها في النافذة 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "شاشة النافذة %s غير متصلة: يتم تشغيل مشغلاتها في النافذة 1",
|
||||
"horizon.splitscreen.setup.status.off": "تقسيم الشاشة معطل.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "قم بتشغيله، ثم اضغط على زر على وحدة تحكم أخرى في العالم.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "اضغط على A للانضمام واختيار ملف التعريف.",
|
||||
"horizon.splitscreen.setup.status.idle": "تقسيم الشاشة خاملاً",
|
||||
"horizon.splitscreen.setup.status.alone": "لا يوجد لاعبين محليين آخرين.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "لم تتغير عناصر التحكم الفردية وملاحظات وحدة التحكم.",
|
||||
"horizon.splitscreen.setup.window_empty": "النافذة %s: فارغة",
|
||||
"horizon.splitscreen.setup.note.select": "انقر فوق أحد اللاعبين، أو اسحبه إلى نافذة أو شاشة.",
|
||||
"horizon.splitscreen.setup.note.apply": "تقدم للعب بهذه الطريقة، ثم ارجع للتراجع.",
|
||||
"horizon.splitscreen.setup.note.place": "أين يجب أن يلعب %s؟",
|
||||
"horizon.splitscreen.setup.this_player": "هذا اللاعب",
|
||||
"horizon.splitscreen.setup.switch_disabled": "يتم إيقاف تشغيل الشاشة المنقسمة بمجرد مغادرة الآخرين (قائمة الإيقاف المؤقت الخاصة بهم).",
|
||||
"horizon.splitscreen.setup.window_place": "النافذة %s (%s من %s)",
|
||||
"horizon.splitscreen.setup.names": "الأسماء",
|
||||
"horizon.splitscreen.setup.identify": "تحديد",
|
||||
"horizon.splitscreen.setup.layout.two": "2 المشاركة: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 المشاركة: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "تلقائي",
|
||||
"horizon.splitscreen.setup.layout.two.1": "جنبا إلى جنب",
|
||||
"horizon.splitscreen.setup.layout.two.2": "أعلى وأسفل",
|
||||
"horizon.splitscreen.setup.layout.three.0": "واحدة واسعة في الأعلى",
|
||||
"horizon.splitscreen.setup.layout.three.1": "واحد طويل القامة على اليسار",
|
||||
"horizon.splitscreen.setup.layout.three.2": "جنبا إلى جنب",
|
||||
"horizon.splitscreen.setup.help.player": "انقر أو اسحب %s لتحريكها.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (جنبًا إلى جنب عندما تكون النافذة واسعة). اضغط للتغيير.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. اضغط للتغيير.",
|
||||
"horizon.splitscreen.setup.help.switch": "تقسيم الشاشة لهذا الكمبيوتر، أو إيقاف تشغيله.",
|
||||
"horizon.splitscreen.setup.help.identify": "تُظهر طريقة عرض كل لاعب النافذة التي يوجد بها.",
|
||||
"horizon.splitscreen.setup.help.names": "اسم كل لاعب في زاوية رؤيته.",
|
||||
"horizon.splitscreen.setup.help.revert": "التراجع عن التغييرات التي لم يتم تطبيقها بعد.",
|
||||
"horizon.splitscreen.setup.help.apply": "العب بالطريقة التي تظهر بها الخريطة.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "لم يتغير شيء بعد.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "التخلي عن الوسادة",
|
||||
"horizon.splitscreen.prompt.controller_connected": "وحدة التحكم متصلة",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "وحدة التحكم مفصولة",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "أعد توصيله، أو اضغط على زر على آخر",
|
||||
"horizon.splitscreen.prompt.join.one": "%s يريد الانضمام",
|
||||
"horizon.splitscreen.prompt.join.many": "%s وحدات التحكم تريد الانضمام",
|
||||
"horizon.splitscreen.prompt.join.yes": "انضم",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "العب بشخصية P1",
|
||||
"horizon.splitscreen.prompt.join.no": "لا",
|
||||
"horizon.splitscreen.prompt.lost": "%s: تم فصل وحدة التحكم",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "استخدم آخر",
|
||||
"horizon.splitscreen.prompt.lost.leave": "غادر",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "تتوقف وحدة التحكم الخاصة بك عن قيادتك ويُعرض عليك الانضمام (أ).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s إلى %s: يسقطها A هنا، ويعيدها B.",
|
||||
"horizon.splitscreen.setup.note.held": "%s تم الالتقاط: يسار/يمين يختار المكان، يسقط A، ويعيده B.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "يخفي النماذج خلف الكتل المعتمة. غير متزامن يتحقق من خارج الموضوع؛ علامة الكيانات.",
|
||||
"Block Entities": "حظر الكيانات",
|
||||
"Shulker Boxes": "صناديق شوكر",
|
||||
"Reuses settled boxes; lid movement stays animated.": "إعادة استخدام الصناديق المستقرة؛ تبقى حركة الغطاء متحركة.",
|
||||
"Signs": "علامات",
|
||||
"Reuses sign text until text, light, or font changes.": "يعيد استخدام نص التوقيع حتى يتغير النص أو الضوء أو الخط.",
|
||||
"Decorated Pots": "الأواني المزخرفة",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "إعادة استخدام الأواني الثابتة؛ كسر تمايل وتغييرات الكسر إعادة رسم.",
|
||||
"Bells": "أجراس",
|
||||
"Reuses still bells; ringing stays animated.": "إعادة استخدام الأجراس الثابتة؛ يبقى الرنين متحركًا.",
|
||||
"Chests": "الصناديق",
|
||||
"Reuses closed chests; moving lids stay animated.": "إعادة استخدام الصناديق المغلقة؛ الأغطية المتحركة تبقى متحركة.",
|
||||
"Banners": "لافتات",
|
||||
"Reuses poles; flags and pattern layers still update.": "إعادة استخدام الأعمدة؛ لا يزال يتم تحديث الأعلام وطبقات الأنماط.",
|
||||
"Copper Golem Statues": "تماثيل النحاس جوليم",
|
||||
"Reuses statue geometry until its block state changes.": "يعيد استخدام هندسة التمثال حتى تتغير حالة الكتلة الخاصة به.",
|
||||
"Leaf Culling": "إعدام الأوراق",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "وجه كل ورقة. أوراق الفانيليا الفاخرة.",
|
||||
"Distant Plant Dither": "ثبات النبات البعيد",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "يحافظ على النباتات الجميلة في عبوات عالية الدقة من البهتان بعيدًا.",
|
||||
"Screenshot Quality": "جودة لقطة الشاشة",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "لقطات شاشة F2 بهذا دقة اللعبة عدة مرات. أعلى يستغرق وقتا أطول.",
|
||||
"Frame Generation": "جيل الإطار",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "يُظهر إطارًا مكوّنًا بين كل إطارين حقيقيين: أكثر سلاسة وتأخرًا قليلًا.",
|
||||
"Shadow Cache": "ذاكرة التخزين المؤقت الظل",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "يعيد استخدام خريطة ظل الحزمة عندما تتطابق الشمس والعرض والعالم. إيقاف بشكل افتراضي.",
|
||||
"Find BlueMap": "ابحث عن بلو ماب",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "على الخوادم، يتم العثور على خريطة الويب BlueMap الخاصة بهم للتضاريس البعيدة. الانضمام التالي.",
|
||||
"BlueMap Radius": "نصف قطر الخريطة الزرقاء",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "إلى أي مدى يتم تنزيل بلاط BlueMap من حولك. الانضمام التالي.",
|
||||
"Synchronous": "متزامن",
|
||||
"Asynchronous": "غير متزامن",
|
||||
"Solid": "صلب",
|
||||
"Solid Aggressive": "العدوانية الصلبة",
|
||||
"Hollow": "جوفاء",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "يتم رسم الأوراق بالكامل داخل المظلة بشكل صلب: طبقات شفافة أقل.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "الأوراق ذات جانب واحد مفتوح ترسم بشكل صلب أيضًا: حواف أسرع وأكثر كثافة.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "يسقط الوجوه بين الأوراق: بشكل أسرع، لكن الفجوات تبدو من خلالها.",
|
||||
"Hotbar: Previous Slot": "Hotbar: الفتحة السابقة",
|
||||
"Hotbar: Next Slot": "Hotbar: الفتحة التالية",
|
||||
"horizon.agent.connected.one": "متصل: اتصال وكيل واحد",
|
||||
"horizon.agent.connected.many": "متصل: %s اتصالات الوكيل",
|
||||
"horizon.agent.connected.one_ago": "متصل: اتصال وكيل واحد، آخر أمر %s مضت",
|
||||
"horizon.agent.connected.many_ago": "متصل: %s اتصالات الوكيل، آخر أمر %s مضت",
|
||||
"horizon.agent.waiting": "في انتظار وكيل.",
|
||||
"horizon.agent.off": "إيقاف: لا يمكن للعملاء الوصول إلى هذه اللعبة.",
|
||||
"horizon.agent.session_opened": "تم فتحه لهذه الجلسة بواسطة البرنامج الذي بدأ اللعبة.",
|
||||
"horizon.agent.socket": "المقبس: %s (انسخ التفاصيل لمساره الكامل)",
|
||||
"horizon.agent.local_only": "محلي فقط: الوكلاء على هذا الكمبيوتر، وليس الشبكة أبدًا.",
|
||||
"%s nits": "%s القمل",
|
||||
"Auto (%s nits)": "تلقائي (%s شمعة في المتر المربع)",
|
||||
"%.2f nits": "%.2f القمل",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "يخفي النماذج خلف الكتل المعتمة. تحديث الكيانات مع حزم مخصصة.",
|
||||
"horizon.extras.repro_keys": "تسجيل الاختصارات",
|
||||
"horizon.extras.repro_keys.tooltip": "يقوم F3+R ببدء التسجيل النسخي أو إيقافه ويقوم F3+E بكتابة الحزمة الخاصة به. متوقف بشكل افتراضي: تسجيل ينسخ العالم.",
|
||||
"horizon.agent.record_session": "ابدأ جلسة التسجيل",
|
||||
"horizon.agent.record_stop": "إيقاف جلسة التسجيل",
|
||||
"horizon.agent.export_repro": "تصدير حزمة النسخ المتماثل",
|
||||
"horizon.repro.started": "بدأ التسجيل؛ F3+R يوقفه ويكتب الحزمة.",
|
||||
"horizon.repro.stopped": "توقف التسجيل: %s",
|
||||
"horizon.repro.bundle": "حزمة النسخ: %s",
|
||||
"horizon.repro.error": "استنساخ: %s",
|
||||
"horizon.repro.no_recording": "لا يوجد تسجيل للتصدير حتى الآن.",
|
||||
"horizon.repro.replay_started": "بدأت إعادة التشغيل: %s",
|
||||
"horizon.pack.addInfo": "إضافة حزمة موارد من Modrinth",
|
||||
"horizon.pack.addWorking": "يسأل مودرنث...",
|
||||
"horizon.pack.addNone": "لم يتم العثور على حزم الموارد",
|
||||
"horizon.pack.addTitle": "أضف حزمة الموارد",
|
||||
"horizon.pack.install": "تثبيت",
|
||||
"horizon.pack.installing": "التثبيت",
|
||||
"horizon.pack.installed": "تم التثبيت",
|
||||
"horizon.pack.search": "بحث",
|
||||
"horizon.pack.searchHint": "بحث مودرينث...",
|
||||
"horizon.pack.filters": "المرشحات",
|
||||
"horizon.pack.filter.realistic": "واقعية",
|
||||
"horizon.pack.filter.simplistic": "تبسيط",
|
||||
"horizon.pack.filter.vanillaLike": "تشبه الفانيليا",
|
||||
"horizon.pack.addFailed": "لا يمكن إضافة الحزمة: %s",
|
||||
"horizon.extras.header.cosmetics": "مستحضرات التجميل",
|
||||
"horizon.extras.cosmetics_mine": "مستحضرات التجميل الخاصة بي",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "مخفي: يراك كل لاعب في Horizon بدون مستحضرات التجميل الخاصة بك، بما في ذلك أنت. نفس مفتاح خزانة المشغل.",
|
||||
"horizon.extras.cosmetics_others": "مستحضرات التجميل للاعبين الآخرين",
|
||||
"horizon.extras.cosmetics_others.tooltip": "ما يرتديه لاعبو Horizon الآخرون: كل شيء، فقط أنماط بطاقة الاسم الخاصة بهم، أو لا شيء. مخفيًا، اللعبة لا تسأل أبدًا عما يرتدونه.",
|
||||
"horizon.extras.cosmetics_auras": "هالات ومسارات الآخرين",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "الهالات والمسارات التي يرتديها اللاعبون الآخرون. إيقاف بشكل افتراضي.",
|
||||
"horizon.extras.cosmetics.shown": "معروض",
|
||||
"horizon.extras.cosmetics.hidden": "مخفي",
|
||||
"horizon.extras.cosmetics.name_tags": "علامات الاسم فقط"
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
{
|
||||
"horizon.options.controller": "Контролер",
|
||||
"horizon.options.vibration": "Вибрация",
|
||||
"horizon.options.adaptive_triggers": "Адаптивни тригери",
|
||||
"horizon.options.lightbar": "Светлинна лента",
|
||||
"horizon.options.look_sensitivity": "Чувствителност на погледа",
|
||||
"horizon.options.invert_x": "Обръщане на оста X на погледа",
|
||||
"horizon.options.invert_y": "Обръщане на оста Y на погледа",
|
||||
"horizon.options.sneak_button": "Бутон за промъкване",
|
||||
"horizon.options.move_deadzone": "Мъртва зона на движението",
|
||||
"horizon.options.look_deadzone": "Мъртва зона на погледа",
|
||||
"horizon.options.button_glyphs": "Глифове на бутони",
|
||||
"horizon.options.menu_navigation": "Навигация в менюто",
|
||||
"horizon.options.on_screen_keyboard": "Екранна клавиатура",
|
||||
"horizon.options.chat_delay": "Забавяне на чата",
|
||||
"horizon.options.lightbar.health": "здраве",
|
||||
"horizon.options.lightbar.dimension": "Измерение",
|
||||
"horizon.options.glyphs.auto": "Авто",
|
||||
"horizon.options.menu_focus": "Фокус",
|
||||
"horizon.options.menu_pointer": "показалец",
|
||||
"horizon.options.keyboard.auto": "Авто",
|
||||
"horizon.options.keyboard.steam": "Пара",
|
||||
"horizon.options.unavailable": "Тази функция все още не е налична в Horizon.",
|
||||
"horizon.options.search": "Настройки за търсене...",
|
||||
"horizon.pages.mods": "Модификации",
|
||||
"horizon.pages.shader_packs": "Шейдърни пакети",
|
||||
"horizon.pages.shader_options": "Опции на шейдъра",
|
||||
"horizon.pages.controller": "Настройки на контролера",
|
||||
"horizon.pages.controller_binds": "Контролерът се свързва",
|
||||
"horizon.pages.agent": "AI агент",
|
||||
"horizon.split.leave": "тръгвай",
|
||||
"horizon.pages.split_settings": "Настройки за разделен екран",
|
||||
"horizon.pages.split_profiles": "Кой играе?",
|
||||
"horizon.pages.split_setup": "Настройка на разделен екран",
|
||||
"horizon.pages.music": "Музика",
|
||||
"horizon.pages.music.menu": "Меню",
|
||||
"horizon.pages.music.overworld": "Надземният свят",
|
||||
"horizon.pages.music.creative": "Творчески",
|
||||
"horizon.pages.music.underwater": "Под вода",
|
||||
"horizon.pages.music.end": "Краят",
|
||||
"horizon.pages.music.boss": "Шефе",
|
||||
"horizon.pages.music.credits": "Кредити",
|
||||
"horizon.pages.music.overworld_biomes": "Биоми от надземния свят",
|
||||
"horizon.pages.music.nether_biomes": "Долни биоми",
|
||||
"horizon.pages.music.end_biomes": "Крайни биоми",
|
||||
"horizon.pages.music.other": "Друга музика",
|
||||
"horizon.pages.music.empty": "Няма музикални записи: звуците на играта не се зареждат.",
|
||||
"horizon.pages.music.tooltip": "Вградените песни според ситуацията: пуснете една сега или изключете някоя, така че играта да спре да я избира.",
|
||||
"horizon.pages.settings.tooltip": "Собствени настройки на Horizon: графики, контролери, екстри и модове.",
|
||||
"horizon.pages.graphics.tooltip": "Графика просто: всяка настройка казва колко струва.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Всяка графична настройка, включително Horizon, с цената на всяка.",
|
||||
"horizon.pages.controller.tooltip": "Контролери: техните бутони, стикове, вибрация и навигация в менюто.",
|
||||
"horizon.pages.extras.tooltip": "Незадължителни добавки, всички изключени по подразбиране: играта остава ванилия, докато не включите някоя.",
|
||||
"horizon.pages.agent.tooltip": "Нека вашият собствен AI агент играе: horizon-ctl и MCP клиенти на този компютър.",
|
||||
"horizon.pages.split_profiles.tooltip": "Кой играе: профил, съхраняван в тази папка на играта.",
|
||||
"horizon.pages.split_setup.tooltip": "Прозорците и мониторите и кой къде играе.",
|
||||
"horizon.pages.shader_packs.tooltip": "Шейдърни пакети от папката shaderpacks: собствените на Horizon и тези във формата shaders/. Един тича наведнъж.",
|
||||
"horizon.pages.mods.tooltip": "Модификации, направени за Horizon, от папката horizon-mods. Всеки работи в пясъчна кутия и остава изключен, докато не го включите.",
|
||||
"horizon.pages.open.graphics": "Графика...",
|
||||
"horizon.pages.open.graphics_advanced": "Разширена графика...",
|
||||
"horizon.pages.open.shader_packs": "Шейдър пакети...",
|
||||
"horizon.pages.open.controller": "Настройки на контролера...",
|
||||
"horizon.pages.open.split_setup": "Разделен екран...",
|
||||
"horizon.pages.open.extras": "Екстри...",
|
||||
"horizon.pages.open.mods": "Модове...",
|
||||
"horizon.pages.open.agent": "AI агент...",
|
||||
"horizon.pages.open.controller_binds": "Контролерът се свързва...",
|
||||
"horizon.pages.open.horizon_settings": "Настройки на Horizon...",
|
||||
"horizon.pages.open.music": "музика...",
|
||||
"horizon.mods.empty": "Без модификации: поставете .hmod файлове в папката horizon-mods",
|
||||
"horizon.mods.stopped": " (спряно)",
|
||||
"horizon.mods.not_built": " (Не е построен)",
|
||||
"horizon.mods.settings": "Настройки",
|
||||
"horizon.mods.in_world": " (оставете света да се промени)",
|
||||
"horizon.mods.open_folder": "Отворете папката на Mod",
|
||||
"horizon.shader_packs.empty": "Без шейдър пакети: поставете ги в папката shaderpacks",
|
||||
"horizon.shader_options.open": "Опции за шейдър...",
|
||||
"horizon.shader_options.profile": "Профил",
|
||||
"horizon.shader_options.loading": "Четене на опциите на шейдър пакета...",
|
||||
"horizon.agent.copy_details": "Копиране на подробности за връзката",
|
||||
"horizon.multiplayer.join": "Присъединете се",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Отказ Повторно свързване",
|
||||
"horizon.multiplayer.join_friend": "Присъединете се към приятел",
|
||||
"horizon.multiplayer.invite_code": "Код за покана",
|
||||
"horizon.multiplayer.relay": "Реле (по избор)",
|
||||
"Preset": "Предварителна настройка",
|
||||
"LOD Distance": "LOD разстояние",
|
||||
"Far Detail": "Далечен детайл",
|
||||
"Cave Culling": "Пещерно избиване",
|
||||
"Entity Culling": "Отстраняване на субекти",
|
||||
"Chunk Updates": "Актуализации на парчета",
|
||||
"Upload Budget": "Бюджет за качване",
|
||||
"Sky": "небе",
|
||||
"Fog": "мъгла",
|
||||
"Weather": "Времето",
|
||||
"Animated Textures": "Анимирани текстури",
|
||||
"Present Mode": "Настоящ режим",
|
||||
"Frame Pacing": "Темпо на рамката",
|
||||
"Worker Threads": "Работни нишки",
|
||||
"Resolution Scale": "Скала на резолюцията",
|
||||
"Upscaling": "Премащабиране",
|
||||
"Paper White": "Бяла хартия",
|
||||
"Peak Brightness": "Пикова яркост",
|
||||
"Black Level": "Черно ниво",
|
||||
"UI Brightness": "Яркост на потребителския интерфейс",
|
||||
"Battery Mode": "Режим на батерия",
|
||||
"Graphics API": "Графичен API",
|
||||
"Translucent Batching": "Полупрозрачно пакетиране",
|
||||
"Performance Overlay": "Наслагване на ефективността",
|
||||
"Full Detail Distance": "Пълно подробно разстояние",
|
||||
"On": "включено",
|
||||
"Off": "Изкл",
|
||||
"Unlimited": "Неограничен",
|
||||
"Auto": "Авто",
|
||||
"Custom": "По поръчка",
|
||||
"Performance": "Изпълнение",
|
||||
"Balanced": "Балансиран",
|
||||
"Quality": "качество",
|
||||
"Ultra": "Ултра",
|
||||
"Ultra Performance": "Ултра производителност",
|
||||
"Native": "Роден",
|
||||
"Full": "Пълна",
|
||||
"Reduced": "Намалена",
|
||||
"Efficient": "Ефективно",
|
||||
"Precise": "Прецизен",
|
||||
"Auto (VSync)": "Автоматично (VSync)",
|
||||
"Mailbox": "Пощенска кутия",
|
||||
"Immediate": "Незабавно",
|
||||
"FPS and Graph": "FPS и графика",
|
||||
"No cost": "Без разходи",
|
||||
"Low cost": "Ниска цена",
|
||||
"Medium cost": "Средна цена",
|
||||
"High cost": "Висока цена",
|
||||
"Saves time": "Спестява време",
|
||||
"Graphics": "Графика",
|
||||
"Done": "Готово",
|
||||
"This feature is not yet available in Horizon.": "Тази функция все още не е налична в Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Вашият дисплей или система не предлага HDR изход.",
|
||||
"Turn HDR on to use it.": "Включете HDR, за да го използвате.",
|
||||
"Your GPU or graphics API doesn't support it.": "Вашият GPU или графичен API не го поддържат.",
|
||||
"%s chunks": "%s парчета",
|
||||
"%s per frame": "%s на кадър",
|
||||
"%s MiB/frame": "%s MiB/кадър",
|
||||
"%s KiB/frame": "%s KiB/кадър",
|
||||
"Auto (%s)": "Авто (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Пълен детайлен обхват на терена. LOD Разстоянието може да разшири локалните светове по-далеч.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Груб далечен терен в местните светове. Интегрираните графични процесори спират на 128 парчета.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Намалено чертане на по-груб терен: по-бързо с този интегриран GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Намалено рисува далеч по-груб терен, за GPU се забавя.",
|
||||
"Field of view. Wider shows more terrain at once.": "Зрително поле. По-широко показва повече терен наведнъж.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Максимални кадри в секунда; капачката спестява енергия и топлина.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Изчаква опресняване на дисплея: без разкъсване, малко забавяне.",
|
||||
"Size of menus and the HUD.": "Размер на менютата и HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Задава заедно опциите по-долу. Промяната на един го прави персонализиран.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Лавата, огънят и слънчевата светлина блестят по-ярко от бялото. Има нужда от HDR дисплей.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Вашият дисплей е най-тъмно черен. 0 за OLED; LCD дисплеите светят малко.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Колко ярки стават акцентите. Автоматично: пикът, който вашата система отчита.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Колко ярки изглеждат бялата хартия и менютата. Автоматично: вашият работен плот е бял.",
|
||||
"How bright the HUD and menus are, against paper white.": "Колко ярки са HUD и менютата на бяла хартия.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Докъде продължават да работят тълпите, културите и машините. процесорно време.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Колко далеч са изтеглени тълпите и предметите, като дял от нормалното.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Пропуска терен, скрит зад затворени участъци. Off струва много.",
|
||||
"Hides entities behind opaque blocks.": "Скрива обекти зад непрозрачни блокове.",
|
||||
"No entity occlusion culling.": "Без отстраняване на оклузия на обект.",
|
||||
"CPU occlusion test runs this frame.": "Тестът за запушване на процесора изпълнява този кадър.",
|
||||
"Occlusion checks run on a worker thread.": "Проверките за затваряне се изпълняват на работна нишка.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Мрежести секции, качени на кадър. Долната е по-плавна при зареждане.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Меки сенки в ъглите. Разходи само време за изграждане на парче.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "По-малки копия на блокови текстури за отдалечени терени: по-малко блясък.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Смесва тревни и водни цветове в биоми при изграждане на парчета.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Облачен слой: изключен, плосък (бърз) или 3D (фантастичен).",
|
||||
"How far clouds reach, in chunks.": "Докъде достигат облаците, на парчета.",
|
||||
"Draws the sky, sun, moon and stars.": "Рисува небето, слънцето, луната и звездите.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Дистанционна мъгла. Изключено показва ръба на заредения свят.",
|
||||
"Draws rain and snow.": "Рисува дъжд и сняг.",
|
||||
"How far rain and snow fall around you.": "Докъде валят дъжд и сняг около вас.",
|
||||
"How many particles effects spawn.": "Колко ефекти на частици хвърлят хайвера си.",
|
||||
"Round shadows under mobs and items.": "Кръгли сенки под тълпи и предмети.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Листа с прозрачни празнини. Off ги рисува солидно.",
|
||||
"Sorts water and glass more exactly behind each other.": "Сортира водата и стъклото по-точно едно зад друго.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Коя част се възстановява, чакайки рамката: нито една, наблизо или вашата.",
|
||||
"Animates water, lava, fire and other moving textures.": "Анимира вода, лава, огън и други движещи се текстури.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Световни пиксели на пиксел на екрана. Под 100% е по-бързо на GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Изглажда назъбените ръбове. FXAA: по-мек. TAA: най-плавните, движещи се неща могат да се проточат.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Прави света по-малък и го възстановява с TAA. По-бързо, по-меко.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR изход на HDR дисплей: лава, огън и слънчева светлина блестят по-ярко.",
|
||||
"How frames reach the screen while VSync is off.": "Как кадрите достигат до екрана, докато VSync е изключен.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Ефективно заспиване до следващия кадър; Прецизни завъртания, като ванилия.",
|
||||
"Fills the screen (borderless).": "Запълва екрана (без граници).",
|
||||
"Brightens dark places.": "Осветява тъмните места.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Намалява честотата на кадрите, когато е минимизирана или неактивна.",
|
||||
"Sways the view while walking.": "Люлее гледката по време на ходене.",
|
||||
"Fades newly loaded chunks in.": "Избледнява новозаредените парчета.",
|
||||
"Darkens the screen's edges.": "Затъмнява ръбовете на екрана.",
|
||||
"Shows when the world saves.": "Показва кога светът спасява.",
|
||||
"Shows attack cooldown.": "Показва изчакване на атаката.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS капачка (30 в менюта или нефокусирани), половината работници: по-малко мощност.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Нишки, които генерират, леки и мрежести парчета. Следващ старт.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, след това DirectX 12 (Windows) или OpenGL (Linux), където не успее. Следващ старт.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Вода и стъкло в един буфер: по-малко извиквания за теглене, малко повече VRAM. Следващ старт.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Мрежови данни, изпратени до GPU на кадър. Долната е по-плавна при зареждане.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Скорост на кадрите и времена на кадрите в играта, с графика.",
|
||||
"horizon.multiplayer.finding_friend": "Намиране на света на вашия приятел...",
|
||||
"horizon.multiplayer.not_signed_in": "Този сървър е в онлайн режим. Влезте с вашия акаунт в Microsoft в стартовия панел на Horizon, за да се присъедините към него.",
|
||||
"horizon.multiplayer.invalid_address": "Невалиден адрес на сървъра: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Свързан директно с вашия приятел...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Свързан с вашия приятел чрез релето...",
|
||||
"General": "генерал",
|
||||
"Advanced": "Разширено",
|
||||
"Undo": "Отмяна",
|
||||
"Search options": "Опции за търсене",
|
||||
"Shortcut": "Пряк път",
|
||||
"Distance": "Разстояние",
|
||||
"Display": "Дисплей",
|
||||
"Interface": "Интерфейс",
|
||||
"Sky and weather": "Небе и време",
|
||||
"Lighting and entities": "Осветление и обекти",
|
||||
"Textures": "Текстури",
|
||||
"Image": "Изображение",
|
||||
"Chunk loading": "Зареждане на парчета",
|
||||
"Culling": "Бране",
|
||||
"Power": "Мощност",
|
||||
"Frames": "Рамки",
|
||||
"Overlay": "Наслагване",
|
||||
"Output": "Изход",
|
||||
"Brightness": "Яркост",
|
||||
"How far you see, the window and the HUD.": "Колко далеч виждате, прозореца и HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Небето и времето, осветлението, текстурите и изображението.",
|
||||
"Chunk loading, culling and power use.": "Зареждане на парчета, отстраняване и използване на енергия.",
|
||||
"How frames reach the screen, and the performance overlay.": "Как рамките достигат до екрана и наслагването на ефективността.",
|
||||
"Brighter-than-white output on an HDR display.": "По-ярък от бялото изход на HDR дисплей.",
|
||||
"Results for \"%s\"": "Резултати за \"%s\"",
|
||||
"No settings match.": "Няма съответстващи настройки.",
|
||||
"horizon.title.not_affiliated": "Не е свързан с Mojang или Microsoft",
|
||||
"horizon.extras.header.lighting": "Осветление",
|
||||
"horizon.extras.header.third_person": "Трето лице",
|
||||
"horizon.extras.header.particles": "частици",
|
||||
"horizon.extras.header.inventory": "Инвентаризация",
|
||||
"horizon.extras.header.chat": "Чат",
|
||||
"horizon.extras.header.multiplayer": "Мултиплейър",
|
||||
"horizon.extras.header.sound": "Звук",
|
||||
"horizon.extras.header.menus": "Менюта",
|
||||
"horizon.extras.fullbright": "Фулбрайт",
|
||||
"horizon.extras.dynamic_lights": "Динамични светлини",
|
||||
"horizon.extras.own_name_tag": "Собствен етикет с име",
|
||||
"horizon.extras.hide_block_particles": "Скриване на частиците за разбиване на блокове",
|
||||
"horizon.extras.auto_reconnect": "Автоматично повторно свързване",
|
||||
"horizon.extras.ui_sound_reserve": "Запазете канали за звуци от потребителския интерфейс",
|
||||
"horizon.extras.music_page": "Музикална страница",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks контроли",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Колелото за превъртане премества елементи",
|
||||
"horizon.extras.sort": "Бутони за сортиране",
|
||||
"horizon.extras.sort_key": "Ключ за сортиране",
|
||||
"horizon.extras.no_ai": "Без AI",
|
||||
"horizon.extras.agent_control": "Агентски контрол",
|
||||
"horizon.extras.discord_presence": "Богато присъствие на Discord",
|
||||
"horizon.extras.splitscreen": "Разделен екран",
|
||||
"horizon.extras.chat_animation": "Плавен чат",
|
||||
"horizon.extras.chat_indicator": "Индикатор за съобщение",
|
||||
"horizon.extras.wheel_cycles_settings": "Колелото за превъртане променя настройките",
|
||||
"horizon.extras.pack_browser": "Браузър за пакети с ресурси",
|
||||
"horizon.extras.fullbright.tooltip": "Показвайте блокове и обекти при пълна светлина, дори в тъмни зони.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Факли и други източници на светлина, държани, изпуснати или запалени, осветяват блоковете около тях.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Показва името ви над главата ви в трето лице, както го виждат другите играчи. Скрит, когато сървърът скрива имената на вашия отбор или сте невидими.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Скрива частиците отломки, които избухват от счупен блок и се разпадат от този, който копаете. Други частици остават.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Присъединява се отново към сървър след загубена връзка или рестартиране, никога след ритване или бан.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Звуците в света оставят 16 от 247 звукови канала свободни, така че потоп от тях (голяма експлозия) не може да заглуши щракванията в менюто.",
|
||||
"horizon.extras.music_page.tooltip": "Music & Sounds получава страница Music...: пуснете която и да е вградена песен сега или изключете някоя, така че играта да спре да я избира. Нищо не се променя, докато не изключите запис.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Плъзгане с десен бутон поставя по един на слот, плъзгане с ляво събира задържания елемент, Shift + плъзгане наляво премества всеки слот.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Надолу изпраща един елемент от слота от другата страна, нагоре връща един обратно.",
|
||||
"horizon.extras.sort.tooltip": "Сортира инвентара, сандъците, бъчвите, сандъците за ендер и кутиите за шулкери в творчески ред на раздели, сливайки стекове.",
|
||||
"horizon.extras.sort_key.tooltip": "Сортира частта под курсора. Щракнете, след това натиснете клавиш; Escape го развързва.",
|
||||
"horizon.extras.no_ai.tooltip": "Скрива и изключва всяка функция на AI: контрол на агента, horizon-ctl и MCP сървърите. Превключвателят на стартера също.",
|
||||
"horizon.extras.agent_control.tooltip": "Вашият собствен AI агент може да играе тази игра чрез horizon-ctl или MCP клиент само на този компютър.",
|
||||
"horizon.extras.discord_presence.tooltip": "Показва Playing Horizon във вашия Discord профил, докато Discord работи: менюта, един играч или мултиплейър, измерението и времето на игра. Никога име на света или адрес на сървър.",
|
||||
"Calibrate...": "Калибриране...",
|
||||
"Calibrate Anyway...": "Калибриране все пак...",
|
||||
"Use Automatic Setup": "Използвайте автоматична настройка",
|
||||
"Shortcut: %s": "Пряк път: %s",
|
||||
"Next": "Следваща",
|
||||
"HDR Calibration %s/%s: %s": "HDR калибриране %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Задава трите под цял екран, една тестова картина наведнъж.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Ключ, който отваря този екран от играта. В началото не е обвързан.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Вашият дисплей не каза колко ярък става: калибрирайте, за да го настроите.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Вече е настроен за вашия дисплей. Калибрирайте само ако HDR изглежда грешно.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Връща Peak Brightness и Paper White обратно към настройката на вашия дисплей.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Настройте за вашия дисплей. Оставете на Auto, освен ако не изглежда грешно.",
|
||||
"Lower it until the logo just disappears into the black.": "Спуснете го, докато логото изчезне в черното.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Повдигнете го, докато логото изчезне в яркия квадрат.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Направете картата бяла, удобна за гледане дълго време, на светлината, на която играете.",
|
||||
"horizon.splitscreen.profiles.forget": "Забравете",
|
||||
"horizon.splitscreen.profiles.create": "Създавайте",
|
||||
"horizon.splitscreen.profiles.you": " - ти",
|
||||
"horizon.splitscreen.profiles.forget_named": "Забравете %s",
|
||||
"horizon.splitscreen.profiles.previous": "Предишен",
|
||||
"horizon.splitscreen.profiles.next": "Следваща",
|
||||
"horizon.splitscreen.profiles.new_button": "Нов профил...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Забравете профила?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Напуска списъка на този екземпляр.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Световният напредък и настройки се запазват.",
|
||||
"horizon.splitscreen.profiles.new_title": "Нов профил",
|
||||
"horizon.splitscreen.profiles.name_hint": "Име (3 до 16 букви, цифри, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Превключване на профил",
|
||||
"horizon.splitscreen.profiles.offline_note": "Офлайн профилите принадлежат към този екземпляр",
|
||||
"horizon.splitscreen.profiles.move": "Движете се там, където играете",
|
||||
"horizon.splitscreen.profiles.move_place": "Преместете се там, където играете: %s",
|
||||
"horizon.friends.title": "Играйте с приятели",
|
||||
"horizon.friends.hint": "Приятели поставете кода след бутона Приятели на заглавния екран, в Direct Connection или на страницата за възпроизвеждане на стартовия панел.",
|
||||
"horizon.friends.copied": "Копирано",
|
||||
"horizon.friends.copy_code": "Копиране на код",
|
||||
"horizon.friends.stop_sharing": "Спиране на споделянето",
|
||||
"horizon.friends.none_yet": "Тук все още няма приятели.",
|
||||
"horizon.friends.here_now": "Тук сега: %s",
|
||||
"horizon.friends.share_intro": "Споделете този свят зад код за покана: той се отваря към LAN и приятели в интернет се присъединяват с кода. Не е включен наш сървър.",
|
||||
"horizon.friends.start_sharing": "Започнете споделяне",
|
||||
"horizon.friends.troubleshooting": "Отстраняване на неизправности...",
|
||||
"horizon.friends.stopped": "Спря споделянето на кода за покана.",
|
||||
"horizon.friends.short_code_changed": "Някой продължаваше да опитва грешен кратък код, така че той се промени.",
|
||||
"horizon.friends.wrong_code": "Някой е опитал грешен код.",
|
||||
"horizon.friends.code_ready": "Вашият код за покана е готов: Меню за игри, Играйте с приятели. Копирайте го и го изпратете на приятелите си.",
|
||||
"horizon.friends.share_failed": "Не можах да споделя света: %s",
|
||||
"horizon.friends.finding_route": "Търся как приятелите могат да се свържат с вас...",
|
||||
"horizon.osk.space": "пространство",
|
||||
"horizon.osk.delete": "Изтриване",
|
||||
"horizon.osk.type": "Тип",
|
||||
"horizon.osk.symbols": "Символи",
|
||||
"horizon.osk.close": "затвори",
|
||||
"horizon.extras.chat_animation.tooltip": "Нова линия за чат се плъзга отляво, докато избледнява, вместо да се показва цяла.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Цветната лента отляво на линия за чат.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Колелцето за превъртане над бутон в настройките променя стойността му, както при vanilla. Изключено, колелото само превърта списъка.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs получава бутон за добавяне, който търси в Modrinth и инсталира пакет във вашата папка с пакети. Off, играта никога не се свързва с Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "В един свят друг контролер може да се присъедини като нов играч: натиснете A, когато поиска, след това изберете профил и къде да играете.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "монитор",
|
||||
"horizon.splitscreen.setup.window": "Прозорец %s",
|
||||
"horizon.splitscreen.setup.new_window": "Нов прозорец",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Попълнете %s",
|
||||
"horizon.splitscreen.setup.done": "Готово",
|
||||
"horizon.splitscreen.setup.on": "включено",
|
||||
"horizon.splitscreen.setup.off": "Изкл",
|
||||
"horizon.splitscreen.setup.player": "Играч %s",
|
||||
"horizon.splitscreen.setup.revert": "Връщане",
|
||||
"horizon.splitscreen.setup.apply": "Кандидатствайте",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Никой не играе в Window 1: той се скрива",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Никой не играе в Window %s: затваря се при Apply",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Никой не играе в Window %s: няма да се отвори",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Window %s изпълва монитора на Window 1: неговите играчи играят в Window 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Мониторът на Window %s не е свързан: неговите играчи играят в Window 1",
|
||||
"horizon.splitscreen.setup.status.off": "Разделеният екран е изключен.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Включете го, след това натиснете бутон на друг контролер в свят.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Натиснете A, за да се присъедините и изберете профил.",
|
||||
"horizon.splitscreen.setup.status.idle": "Разделен екран неактивен",
|
||||
"horizon.splitscreen.setup.status.alone": "Няма други местни играчи.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Самостоятелните контроли и обратната връзка на контролера са непроменени.",
|
||||
"horizon.splitscreen.setup.window_empty": "Прозорец %s: празен",
|
||||
"horizon.splitscreen.setup.note.select": "Щракнете върху плейър или го плъзнете към прозорец или монитор.",
|
||||
"horizon.splitscreen.setup.note.apply": "Приложете, за да играете по този начин, Върнете, за да отмените.",
|
||||
"horizon.splitscreen.setup.note.place": "Къде трябва да играе %s?",
|
||||
"horizon.splitscreen.setup.this_player": "този играч",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen се изключва, след като другите напуснат (тяхното меню за пауза).",
|
||||
"horizon.splitscreen.setup.window_place": "Прозорец %s (%s от %s)",
|
||||
"horizon.splitscreen.setup.names": "имена",
|
||||
"horizon.splitscreen.setup.identify": "Идентифицирайте",
|
||||
"horizon.splitscreen.setup.layout.two": "2 споделяне: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 споделяне: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "Авто",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Един до друг",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Отгоре и отдолу",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Един широк отгоре",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Един висок отляво",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Един до друг",
|
||||
"horizon.splitscreen.setup.help.player": "Кликнете или плъзнете %s, за да ги преместите.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (един до друг, когато прозорецът е широк). Натиснете за промяна.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Натиснете за промяна.",
|
||||
"horizon.splitscreen.setup.help.switch": "Разделен екран за този компютър, включен или изключен.",
|
||||
"horizon.splitscreen.setup.help.identify": "Изгледът на всеки играч показва в кой прозорец се намира.",
|
||||
"horizon.splitscreen.setup.help.names": "Името на всеки играч в ъгъла на неговия изглед.",
|
||||
"horizon.splitscreen.setup.help.revert": "Отменете промените, които все още не са приложени.",
|
||||
"horizon.splitscreen.setup.help.apply": "Играйте по начина, по който картата показва.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Все още нищо не се е променило.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Подложка за отказване",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Контролерът е свързан",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Контролерът е изключен",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Свържете го отново или натиснете бутон на друг",
|
||||
"horizon.splitscreen.prompt.join.one": "%s иска да се присъедини",
|
||||
"horizon.splitscreen.prompt.join.many": "%s контролери искат да се присъединят",
|
||||
"horizon.splitscreen.prompt.join.yes": "Присъединете се",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Играйте като P1",
|
||||
"horizon.splitscreen.prompt.join.no": "не",
|
||||
"horizon.splitscreen.prompt.lost": "%s: контролерът е изключен",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Използвайте друг",
|
||||
"horizon.splitscreen.prompt.lost.leave": "тръгвай",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Вашият контролер спира да ви кара и получава предложение да се присъедини (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s до %s: A го пуска тук, B го връща обратно.",
|
||||
"horizon.splitscreen.setup.note.held": "%s взето: ляв/десен избира къде, A пуска, B го връща обратно.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Скрива модели зад непрозрачни блокове. Async проверява извън нишката; субекти отметка.",
|
||||
"Block Entities": "Блокиране на обекти",
|
||||
"Shulker Boxes": "Кутии Шулкер",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Повторно използване на утаени кутии; движението на капака остава анимирано.",
|
||||
"Signs": "Знаци",
|
||||
"Reuses sign text until text, light, or font changes.": "Използва повторно текста на знака, докато текстът, светлината или шрифтът се променят.",
|
||||
"Decorated Pots": "Декорирани саксии",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Повторно използване на саксии; прекъсване на колебанията и отделяне на промени, преначертаване.",
|
||||
"Bells": "Камбани",
|
||||
"Reuses still bells; ringing stays animated.": "Повторно използване на неподвижни камбани; звъненето остава анимирано.",
|
||||
"Chests": "Сандъци",
|
||||
"Reuses closed chests; moving lids stay animated.": "Повторно използване на затворени сандъци; подвижните клепачи остават анимирани.",
|
||||
"Banners": "Банери",
|
||||
"Reuses poles; flags and pattern layers still update.": "Повторно използване на стълбове; флаговете и слоевете с шаблони все още се актуализират.",
|
||||
"Copper Golem Statues": "Медни статуи на голем",
|
||||
"Reuses statue geometry until its block state changes.": "Използва повторно геометрията на статуята, докато блоковото й състояние се промени.",
|
||||
"Leaf Culling": "Изрязване на листа",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Всеки лист лице. Елегантните листа на ванилия.",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Предпазва фините растения в пакети с висока разделителна способност от избледняване.",
|
||||
"Screenshot Quality": "Качество на екранната снимка",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "Екранни снимки на F2 при толкова многократна разделителна способност на играта. По-високото отнема повече време.",
|
||||
"Frame Generation": "Генериране на рамка",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Показва измислен кадър между всеки два реални: по-гладък, с малко повече забавяне.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Използва повторно картата на сенките на глутницата, когато слънцето, гледката и света съвпадат. Изключено по подразбиране.",
|
||||
"Find BlueMap": "Намерете BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "На сървъри намира тяхната уеб карта BlueMap за далечен терен. Следващо присъединяване.",
|
||||
"BlueMap Radius": "Радиус на BlueMap",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "На какво разстояние около вас се изтеглят плочките BlueMap. Следващо присъединяване.",
|
||||
"Synchronous": "Синхронен",
|
||||
"Asynchronous": "Асинхронен",
|
||||
"Solid": "Твърди",
|
||||
"Solid Aggressive": "Твърди агресивни",
|
||||
"Hollow": "Кух",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Листата изцяло вътре в сенник рисуват плътно: по-малко прозрачни слоеве.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Листата с една отворена страна също рисуват плътно: по-бързи, по-едри ръбове.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Капки лица между листата: по-бързо, но празнини прозират.",
|
||||
"Hotbar: Previous Slot": "Гореща лента: Предишен слот",
|
||||
"Hotbar: Next Slot": "Гореща лента: Следващ слот",
|
||||
"horizon.agent.connected.one": "Свързан: 1 връзка с агент",
|
||||
"horizon.agent.connected.many": "Свързани: %s агентни връзки",
|
||||
"horizon.agent.connected.one_ago": "Свързан: 1 връзка с агент, последна команда преди %s сек",
|
||||
"horizon.agent.connected.many_ago": "Свързани: %s агентни връзки, последна команда преди %s сек",
|
||||
"horizon.agent.waiting": "В очакване на агент.",
|
||||
"horizon.agent.off": "Изключено: агентите не могат да достигнат до тази игра.",
|
||||
"horizon.agent.session_opened": "Отворено за тази сесия от програмата, която стартира играта.",
|
||||
"horizon.agent.socket": "Сокет: %s (копирайте подробностите за пълния му път)",
|
||||
"horizon.agent.local_only": "Само локално: агенти на този компютър, никога в мрежата.",
|
||||
"%s nits": "%s нита",
|
||||
"Auto (%s nits)": "Автоматично (%s нита)",
|
||||
"%.2f nits": "%.2f нита",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Скрива модели зад непрозрачни блокове. Актуализация на обекти. Изключване с персонализирани пакети.",
|
||||
"horizon.extras.repro_keys": "Преки пътища за запис",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R стартира или спира възпроизвеждане на запис и F3+E записва неговия пакет. Изключено по подразбиране: записът копира света.",
|
||||
"horizon.agent.record_session": "Започнете запис на сесия",
|
||||
"horizon.agent.record_stop": "Спрете сесията за запис",
|
||||
"horizon.agent.export_repro": "Експортиране на Repro Bundle",
|
||||
"horizon.repro.started": "Записът започна; F3+R го спира и записва пакета.",
|
||||
"horizon.repro.stopped": "Записът е спрян: %s",
|
||||
"horizon.repro.bundle": "Repro пакет: %s",
|
||||
"horizon.repro.error": "Репро: %s",
|
||||
"horizon.repro.no_recording": "Все още няма запис за експортиране.",
|
||||
"horizon.repro.replay_started": "Стартирало повторение: %s",
|
||||
"horizon.pack.addInfo": "Добавете ресурсен пакет от Modrinth",
|
||||
"horizon.pack.addNone": "Няма намерени пакети с ресурси",
|
||||
"horizon.pack.addTitle": "Добавете пакет с ресурси",
|
||||
"horizon.pack.install": "Инсталирайте",
|
||||
"horizon.pack.installing": "Инсталиране",
|
||||
"horizon.pack.installed": "Инсталиран",
|
||||
"horizon.pack.search": "Търсене",
|
||||
"horizon.pack.searchHint": "Търсете Modrinth...",
|
||||
"horizon.pack.filters": "Филтри",
|
||||
"horizon.pack.filter.realistic": "Реалистичен",
|
||||
"horizon.pack.filter.simplistic": "Опростено",
|
||||
"horizon.pack.filter.vanillaLike": "Подобно на ванилия",
|
||||
"horizon.pack.addFailed": "Пакетът не можа да бъде добавен: %s",
|
||||
"horizon.extras.header.cosmetics": "Козметика",
|
||||
"horizon.extras.cosmetics_mine": "Моята козметика",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Скрит: всеки играч на Horizon ви вижда без вашата козметика, включително и вие. Същият превключвател като гардероба на стартера.",
|
||||
"horizon.extras.cosmetics_others": "Козметика за други играчи",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Какво носят другите играчи на Horizon: всичко, само техните стилове на имената или нищо. Скрито, играта никога не пита какво носят.",
|
||||
"horizon.extras.cosmetics_auras": "Аури и следи на други",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Аури и пътеки, които другите играчи носят. Изключено по подразбиране.",
|
||||
"horizon.extras.cosmetics.shown": "Показано",
|
||||
"horizon.extras.cosmetics.hidden": "Скрити",
|
||||
"horizon.extras.cosmetics.name_tags": "Само етикети с имена"
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
{
|
||||
"horizon.options.controller": "Ovladač",
|
||||
"horizon.options.vibration": "Vibrace",
|
||||
"horizon.options.adaptive_triggers": "Adaptivní spouštěče",
|
||||
"horizon.options.lightbar": "Světelná lišta",
|
||||
"horizon.options.look_sensitivity": "Citlivost pohledu",
|
||||
"horizon.options.invert_x": "Invertovat pohled X",
|
||||
"horizon.options.invert_y": "Invertovat pohled Y",
|
||||
"horizon.options.sneak_button": "Tlačítko plížení",
|
||||
"horizon.options.move_deadzone": "Mrtvá zóna pohybu",
|
||||
"horizon.options.look_deadzone": "Mrtvá zóna pohledu",
|
||||
"horizon.options.button_glyphs": "Glyfy tlačítek",
|
||||
"horizon.options.menu_navigation": "Navigace v menu",
|
||||
"horizon.options.on_screen_keyboard": "Klávesnice na obrazovce",
|
||||
"horizon.options.chat_delay": "Zpoždění chatu",
|
||||
"horizon.options.lightbar.health": "Zdraví",
|
||||
"horizon.options.lightbar.dimension": "Dimenze",
|
||||
"horizon.options.menu_focus": "Zaměřte se",
|
||||
"horizon.options.menu_pointer": "Ukazatel",
|
||||
"horizon.options.keyboard.horizon": "Horizont",
|
||||
"horizon.options.keyboard.steam": "Pára",
|
||||
"horizon.options.unavailable": "Tato funkce zatím není v Horizon k dispozici.",
|
||||
"horizon.options.search": "Nastavení vyhledávání...",
|
||||
"horizon.pages.shader_packs": "Shader Pack",
|
||||
"horizon.pages.shader_options": "Možnosti stínování",
|
||||
"horizon.pages.controller": "Nastavení ovladače",
|
||||
"horizon.pages.settings": "Nastavení Horizonu",
|
||||
"horizon.pages.agent": "Agent AI",
|
||||
"horizon.split.leave": "Odejděte",
|
||||
"horizon.pages.split_settings": "Nastavení rozdělené obrazovky",
|
||||
"horizon.pages.split_profiles": "kdo hraje?",
|
||||
"horizon.pages.split_setup": "Nastavení rozdělené obrazovky",
|
||||
"horizon.pages.music": "Hudba",
|
||||
"horizon.pages.music.creative": "Kreativní",
|
||||
"horizon.pages.music.underwater": "Pod vodou",
|
||||
"horizon.pages.music.end": "Konec",
|
||||
"horizon.pages.music.boss": "šéfe",
|
||||
"horizon.pages.music.credits": "Kredity",
|
||||
"horizon.pages.music.overworld_biomes": "Nadsvětové biomy",
|
||||
"horizon.pages.music.nether_biomes": "Nether biomy",
|
||||
"horizon.pages.music.end_biomes": "Ukončete biomy",
|
||||
"horizon.pages.music.other": "Jiná hudba",
|
||||
"horizon.pages.music.empty": "Žádné hudební stopy: zvuky hry se nenačtou.",
|
||||
"horizon.pages.music.tooltip": "Vestavěné stopy podle situace: zahrajte si jednu hned, nebo kteroukoli vypněte, aby ji hra přestala vybírat.",
|
||||
"horizon.pages.settings.tooltip": "Vlastní nastavení Horizon: grafika, ovladače, doplňky a mody.",
|
||||
"horizon.pages.graphics.tooltip": "Grafika, jednoduše: každé nastavení říká, kolik to stojí.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Každé grafické nastavení, včetně Horizon, s tím, co každé stojí.",
|
||||
"horizon.pages.controller.tooltip": "Ovladače: jejich tlačítka, páčky, vibrace a navigace v menu.",
|
||||
"horizon.pages.extras.tooltip": "Volitelné doplňky, všechny ve výchozím nastavení vypnuté: hra zůstane vanilková, dokud jeden nezapnete.",
|
||||
"horizon.pages.agent.tooltip": "Nechte hrát svého vlastního agenta AI: klienty horizon-ctl a MCP na tomto počítači.",
|
||||
"horizon.pages.split_profiles.tooltip": "Kdo hraje: profil uložený v této složce hry.",
|
||||
"horizon.pages.split_setup.tooltip": "Okna a monitory a kdo kde hraje.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader packy ze složky shaderpacks: vlastní Horizon a ty ve formátu shaders/. Jeden běží najednou.",
|
||||
"horizon.pages.mods.tooltip": "Mody vytvořené pro Horizon ze složky horizon-mods. Každá běží v pískovišti a zůstane vypnutá, dokud ji nezapnete.",
|
||||
"horizon.pages.open.graphics": "grafika...",
|
||||
"horizon.pages.open.graphics_advanced": "Pokročilá grafika...",
|
||||
"horizon.pages.open.shader_packs": "Shader Pack...",
|
||||
"horizon.pages.open.controller": "Nastavení ovladače...",
|
||||
"horizon.pages.open.split_setup": "Rozdělená obrazovka...",
|
||||
"horizon.pages.open.extras": "Extra...",
|
||||
"horizon.pages.open.mods": "Modifikace...",
|
||||
"horizon.pages.open.agent": "AI agent...",
|
||||
"horizon.pages.open.controller_binds": "Ovladač se váže...",
|
||||
"horizon.pages.open.horizon_settings": "Nastavení Horizonu...",
|
||||
"horizon.pages.open.music": "hudba...",
|
||||
"horizon.mods.empty": "Žádné mody: vložte soubory .hmod do složky horizon-mods",
|
||||
"horizon.mods.stopped": " (Zastaveno)",
|
||||
"horizon.mods.not_built": " (Nepostaveno)",
|
||||
"horizon.mods.dev": " (vývoj)",
|
||||
"horizon.mods.settings": "Nastavení",
|
||||
"horizon.mods.in_world": " (nechat svět změnit)",
|
||||
"horizon.mods.open_folder": "Otevřete složku Mod",
|
||||
"horizon.shader_packs.empty": "Žádné balíčky shaderů: vložte je do složky shaderpacks",
|
||||
"horizon.shader_packs.kind_horizon": " (Horizont)",
|
||||
"horizon.shader_options.open": "Možnosti stínování...",
|
||||
"horizon.shader_options.profile": "Profil",
|
||||
"horizon.shader_options.loading": "Čtení možností sady shaderů...",
|
||||
"horizon.agent.copy_details": "Kopírovat podrobnosti o připojení",
|
||||
"horizon.multiplayer.join": "Připojte se",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Zrušit opětovné připojení",
|
||||
"horizon.multiplayer.join_friend": "Přidejte se k příteli",
|
||||
"horizon.multiplayer.invite_code": "Kód pozvánky",
|
||||
"horizon.multiplayer.relay": "relé (volitelné)",
|
||||
"Preset": "Přednastaveno",
|
||||
"LOD Distance": "LOD Vzdálenost",
|
||||
"Far Detail": "Daleký detail",
|
||||
"Entity Culling": "Vyřazení entit",
|
||||
"Chunk Updates": "Aktualizace bloků",
|
||||
"Upload Budget": "Nahrát rozpočet",
|
||||
"Sky": "nebe",
|
||||
"Fog": "mlha",
|
||||
"Weather": "Počasí",
|
||||
"Animated Textures": "Animované textury",
|
||||
"Present Mode": "Současný režim",
|
||||
"Worker Threads": "Pracovní vlákna",
|
||||
"Resolution Scale": "Měřítko rozlišení",
|
||||
"Paper White": "Bílý papír",
|
||||
"Peak Brightness": "Špičkový jas",
|
||||
"Black Level": "Úroveň černé",
|
||||
"UI Brightness": "Jas uživatelského rozhraní",
|
||||
"Battery Mode": "Režim baterie",
|
||||
"Graphics API": "Grafické rozhraní API",
|
||||
"Translucent Batching": "Průsvitné dávkování",
|
||||
"Performance Overlay": "Překryvný výkon",
|
||||
"Full Detail Distance": "Úplná podrobná vzdálenost",
|
||||
"On": "Zapnuto",
|
||||
"Off": "Vypnuto",
|
||||
"Unlimited": "Neomezené",
|
||||
"Custom": "Vlastní",
|
||||
"Performance": "Výkon",
|
||||
"Balanced": "Vyvážený",
|
||||
"Quality": "Kvalita",
|
||||
"Ultra Performance": "Ultra výkon",
|
||||
"Native": "Nativní",
|
||||
"Full": "Plný",
|
||||
"Reduced": "Snížená",
|
||||
"Efficient": "Efektivní",
|
||||
"Precise": "Přesný",
|
||||
"Mailbox": "Poštovní schránka",
|
||||
"Immediate": "Okamžitě",
|
||||
"FPS and Graph": "FPS a graf",
|
||||
"No cost": "Žádné náklady",
|
||||
"Low cost": "Nízká cena",
|
||||
"Medium cost": "Střední náklady",
|
||||
"High cost": "Vysoká cena",
|
||||
"Saves time": "Šetří čas",
|
||||
"Graphics": "Grafika",
|
||||
"Done": "Hotovo",
|
||||
"This feature is not yet available in Horizon.": "Tato funkce zatím není v Horizon k dispozici.",
|
||||
"Your display or system doesn't offer HDR output.": "Váš displej nebo systém nenabízí výstup HDR.",
|
||||
"Turn HDR on to use it.": "Chcete-li jej používat, zapněte HDR.",
|
||||
"Your GPU or graphics API doesn't support it.": "Vaše GPU nebo grafické rozhraní API to nepodporuje.",
|
||||
"%s chunks": "%s kousky",
|
||||
"%s per frame": "%s na snímek",
|
||||
"%s MiB/frame": "%s MiB/snímek",
|
||||
"%s KiB/frame": "%s KiB/snímek",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Úplný rozsah terénu. LOD Distance může rozšířit místní světy dále.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Hrubý vzdálený terén v místních světech. Integrované GPU se zastaví na 128 blocích.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Snížené vykreslování daleko hrubšího terénu: rychlejší na tomto integrovaném GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Snížené vykresluje daleko hrubší terén, u GPU se zpomaluje.",
|
||||
"Field of view. Wider shows more terrain at once.": "Zorné pole. Širší zobrazuje více terénu najednou.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Počet snímků za sekundu; čepice šetří energii a teplo.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Čeká na obnovení displeje: žádné trhání, malá latence.",
|
||||
"Size of menus and the HUD.": "Velikost menu a HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Nastaví společně níže uvedené možnosti. Změnou jednoho se stane vlastní.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Láva, oheň a sluneční světlo září jasněji než bílé. Vyžaduje HDR displej.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Váš displej je nejtmavší černý. 0 pro OLED; LCD trochu svítí.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Jak se získá jasný melír. Auto: vrchol, který váš systém hlásí.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Jak vypadají zářivě bílé papíry a nabídky. Auto: vaše plocha je bílá.",
|
||||
"How bright the HUD and menus are, against paper white.": "Jak jasný je HUD a nabídky oproti bílé na papíře.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Jak dalece davy, plodiny a stroje fungují. CPU čas.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Jak daleko jsou mobové a předměty nakresleny jako podíl normálu.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Přeskočí terén skrytý za uzavřenými úseky. Vypnutí stojí hodně.",
|
||||
"Hides entities behind opaque blocks.": "Skryje entity za neprůhlednými bloky.",
|
||||
"No entity occlusion culling.": "Žádné vyřazení okluze entity.",
|
||||
"CPU occlusion test runs this frame.": "Test okluze CPU spustí tento rámec.",
|
||||
"Occlusion checks run on a worker thread.": "Kontroly okluze běží na pracovním vláknu.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Síťované sekce nahrané na snímek. Nižší je při načítání hladší.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Jemné stíny v rozích. Stojí jen kus času výstavby.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Menší kopie textur bloků pro vzdálený terén: méně třpytu.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Při stavbě kousků mísí barvy trávy a vody napříč biomy.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Cloudová vrstva: vypnutá, plochá (rychlá) nebo 3D (efektní).",
|
||||
"How far clouds reach, in chunks.": "Jak daleko dosahují mraky, v kouscích.",
|
||||
"Draws the sky, sun, moon and stars.": "Kreslí oblohu, slunce, měsíc a hvězdy.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Vzdálená mlha. Vypnuto ukazuje okraj načteného světa.",
|
||||
"Draws rain and snow.": "Přitahuje déšť a sníh.",
|
||||
"How far rain and snow fall around you.": "Jak daleko kolem vás padá déšť a sníh.",
|
||||
"How many particles effects spawn.": "Kolik částic se vytvoří.",
|
||||
"Round shadows under mobs and items.": "Kulaté stíny pod moby a předměty.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Listy s průhlednými mezerami. Off je vykreslí pevně.",
|
||||
"Sorts water and glass more exactly behind each other.": "Třídí vodu a sklo přesněji za sebou.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Který kus přestavby čeká na rám: žádný, poblíž nebo váš.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animuje vodu, lávu, oheň a další pohyblivé textury.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Světové pixely na pixel obrazovky. Pod 100 % je rychlejší na GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Vyhlazuje zubaté okraje. FXAA: měkčí. TAA: nejhladší, pohyblivé věci se mohou táhnout.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Zmenší svět a přestaví ho pomocí TAA. Rychlejší, měkčí.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "Výstup HDR na displeji HDR: láva, oheň a sluneční světlo září jasněji.",
|
||||
"How frames reach the screen while VSync is off.": "Jak se snímky dostanou na obrazovku, když je VSync vypnutá.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Efektivní režim spánku až do dalšího snímku; Přesná rotace, jako vanilka.",
|
||||
"Fills the screen (borderless).": "Vyplní obrazovku (bez okrajů).",
|
||||
"Brightens dark places.": "Rozjasňuje tmavá místa.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Při minimalizaci nebo nečinnosti sníží snímkovou frekvenci.",
|
||||
"Sways the view while walking.": "Při chůzi kývá výhled.",
|
||||
"Fades newly loaded chunks in.": "Nově načtené kousky zmizí.",
|
||||
"Darkens the screen's edges.": "Ztmaví okraje obrazovky.",
|
||||
"Shows when the world saves.": "Ukazuje, kdy svět zachraňuje.",
|
||||
"Shows attack cooldown.": "Ukazuje cooldown útoku.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 snímků za sekundu (30 v nabídkách nebo nezaostřené), polovina pracovníků: méně energie.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Vlákna, která generují, osvětlují a propojují kousky. Další start.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, pak DirectX 12 (Windows) nebo OpenGL (Linux), kde selže. Další start.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Voda a sklo v jedné vyrovnávací paměti: méně remízových volání, o něco více VRAM. Další start.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Data sítě odesílaná do GPU na snímek. Nižší je při načítání hladší.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Frekvence snímků a časy snímků během hry s grafem.",
|
||||
"horizon.multiplayer.finding_friend": "Hledání světa svého přítele...",
|
||||
"horizon.multiplayer.not_signed_in": "Tento server je v online režimu. Přihlaste se pomocí svého účtu Microsoft ve spouštěči Horizon a připojte se k němu.",
|
||||
"horizon.multiplayer.invalid_address": "Neplatná adresa serveru: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Přímé spojení s vaším přítelem...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Připojeno k vašemu příteli přes relé...",
|
||||
"General": "Generál",
|
||||
"Advanced": "Pokročilé",
|
||||
"Undo": "Vrátit zpět",
|
||||
"Search options": "Možnosti vyhledávání",
|
||||
"Shortcut": "Zkratka",
|
||||
"Distance": "Vzdálenost",
|
||||
"Display": "Displej",
|
||||
"Interface": "Rozhraní",
|
||||
"Sky and weather": "Obloha a počasí",
|
||||
"Lighting and entities": "Osvětlení a entity",
|
||||
"Textures": "Textury",
|
||||
"Image": "Obrázek",
|
||||
"Chunk loading": "Načítání kusů",
|
||||
"Culling": "Porážení",
|
||||
"Power": "Moc",
|
||||
"Frames": "Rámečky",
|
||||
"Overlay": "Překryvná vrstva",
|
||||
"Output": "Výstup",
|
||||
"Brightness": "Jas",
|
||||
"How far you see, the window and the HUD.": "Jak daleko vidíte, okno a HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Obloha a počasí, osvětlení, textury a obraz.",
|
||||
"Chunk loading, culling and power use.": "Nakládání, utracení a spotřeba energie.",
|
||||
"How frames reach the screen, and the performance overlay.": "Jak se snímky dostanou na obrazovku a překryvný výkon.",
|
||||
"Brighter-than-white output on an HDR display.": "Výstup jasnější než bílý na displeji HDR.",
|
||||
"Results for \"%s\"": "Výsledky pro „%s“",
|
||||
"No settings match.": "Žádné nastavení neodpovídá.",
|
||||
"horizon.title.not_affiliated": "Není spojen s Mojang nebo Microsoft",
|
||||
"horizon.extras.header.lighting": "Osvětlení",
|
||||
"horizon.extras.header.third_person": "Třetí osoba",
|
||||
"horizon.extras.header.particles": "Částice",
|
||||
"horizon.extras.header.inventory": "Inventář",
|
||||
"horizon.extras.header.sound": "Zvuk",
|
||||
"horizon.extras.header.menus": "Menu",
|
||||
"horizon.extras.dynamic_lights": "Dynamická světla",
|
||||
"horizon.extras.own_name_tag": "Vlastní jmenovka",
|
||||
"horizon.extras.hide_block_particles": "Skryjte částice pro rozbití bloku",
|
||||
"horizon.extras.auto_reconnect": "Automatické opětovné připojení",
|
||||
"horizon.extras.ui_sound_reserve": "Ponechat kanály pro zvuky uživatelského rozhraní",
|
||||
"horizon.extras.music_page": "Hudební stránka",
|
||||
"horizon.extras.mouse_tweaks": "Ovládání myši Tweaks",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Rolovací kolečko posouvá položky",
|
||||
"horizon.extras.sort": "Tlačítka řazení",
|
||||
"horizon.extras.sort_key": "Klíč řazení",
|
||||
"horizon.extras.no_ai": "Žádná umělá inteligence",
|
||||
"horizon.extras.agent_control": "Kontrola agentů",
|
||||
"horizon.extras.discord_presence": "Neshoda bohatá přítomnost",
|
||||
"horizon.extras.chat_animation": "Hladký chat",
|
||||
"horizon.extras.chat_indicator": "Indikátor zprávy",
|
||||
"horizon.extras.wheel_cycles_settings": "Rolovací kolečko mění nastavení",
|
||||
"horizon.extras.pack_browser": "Prohlížeč balíčku zdrojů",
|
||||
"horizon.extras.fullbright.tooltip": "Ukažte bloky a entity při plném osvětlení, dokonce i v tmavých oblastech.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Pochodně a jiné světelné zdroje držené, shozené nebo v plamenech osvětlují bloky kolem nich.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Zobrazuje vaše jméno nad vaší hlavou ve třetí osobě, jak to vidí ostatní hráči. Skryté, když server skryje jména vašeho týmu nebo jste neviditelní.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Skryje částečky trosek, které prasknou z rozbitého bloku a rozpadnou se z toho, který vytěžíte. Ostatní částice zůstávají.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Znovu se připojí k serveru po ztrátě připojení nebo restartu, nikdy po kopu nebo banu.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Zvuky na světě nechávají 16 z 247 zvukových kanálů volných, takže jejich záplava (velká exploze) nemůže umlčet kliknutí na nabídku.",
|
||||
"horizon.extras.music_page.tooltip": "Music & Sounds dostane stránku Hudba...: přehrajte libovolnou vestavěnou stopu hned, nebo jednu vypněte, aby ji hra přestala vybírat. Nic se nezmění, dokud stopu nevypnete.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Tažením doprava umístíte jednu na každý slot, tažením doleva shromažďujete zadržený předmět, Shift + tažením doleva posunete každý slot.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Dolů pošle jednu položku slotu na druhou stranu, nahoru jednu vrátí zpět.",
|
||||
"horizon.extras.sort.tooltip": "Třídí inventář, truhly, sudy, ender truhly a shulker boxy v kreativním pořadí karet a slučuje hromádky.",
|
||||
"horizon.extras.sort_key.tooltip": "Seřadí část pod kurzorem. Klikněte a poté stiskněte klávesu; Escape to rozváže.",
|
||||
"horizon.extras.no_ai.tooltip": "Skryje a vypne všechny funkce AI: ovládání agenta, horizon-ctl a servery MCP. Vypínač spouštěče také.",
|
||||
"horizon.extras.agent_control.tooltip": "Váš vlastní AI agent může hrát tuto hru prostřednictvím horizon-ctl nebo klienta MCP pouze na tomto počítači.",
|
||||
"horizon.extras.discord_presence.tooltip": "Zobrazuje Playing Horizon na vašem Discord profilu, zatímco Discord běží: nabídky, single player nebo multiplayer, dimenze a hraný čas. Nikdy jméno světa nebo adresa serveru.",
|
||||
"Calibrate...": "Kalibrovat...",
|
||||
"Calibrate Anyway...": "Přesto kalibrovat...",
|
||||
"Use Automatic Setup": "Použijte Automatické nastavení",
|
||||
"Shortcut: %s": "Zkratka: %s",
|
||||
"Next": "Další",
|
||||
"HDR Calibration %s/%s: %s": "Kalibrace HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Nastaví tři pod celou obrazovkou, vždy jeden testovací obrázek.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Klíč, který otevře tuto obrazovku ze hry. Zpočátku bez vazby.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Váš displej neříkal, jak jasný je: zkalibrujte jej, abyste jej nastavili.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Již nastaveno pro váš displej. Kalibrujte pouze v případě, že HDR vypadá špatně.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Vrátí maximální jas a papírovou bílou zpět do nastavení vašeho displeje.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Nastavte pro svůj displej. Nechte na Auto, pokud to nevypadá špatně.",
|
||||
"Lower it until the logo just disappears into the black.": "Snižte jej, dokud logo nezmizí v černé barvě.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Zvedněte jej, dokud logo nezmizí ve světlém čtverci.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Udělejte z karty bílou, na kterou se budete pohodlně dívat po dlouhou dobu ve světle, ve kterém hrajete.",
|
||||
"horizon.splitscreen.profiles.forget": "Zapomeňte",
|
||||
"horizon.splitscreen.profiles.create": "Vytvořit",
|
||||
"horizon.splitscreen.profiles.you": " - vy",
|
||||
"horizon.splitscreen.profiles.forget_named": "Zapomenout %s",
|
||||
"horizon.splitscreen.profiles.previous": "Předchozí",
|
||||
"horizon.splitscreen.profiles.next": "Další",
|
||||
"horizon.splitscreen.profiles.new_button": "Nový profil...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Zapomenout profil?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Opustí seznam této instance.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Světový pokrok a nastavení jsou zachovány.",
|
||||
"horizon.splitscreen.profiles.new_title": "Nový profil",
|
||||
"horizon.splitscreen.profiles.name_hint": "Jméno (3 až 16 písmen, číslic, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Přepnout profil",
|
||||
"horizon.splitscreen.profiles.offline_note": "Offline profily patří k této instanci",
|
||||
"horizon.splitscreen.profiles.move": "Pohybujte se tam, kde hrajete",
|
||||
"horizon.splitscreen.profiles.move_place": "Přesuňte se, kde hrajete: %s",
|
||||
"horizon.friends.title": "Hrajte s přáteli",
|
||||
"horizon.friends.hint": "Přátelé vloží kód za tlačítko Přátelé na titulní obrazovce, do Přímého připojení nebo na stránku Play spouštěče.",
|
||||
"horizon.friends.copied": "Zkopírováno",
|
||||
"horizon.friends.copy_code": "Kopírovat kód",
|
||||
"horizon.friends.stop_sharing": "Přestat sdílet",
|
||||
"horizon.friends.none_yet": "Zatím tu nejsou žádní přátelé.",
|
||||
"horizon.friends.here_now": "Tady teď: %s",
|
||||
"horizon.friends.share_intro": "Sdílejte tento svět pomocí zvacího kódu: otevře se na LAN a přátelé na internetu se připojí pomocí kódu. Není zapojen žádný náš server.",
|
||||
"horizon.friends.start_sharing": "Začněte sdílet",
|
||||
"horizon.friends.troubleshooting": "Odstraňování problémů...",
|
||||
"horizon.friends.stopped": "Sdílení kódu pozvánky bylo zastaveno.",
|
||||
"horizon.friends.short_code_changed": "Někdo neustále zkoušel špatný krátký kód, takže se to změnilo.",
|
||||
"horizon.friends.wrong_code": "Někdo zkusil špatný kód.",
|
||||
"horizon.friends.code_ready": "Váš zvací kód je připraven: Herní menu, Hrajte s přáteli. Zkopírujte jej a pošlete svým přátelům.",
|
||||
"horizon.friends.share_failed": "Nepodařilo se sdílet svět: %s",
|
||||
"horizon.friends.finding_route": "Zjištění, jak vás mohou přátelé kontaktovat…",
|
||||
"horizon.osk.space": "Prostor",
|
||||
"horizon.osk.delete": "Smazat",
|
||||
"horizon.osk.type": "Typ",
|
||||
"horizon.osk.symbols": "Symboly",
|
||||
"horizon.osk.close": "Zavřít",
|
||||
"horizon.extras.chat_animation.tooltip": "Nová čára chatu se vsune zleva, zatímco se mizí, místo aby vypadala celá.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Barevný pruh nalevo od chatovací linky.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Rolovací kolečko nad tlačítkem v nastavení mění jeho hodnotu, jako u vanilky. Vypnuto, kolečko pouze posouvá seznam.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs dostane tlačítko Přidat, které prohledá Modrinth a nainstaluje balíček do vaší složky balíčků. Vypnuto, hra nikdy nekontaktuje Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Ve světě se může jako nový hráč připojit další ovladač: stiskněte A, když se zeptá, pak vyberte profil a kde hrát.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
"horizon.splitscreen.setup.window": "okno %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nové okno",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Vyplňte %s",
|
||||
"horizon.splitscreen.setup.done": "Hotovo",
|
||||
"horizon.splitscreen.setup.on": "Zapnuto",
|
||||
"horizon.splitscreen.setup.off": "Vypnuto",
|
||||
"horizon.splitscreen.setup.player": "Hráč %s",
|
||||
"horizon.splitscreen.setup.revert": "Vrátit zpět",
|
||||
"horizon.splitscreen.setup.apply": "Použít",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "V okně 1 nikdo nehraje: skrývá se",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "V okně %s nikdo nehraje: zavře se při Použít",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "V okně %s nikdo nehraje: neotevře se",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Okno %s vyplní monitor okna 1: jeho hráči hrají v okně 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Monitor okna %s není připojen: jeho hráči hrají v okně 1",
|
||||
"horizon.splitscreen.setup.status.off": "Splitscreen je vypnutý.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Zapněte jej a poté stiskněte tlačítko na jiném ovladači na světě.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Stisknutím tlačítka A se připojíte a vyberete profil.",
|
||||
"horizon.splitscreen.setup.status.idle": "Splitscreen nečinný",
|
||||
"horizon.splitscreen.setup.status.alone": "Žádní další místní hráči.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Solo ovládací prvky a zpětná vazba regulátoru se nemění.",
|
||||
"horizon.splitscreen.setup.window_empty": "Okno %s: prázdné",
|
||||
"horizon.splitscreen.setup.note.select": "Klikněte na přehrávač nebo jej přetáhněte do okna nebo na monitor.",
|
||||
"horizon.splitscreen.setup.note.apply": "Použít pro přehrávání tímto způsobem, Vrátit pro vrácení zpět.",
|
||||
"horizon.splitscreen.setup.note.place": "Kde by měl %s hrát?",
|
||||
"horizon.splitscreen.setup.this_player": "tohoto hráče",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen se vypne, jakmile ostatní odejdou (jejich nabídka pozastavení).",
|
||||
"horizon.splitscreen.setup.window_place": "Okno %s (%s z %s)",
|
||||
"horizon.splitscreen.setup.names": "Jména",
|
||||
"horizon.splitscreen.setup.identify": "Identifikovat",
|
||||
"horizon.splitscreen.setup.layout.two": "2 sdílení: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 sdílení: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Bok po boku",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Nahoře a dole",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Jeden široký nahoře",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Jeden vysoký vlevo",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Bok po boku",
|
||||
"horizon.splitscreen.setup.help.player": "Kliknutím nebo přetažením %s je přesunete.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (vedle sebe, když je okno široké). Stiskněte pro změnu.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Stiskněte pro změnu.",
|
||||
"horizon.splitscreen.setup.help.switch": "Rozdělená obrazovka pro tento počítač, zapnutá nebo vypnutá.",
|
||||
"horizon.splitscreen.setup.help.identify": "Pohled každého hráče ukazuje, ve kterém okně se nachází.",
|
||||
"horizon.splitscreen.setup.help.names": "Jméno každého hráče v rohu jeho pohledu.",
|
||||
"horizon.splitscreen.setup.help.revert": "Vraťte zpět změny, které dosud nebyly použity.",
|
||||
"horizon.splitscreen.setup.help.apply": "Hrajte tak, jak ukazuje mapa.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Zatím se nic nezměnilo.",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Ovladač připojen",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Ovladač odpojen",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Znovu jej připojte nebo stiskněte tlačítko na jiném",
|
||||
"horizon.splitscreen.prompt.join.one": "%s se chce připojit",
|
||||
"horizon.splitscreen.prompt.join.many": "%s kontroloři se chtějí připojit",
|
||||
"horizon.splitscreen.prompt.join.yes": "Připojte se",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Hrajte jako P1",
|
||||
"horizon.splitscreen.prompt.join.no": "ne",
|
||||
"horizon.splitscreen.prompt.lost": "%s: ovladač odpojen",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Použijte jiný",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Odejděte",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Váš ovladač vás přestane řídit a je mu nabídnuto připojení (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s až %s: A to sem hodí, B to vrátí.",
|
||||
"horizon.splitscreen.setup.note.held": "%s zvedl: vlevo/vpravo vybírá tam, kde A spadne, B to vrátí zpět.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Skryje modely za neprůhledné bloky. Async kontroluje off-thread; entity klíštěte.",
|
||||
"Block Entities": "Blokovat entity",
|
||||
"Shulker Boxes": "Krabice Shulker",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Znovu používá usazené krabice; pohyb víka zůstává animovaný.",
|
||||
"Signs": "Známky",
|
||||
"Reuses sign text until text, light, or font changes.": "Znovu používá podepsaný text, dokud se nezmění text, světlo nebo písmo.",
|
||||
"Decorated Pots": "Zdobené hrnce",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Znovu používá nehybné hrnce; break wobble and sherd changes redraw.",
|
||||
"Bells": "Zvony",
|
||||
"Reuses still bells; ringing stays animated.": "Znovu používá stále zvony; vyzvánění zůstává animované.",
|
||||
"Chests": "Truhly",
|
||||
"Reuses closed chests; moving lids stay animated.": "Znovu používá uzavřené truhly; pohyblivá víčka zůstávají animovaná.",
|
||||
"Banners": "Bannery",
|
||||
"Reuses poles; flags and pattern layers still update.": "Znovu používá tyče; příznaky a vrstvy vzorů se stále aktualizují.",
|
||||
"Copper Golem Statues": "Sochy měděných golemů",
|
||||
"Reuses statue geometry until its block state changes.": "Znovu používá geometrii sochy, dokud se její stav bloku nezmění.",
|
||||
"Leaf Culling": "Vyřazování listů",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Tvář každého listu. Vanilkovy efektní listy.",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Chrání jemné rostliny v balíčcích s vysokým rozlišením před vyblednutím.",
|
||||
"Screenshot Quality": "Kvalita snímku obrazovky",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2 screenshoty v tomto mnohonásobku rozlišení hry. Vyšší trvá déle.",
|
||||
"Frame Generation": "Generování snímků",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Zobrazuje vytvořený rám mezi každým dvěma skutečnými: hladší, trochu více zpoždění.",
|
||||
"Shadow Cache": "Stínová mezipaměť",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Znovu použije mapu stínu balíčku, když se slunce, pohled a svět shodují. Ve výchozím nastavení vypnuto.",
|
||||
"Find BlueMap": "Najděte BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Na serverech najde jejich webovou mapu BlueMap pro vzdálený terén. Další připojení.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Jak daleko ve vašem okolí jsou staženy dlaždice BlueMap. Další připojení.",
|
||||
"Synchronous": "Synchronní",
|
||||
"Asynchronous": "Asynchronní",
|
||||
"Solid": "Pevné",
|
||||
"Solid Aggressive": "Solidní agresivní",
|
||||
"Hollow": "Dutý",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Listy plně uvnitř baldachýnu kreslí pevně: méně průhledných vrstev.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Listy s jednou otevřenou stranou se také kreslí pevně: rychlejší, robustnější okraje.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Klesá obličeje mezi listy: rychlejší, ale prohlížejí mezery.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Předchozí slot",
|
||||
"Hotbar: Next Slot": "Hotbar: Další slot",
|
||||
"horizon.agent.connected.one": "Připojeno: 1 připojení agenta",
|
||||
"horizon.agent.connected.many": "Připojeno: %s připojení agentů",
|
||||
"horizon.agent.connected.one_ago": "Připojeno: 1 připojení agenta, poslední příkaz před %s s",
|
||||
"horizon.agent.connected.many_ago": "Připojeno: %s připojení agentů, poslední příkaz před %s s",
|
||||
"horizon.agent.waiting": "Čekání na agenta.",
|
||||
"horizon.agent.off": "Vypnuto: agenti se k této hře nedostanou.",
|
||||
"horizon.agent.session_opened": "Otevřeno pro tuto relaci programem, který spustil hru.",
|
||||
"horizon.agent.socket": "Socket: %s (zkopírujte podrobnosti pro jeho úplnou cestu)",
|
||||
"horizon.agent.local_only": "Pouze místní: agenti na tomto počítači, nikdy v síti.",
|
||||
"%s nits": "%s hnid",
|
||||
"%.2f nits": "%.2f hnid",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Skryje modely za neprůhledné bloky. Aktualizace entit. Vypnuto s vlastními balíčky.",
|
||||
"horizon.extras.repro_keys": "Zkratky nahrávání",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R spustí nebo zastaví repro záznam a F3+E zapíše jeho svazek. Ve výchozím nastavení vypnuto: nahrávka zkopíruje svět.",
|
||||
"horizon.agent.record_session": "Spusťte záznam relace",
|
||||
"horizon.agent.record_stop": "Zastavit záznam relace",
|
||||
"horizon.repro.started": "Nahrávání zahájeno; F3+R to zastaví a zapíše svazek.",
|
||||
"horizon.repro.stopped": "Nahrávání zastaveno: %s",
|
||||
"horizon.repro.bundle": "Repro balíček: %s",
|
||||
"horizon.repro.no_recording": "Zatím není k dispozici žádná nahrávka k exportu.",
|
||||
"horizon.repro.replay_started": "Přehrávání zahájeno: %s",
|
||||
"horizon.pack.addInfo": "Přidejte zdrojový balíček z Modrinth",
|
||||
"horizon.pack.addWorking": "Ptát se Modrintha...",
|
||||
"horizon.pack.addNone": "Nebyly nalezeny žádné balíčky zdrojů",
|
||||
"horizon.pack.addTitle": "Přidejte Resource Pack",
|
||||
"horizon.pack.install": "Instalovat",
|
||||
"horizon.pack.installing": "Instalace",
|
||||
"horizon.pack.installed": "Instalováno",
|
||||
"horizon.pack.search": "Hledat",
|
||||
"horizon.pack.searchHint": "Hledat Modrinth...",
|
||||
"horizon.pack.filters": "Filtry",
|
||||
"horizon.pack.filter.realistic": "Realistické",
|
||||
"horizon.pack.filter.simplistic": "Zjednodušené",
|
||||
"horizon.pack.filter.vanillaLike": "Vanilkové",
|
||||
"horizon.pack.addFailed": "Balíček nelze přidat: %s",
|
||||
"horizon.extras.header.cosmetics": "Kosmetika",
|
||||
"horizon.extras.cosmetics_mine": "Moje kosmetika",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skryté: každý hráč Horizon vás vidí bez vaší kosmetiky, včetně vás. Stejný vypínač jako skříň odpalovacího zařízení.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetika ostatních hráčů",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Co nosí ostatní hráči Horizon: všechno, jen styly jmenovek nebo nic. Skryto, hra se nikdy neptá, co nosí.",
|
||||
"horizon.extras.cosmetics_auras": "Aury a stezky ostatních",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aury a stezky, které nosí ostatní hráči. Ve výchozím nastavení vypnuto.",
|
||||
"horizon.extras.cosmetics.shown": "Zobrazeno",
|
||||
"horizon.extras.cosmetics.hidden": "Skrytá",
|
||||
"horizon.extras.cosmetics.name_tags": "Pouze jmenovky"
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
{
|
||||
"horizon.options.adaptive_triggers": "Adaptive triggere",
|
||||
"horizon.options.lightbar": "Lysbjælke",
|
||||
"horizon.options.look_sensitivity": "Følsomhed for kig",
|
||||
"horizon.options.invert_x": "Inverter X-aksen for kig",
|
||||
"horizon.options.invert_y": "Inverter Y-aksen for kig",
|
||||
"horizon.options.sneak_button": "Snigeknap",
|
||||
"horizon.options.move_deadzone": "Dødzone for bevægelse",
|
||||
"horizon.options.look_deadzone": "Dødzone for kig",
|
||||
"horizon.options.button_glyphs": "Knap Glyfer",
|
||||
"horizon.options.menu_navigation": "Menu navigation",
|
||||
"horizon.options.on_screen_keyboard": "Skærmtastatur",
|
||||
"horizon.options.chat_delay": "Chatforsinkelse",
|
||||
"horizon.options.lightbar.health": "Sundhed",
|
||||
"horizon.options.menu_focus": "Fokus",
|
||||
"horizon.options.keyboard.steam": "Damp",
|
||||
"horizon.options.unavailable": "Denne funktion er endnu ikke tilgængelig i Horizon.",
|
||||
"horizon.options.search": "Søgeindstillinger...",
|
||||
"horizon.pages.shader_packs": "Shader-pakker",
|
||||
"horizon.pages.shader_options": "Shader indstillinger",
|
||||
"horizon.pages.controller": "Controllerindstillinger",
|
||||
"horizon.pages.controller_binds": "Controller binder",
|
||||
"horizon.pages.agent": "AI-agent",
|
||||
"horizon.split.leave": "Forlad",
|
||||
"horizon.pages.split_settings": "Indstillinger for splitskærm",
|
||||
"horizon.pages.split_profiles": "Hvem spiller?",
|
||||
"horizon.pages.split_setup": "Opsætning af splitskærm",
|
||||
"horizon.pages.music": "Musik",
|
||||
"horizon.pages.music.overworld": "Oververden",
|
||||
"horizon.pages.music.creative": "Kreativ",
|
||||
"horizon.pages.music.underwater": "Under vandet",
|
||||
"horizon.pages.music.end": "Slutningen",
|
||||
"horizon.pages.music.credits": "Kreditter",
|
||||
"horizon.pages.music.overworld_biomes": "Oververdens biomer",
|
||||
"horizon.pages.music.nether_biomes": "Underlige biomer",
|
||||
"horizon.pages.music.end_biomes": "Slut biomer",
|
||||
"horizon.pages.music.other": "Anden musik",
|
||||
"horizon.pages.music.empty": "Ingen musiknumre: Spillets lyde er ikke indlæst.",
|
||||
"horizon.pages.music.tooltip": "De indbyggede numre, efter situation: Spil et nu, eller sluk et hvilket som helst, så spillet stopper med at vælge det.",
|
||||
"horizon.pages.settings.tooltip": "Horizons egne indstillinger: grafik, controllere, ekstramateriale og mods.",
|
||||
"horizon.pages.graphics.tooltip": "Grafik, ganske enkelt: hver indstilling siger, hvad den koster.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Hver grafikindstilling, Horizon er inkluderet, med hvad hver enkelt koster.",
|
||||
"horizon.pages.controller.tooltip": "Controllere: deres knapper, sticks, vibration og menunavigation.",
|
||||
"horizon.pages.extras.tooltip": "Valgfrie tilføjelser, alle slået fra som standard: spillet forbliver vanilje, indtil du tænder en.",
|
||||
"horizon.pages.agent.tooltip": "Lad din egen AI-agent spille: horizon-ctl og MCP-klienter på denne computer.",
|
||||
"horizon.pages.split_profiles.tooltip": "Hvem spiller: en profil gemt i denne spilmappe.",
|
||||
"horizon.pages.split_setup.tooltip": "Vinduerne og skærmene, og hvem spiller hvor.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader-pakker fra shaderpacks-mappen: Horizons egne og dem i shaders/-formatet. Man løber ad gangen.",
|
||||
"horizon.pages.mods.tooltip": "Mods lavet til Horizon, fra mappen horizon-mods. Hver kører i en sandkasse og forbliver slukket, indtil du tænder den.",
|
||||
"horizon.pages.open.graphics": "Grafik...",
|
||||
"horizon.pages.open.graphics_advanced": "Avanceret grafik...",
|
||||
"horizon.pages.open.shader_packs": "Shader-pakker...",
|
||||
"horizon.pages.open.controller": "Controllerindstillinger...",
|
||||
"horizon.pages.open.extras": "Ekstra...",
|
||||
"horizon.pages.open.agent": "AI-agent...",
|
||||
"horizon.pages.open.controller_binds": "Controller binder...",
|
||||
"horizon.pages.open.music": "Musik...",
|
||||
"horizon.mods.empty": "Ingen mods: læg .hmod-filer i mappen horizon-mods",
|
||||
"horizon.mods.stopped": " (stoppet)",
|
||||
"horizon.mods.not_built": " (Ikke bygget)",
|
||||
"horizon.mods.dev": " (udvikler)",
|
||||
"horizon.mods.settings": "Indstillinger",
|
||||
"horizon.mods.in_world": " (lad verden ændre sig)",
|
||||
"horizon.mods.open_folder": "Åbn Mod-mappe",
|
||||
"horizon.shader_packs.empty": "Ingen shader-pakker: læg dem i shaderpacks-mappen",
|
||||
"horizon.shader_options.open": "Indstillinger for skygge...",
|
||||
"horizon.shader_options.profile": "Profil",
|
||||
"horizon.shader_options.loading": "Læser shader-pakkens muligheder...",
|
||||
"horizon.agent.copy_details": "Kopier forbindelsesoplysninger",
|
||||
"horizon.multiplayer.join": "Deltag",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Annuller Gentilslutning",
|
||||
"horizon.multiplayer.join_friend": "Slut dig til en ven",
|
||||
"horizon.multiplayer.invite_code": "Invitationskode",
|
||||
"horizon.multiplayer.relay": "Relæ (valgfrit)",
|
||||
"Preset": "Forudindstillet",
|
||||
"LOD Distance": "LOD Afstand",
|
||||
"Far Detail": "Fjern detalje",
|
||||
"Cave Culling": "Nedskæring af huler",
|
||||
"Entity Culling": "Entitetsnedskæring",
|
||||
"Chunk Updates": "Chunk opdateringer",
|
||||
"Upload Budget": "Upload budget",
|
||||
"Sky": "Himmel",
|
||||
"Fog": "Tåge",
|
||||
"Weather": "Vejret",
|
||||
"Animated Textures": "Animerede teksturer",
|
||||
"Present Mode": "Nuværende tilstand",
|
||||
"Worker Threads": "Arbejdstråde",
|
||||
"Resolution Scale": "Opløsningsskala",
|
||||
"Upscaling": "Opskalering",
|
||||
"Paper White": "Papir hvid",
|
||||
"Peak Brightness": "Højeste lysstyrke",
|
||||
"Black Level": "Sort niveau",
|
||||
"UI Brightness": "UI lysstyrke",
|
||||
"Battery Mode": "Batteritilstand",
|
||||
"Graphics API": "Grafik API",
|
||||
"Translucent Batching": "Gennemskinnelig batching",
|
||||
"Performance Overlay": "Ydeevne Overlay",
|
||||
"Full Detail Distance": "Fuld detalje afstand",
|
||||
"On": "På",
|
||||
"Off": "Fra",
|
||||
"Unlimited": "Ubegrænset",
|
||||
"Custom": "Brugerdefineret",
|
||||
"Performance": "Ydeevne",
|
||||
"Balanced": "Balanceret",
|
||||
"Quality": "Kvalitet",
|
||||
"Ultra Performance": "Ultra ydeevne",
|
||||
"Full": "Fuld",
|
||||
"Reduced": "Reduceret",
|
||||
"Efficient": "Effektiv",
|
||||
"Precise": "Præcis",
|
||||
"Mailbox": "Postkasse",
|
||||
"Immediate": "Øjeblikkelig",
|
||||
"FPS and Graph": "FPS og graf",
|
||||
"No cost": "Ingen omkostninger",
|
||||
"Low cost": "Lave omkostninger",
|
||||
"Medium cost": "Mellem omkostning",
|
||||
"High cost": "Høje omkostninger",
|
||||
"Saves time": "Sparer tid",
|
||||
"Graphics": "Grafik",
|
||||
"Done": "Færdig",
|
||||
"This feature is not yet available in Horizon.": "Denne funktion er endnu ikke tilgængelig i Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Din skærm eller dit system tilbyder ikke HDR-output.",
|
||||
"Turn HDR on to use it.": "Slå HDR til for at bruge det.",
|
||||
"Your GPU or graphics API doesn't support it.": "Din GPU eller grafik-API understøtter det ikke.",
|
||||
"%s chunks": "%s bidder",
|
||||
"%s per frame": "%s pr. ramme",
|
||||
"%s MiB/frame": "%s MiB/ramme",
|
||||
"%s KiB/frame": "%s KiB/ramme",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Fuld detaljeret terrænområde. LOD Distance kan udvide lokale verdener længere.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Groft fjernt terræn i lokale verdener. Integrerede GPU'er stopper ved 128 bidder.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Reduceret trækker langt grovere terræn: hurtigere på denne integrerede GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Reduceret trækker terrænet langt grovere, for GPU'er bliver det langsommere.",
|
||||
"Field of view. Wider shows more terrain at once.": "Synsfelt. Bredere viser mere terræn på én gang.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Caps billeder per sekund; en hætte sparer strøm og varme.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Venter på skærmens opdatering: ingen rivning, lidt forsinkelse.",
|
||||
"Size of menus and the HUD.": "Størrelse på menuer og HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Indstiller nedenstående muligheder sammen. At ændre en gør det brugerdefineret.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, ild og sollys skinner klarere end hvidt. Har brug for en HDR-skærm.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Din skærm er mørkest sort. 0 for OLED; LCD'er lyser lidt.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Hvor lyse højdepunkter bliver. Auto: det højeste, dit system rapporterer.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Hvor lyst hvidt papir og menuer ser ud. Auto: dit skrivebord er hvidt.",
|
||||
"How bright the HUD and menus are, against paper white.": "Hvor lys HUD og menuer er i forhold til hvidt papir.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Hvor langt mobs, afgrøder og maskiner bliver ved med at arbejde. CPU tid.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Hvor langt væk mobs og genstande trækkes, som en andel af normalen.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Springer over terræn skjult bag lukkede sektioner. Off koster meget.",
|
||||
"Hides entities behind opaque blocks.": "Skjuler enheder bag uigennemsigtige blokke.",
|
||||
"No entity occlusion culling.": "Ingen enhedsokklusion aflivning.",
|
||||
"CPU occlusion test runs this frame.": "CPU-okklusionstest kører denne ramme.",
|
||||
"Occlusion checks run on a worker thread.": "Okklusionstjek kører på en arbejdstråd.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Meshed sektioner uploadet pr. frame. Lavere er glattere under læsning.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Bløde skygger i hjørner. Det koster kun en del byggetid.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Mindre kopier af blokteksturer til fjernt terræn: mindre shimmer.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Blander græs og vandfarver på tværs af biomer, når du bygger bidder.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Skylag: slukket, fladt (hurtigt) eller 3D (fancy).",
|
||||
"How far clouds reach, in chunks.": "Hvor langt skyer når, i bidder.",
|
||||
"Draws the sky, sun, moon and stars.": "Tegner himmel, sol, måne og stjerner.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Afstandståge. Off viser kanten af den belastede verden.",
|
||||
"Draws rain and snow.": "Tegner regn og sne.",
|
||||
"How far rain and snow fall around you.": "Hvor langt regn og sne falder omkring dig.",
|
||||
"How many particles effects spawn.": "Hvor mange partikler effekter gyder.",
|
||||
"Round shadows under mobs and items.": "Runde skygger under mobs og genstande.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Blade med gennemsigtige mellemrum. Off tegner dem solidt.",
|
||||
"Sorts water and glass more exactly behind each other.": "Sorterer vand og glas mere præcist efter hinanden.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Hvilken chunk-genopbygning venter på rammen: ingen, i nærheden eller din.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animerer vand, lava, ild og andre bevægelige teksturer.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Verdenspixel pr. skærmpixel. Under 100% er hurtigere på GPU'en.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Udglatter takkede kanter. FXAA: blødere. TAA: de glatteste ting i bevægelse kan spore.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Gør verden mindre og genopbygger den med TAA. Hurtigere, blødere.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR-output på en HDR-skærm: lava, ild og sollys skinner kraftigere.",
|
||||
"How frames reach the screen while VSync is off.": "Hvordan rammer når skærmen, mens VSync er slukket.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Effektiv sover indtil næste billede; Præcise spins, som vanilje.",
|
||||
"Fills the screen (borderless).": "Fylder skærmen (uden kant).",
|
||||
"Brightens dark places.": "Lysner mørke steder.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Sænker billedhastigheden, når den minimeres, eller også inaktiv.",
|
||||
"Sways the view while walking.": "Svinger udsigten, mens du går.",
|
||||
"Fades newly loaded chunks in.": "Fader nyindlæste bidder ind.",
|
||||
"Darkens the screen's edges.": "Gør skærmens kanter mørkere.",
|
||||
"Shows when the world saves.": "Viser, når verden redder.",
|
||||
"Shows attack cooldown.": "Viser nedkøling af angrebet.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS cap (30 i menuer eller ufokuseret), halvdelen af arbejderne: mindre strøm.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Tråde, der genererer, lyser og mesh bidder. Næste start.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, derefter DirectX 12 (Windows) eller OpenGL (Linux), hvor det fejler. Næste start.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Vand og glas i én buffer: færre draw calls, lidt mere VRAM. Næste start.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Mesh-data sendt til GPU'en pr. frame. Lavere er glattere under læsning.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Billedhastighed og billedtider i løbet af spillet med en graf.",
|
||||
"horizon.multiplayer.finding_friend": "At finde din vens verden...",
|
||||
"horizon.multiplayer.not_signed_in": "Denne server er i onlinetilstand. Log ind med din Microsoft-konto i Horizon launcher for at deltage i den.",
|
||||
"horizon.multiplayer.invalid_address": "Ugyldig serveradresse: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Forbundet direkte til din ven...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Forbundet til din ven gennem relæet...",
|
||||
"General": "Generelt",
|
||||
"Advanced": "Avanceret",
|
||||
"Undo": "Fortryd",
|
||||
"Search options": "Søgemuligheder",
|
||||
"Shortcut": "Genvej",
|
||||
"Distance": "Afstand",
|
||||
"Sky and weather": "Himmel og vejr",
|
||||
"Lighting and entities": "Belysning og enheder",
|
||||
"Textures": "Teksturer",
|
||||
"Image": "Billede",
|
||||
"Culling": "Nedskæring",
|
||||
"Power": "Strøm",
|
||||
"Frames": "Rammer",
|
||||
"Overlay": "Overlejring",
|
||||
"Brightness": "Lysstyrke",
|
||||
"How far you see, the window and the HUD.": "Hvor langt du ser, vinduet og HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Himlen og vejret, belysning, teksturer og billedet.",
|
||||
"Chunk loading, culling and power use.": "Chunk loading, nedslagning og strømforbrug.",
|
||||
"How frames reach the screen, and the performance overlay.": "Hvordan rammer når frem til skærmen, og ydeevneoverlejringen.",
|
||||
"Brighter-than-white output on an HDR display.": "Lysere-end-hvidt output på en HDR-skærm.",
|
||||
"Results for \"%s\"": "Resultater for \"%s\"",
|
||||
"No settings match.": "Ingen indstillinger matcher.",
|
||||
"horizon.title.not_affiliated": "Ikke tilknyttet Mojang eller Microsoft",
|
||||
"horizon.extras.header.lighting": "Belysning",
|
||||
"horizon.extras.header.third_person": "Tredje person",
|
||||
"horizon.extras.header.particles": "Partikler",
|
||||
"horizon.extras.header.inventory": "Inventar",
|
||||
"horizon.extras.header.sound": "Lyd",
|
||||
"horizon.extras.header.menus": "Menuer",
|
||||
"horizon.extras.fullbright": "Fuldt lys",
|
||||
"horizon.extras.dynamic_lights": "Dynamiske lys",
|
||||
"horizon.extras.own_name_tag": "Eget navneskilt",
|
||||
"horizon.extras.hide_block_particles": "Skjul blokbrudspartikler",
|
||||
"horizon.extras.auto_reconnect": "Auto-genopret forbindelse",
|
||||
"horizon.extras.ui_sound_reserve": "Behold kanaler til UI-lyde",
|
||||
"horizon.extras.music_page": "Musik side",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks kontroller",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Rullehjul flytter elementer",
|
||||
"horizon.extras.sort": "Sorter knapper",
|
||||
"horizon.extras.sort_key": "Sorteringstast",
|
||||
"horizon.extras.no_ai": "Ingen AI",
|
||||
"horizon.extras.agent_control": "Agent kontrol",
|
||||
"horizon.extras.discord_presence": "Discord rig tilstedeværelse",
|
||||
"horizon.extras.chat_animation": "Glat chat",
|
||||
"horizon.extras.chat_indicator": "Meddelelsesindikator",
|
||||
"horizon.extras.wheel_cycles_settings": "Rullehjul ændrer indstillinger",
|
||||
"horizon.extras.pack_browser": "Ressourcepakkebrowser",
|
||||
"horizon.extras.fullbright.tooltip": "Vis blokke og enheder i fuldt lys, selv i mørke områder.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Fakler og andre lyskilder, der holdes, tabes eller brænder, tænder blokkene omkring dem.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Viser dit navn over dit hoved i tredje person, som andre spillere ser det. Skjult, når serveren skjuler dit teams navne, eller du er usynlig.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Skjuler affaldspartiklerne, der brister fra en knækket blok og smuldrer af den, du mine. Andre partikler bliver.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Tilslutter sig en server igen efter en mistet forbindelse eller en genstart, aldrig efter et kick eller ban.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Lyde i verden efterlader 16 af de 247 lydkanaler fri, så en strøm af dem (en stor eksplosion) kan ikke dæmpe menuklik.",
|
||||
"horizon.extras.music_page.tooltip": "Musik og lyde får en Musik...-side: afspil et hvilket som helst indbygget nummer nu, eller sluk et, så spillet stopper med at vælge det. Intet ændrer sig, før du slår et spor fra.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Højre-træk placerer én pr. plads, venstre-træk samler det tilbageholdte element, Shift + venstre-træk flytter hver plads.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Ned sender en genstand i åbningen til den anden side, op bringer en tilbage.",
|
||||
"horizon.extras.sort.tooltip": "Sorterer beholdningen, kister, tønder, ender-kister og shulker-kasser i kreativ fanerækkefølge, fletning af stakke.",
|
||||
"horizon.extras.sort_key.tooltip": "Sorterer delen under markøren. Klik, og tryk derefter på en tast; Escape frigør det.",
|
||||
"horizon.extras.no_ai.tooltip": "Skjuler og deaktiverer alle AI-funktioner: agentkontrol, horizon-ctl og MCP-serverne. Starterens kontakt også.",
|
||||
"horizon.extras.agent_control.tooltip": "Din egen AI-agent kan kun spille dette spil gennem horizon-ctl eller en MCP-klient på denne computer.",
|
||||
"horizon.extras.discord_presence.tooltip": "Viser Playing Horizon på din Discord-profil, mens Discord kører: menuer, single player eller multiplayer, dimensionen og spilletid. Aldrig en verdens navn eller en servers adresse.",
|
||||
"Calibrate...": "Kalibrer...",
|
||||
"Calibrate Anyway...": "Kalibrer alligevel...",
|
||||
"Use Automatic Setup": "Brug automatisk opsætning",
|
||||
"Shortcut: %s": "Genvej: %s",
|
||||
"Next": "Næste",
|
||||
"HDR Calibration %s/%s: %s": "HDR-kalibrering %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Indstiller de tre nedenfor i fuld skærm, et testbillede ad gangen.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "En tast, der åbner denne skærm fra spillet. Ikke bundet i starten.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Din skærm sagde ikke, hvor lys den bliver: Kalibrer for at konfigurere den.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Allerede konfigureret til din skærm. Kalibrer kun, hvis HDR ser forkert ud.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Sætter Peak Brightness og Paper White tilbage til din skærms opsætning.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Konfigurer din skærm. Lad være på Auto, medmindre det ser forkert ud.",
|
||||
"Lower it until the logo just disappears into the black.": "Sænk det, indtil logoet lige forsvinder ind i det sorte.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Hæv det, indtil logoet lige forsvinder ind i den lyse firkant.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Gør kortet hvidt, der er behageligt at se på i lang tid, i det lys du spiller i.",
|
||||
"horizon.splitscreen.profiles.forget": "Glem",
|
||||
"horizon.splitscreen.profiles.create": "Opret",
|
||||
"horizon.splitscreen.profiles.you": " - dig",
|
||||
"horizon.splitscreen.profiles.forget_named": "Glem %s",
|
||||
"horizon.splitscreen.profiles.previous": "Forrige",
|
||||
"horizon.splitscreen.profiles.next": "Næste",
|
||||
"horizon.splitscreen.profiles.new_button": "Ny profil...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Glem profil?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Det forlader denne instans liste.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Verdens fremskridt og indstillinger bevares.",
|
||||
"horizon.splitscreen.profiles.new_title": "Ny profil",
|
||||
"horizon.splitscreen.profiles.name_hint": "Navn (3 til 16 bogstaver, cifre, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Skift profil",
|
||||
"horizon.splitscreen.profiles.offline_note": "Offlineprofiler hører til denne instans",
|
||||
"horizon.splitscreen.profiles.move": "Flyt hvor du spiller",
|
||||
"horizon.splitscreen.profiles.move_place": "Flyt hvor du spiller: %s",
|
||||
"horizon.friends.title": "Spil med venner",
|
||||
"horizon.friends.hint": "Venner indsætter koden efter titelskærmens Venner-knap, i Direct Connection eller på launcherens Play-side.",
|
||||
"horizon.friends.copied": "Kopieret",
|
||||
"horizon.friends.copy_code": "Kopiér kode",
|
||||
"horizon.friends.stop_sharing": "Stop deling",
|
||||
"horizon.friends.none_yet": "Ingen venner her endnu.",
|
||||
"horizon.friends.here_now": "Her nu: %s",
|
||||
"horizon.friends.share_intro": "Del denne verden bag en invitationskode: den åbner til LAN, og venner på internettet deltager med koden. Ingen af vores servere er involveret.",
|
||||
"horizon.friends.start_sharing": "Begynd at dele",
|
||||
"horizon.friends.troubleshooting": "Fejlfinding...",
|
||||
"horizon.friends.stopped": "Stoppet med at dele invitationskoden.",
|
||||
"horizon.friends.short_code_changed": "Nogen blev ved med at prøve en forkert kort kode, så den ændrede sig.",
|
||||
"horizon.friends.wrong_code": "Nogen prøvede en forkert kode.",
|
||||
"horizon.friends.code_ready": "Din invitationskode er klar: Spilmenu, Spil med venner. Kopier det og send det til dine venner.",
|
||||
"horizon.friends.share_failed": "Kunne ikke dele verden: %s",
|
||||
"horizon.friends.finding_route": "Finder, hvordan venner kan nå dig...",
|
||||
"horizon.osk.shift": "Skift",
|
||||
"horizon.osk.space": "Plads",
|
||||
"horizon.osk.delete": "Slet",
|
||||
"horizon.osk.symbols": "Symboler",
|
||||
"horizon.osk.close": "Luk",
|
||||
"horizon.extras.chat_animation.tooltip": "En ny chatlinje glider ind fra venstre, mens den toner ind, i stedet for at virke hel.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Den farvede bjælke til venstre for en chatlinje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Scrollhjulet over en knap i indstillingerne ændrer dens værdi, som i vanilje. Fra, hjulet ruller kun listen.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs får en Tilføj knap, der søger mod Modrinth og installerer en pakke i din pakkemappe. Off, spillet kontakter aldrig Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "I en verden kan en anden controller deltage som en ny spiller: Tryk på A, når den spørger, vælg derefter en profil og hvor du vil spille.",
|
||||
"horizon.splitscreen.setup.monitor": "Overvåg %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "en skærm",
|
||||
"horizon.splitscreen.setup.window": "Vindue %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nyt vindue",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Udfyld %s",
|
||||
"horizon.splitscreen.setup.done": "Færdig",
|
||||
"horizon.splitscreen.setup.on": "På",
|
||||
"horizon.splitscreen.setup.off": "Fra",
|
||||
"horizon.splitscreen.setup.player": "Spiller %s",
|
||||
"horizon.splitscreen.setup.revert": "Vend tilbage",
|
||||
"horizon.splitscreen.setup.apply": "Ansøg",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Ingen spiller i vindue 1: det gemmer sig",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Ingen spiller i vinduet %s: det lukker ved Anvend",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Ingen spiller i vinduet %s: det åbner ikke",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Vindue %s fylder vinduet 1's skærm: dets spillere spiller i vindue 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Window %ss skærm er ikke tilsluttet: dens spillere spiller i Window 1",
|
||||
"horizon.splitscreen.setup.status.off": "Splitscreen er slået fra.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Tænd den, og tryk derefter på en knap på en anden controller i en verden.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Tryk på A for at deltage og vælge en profil.",
|
||||
"horizon.splitscreen.setup.status.idle": "Splitscreen inaktiv",
|
||||
"horizon.splitscreen.setup.status.alone": "Ingen andre lokale spillere.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Solostyringer og controllerfeedback er uændrede.",
|
||||
"horizon.splitscreen.setup.window_empty": "Vindue %s: tomt",
|
||||
"horizon.splitscreen.setup.note.select": "Klik på en afspiller, eller træk den til et vindue eller en skærm.",
|
||||
"horizon.splitscreen.setup.note.apply": "Ansøg om at spille på denne måde, Gå tilbage for at fortryde.",
|
||||
"horizon.splitscreen.setup.note.place": "Hvor skal %s spille?",
|
||||
"horizon.splitscreen.setup.this_player": "denne spiller",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen slukker, når de andre er gået (deres pausemenu).",
|
||||
"horizon.splitscreen.setup.window_place": "Vindue %s (%s af %s)",
|
||||
"horizon.splitscreen.setup.names": "Navne",
|
||||
"horizon.splitscreen.setup.identify": "Identificer",
|
||||
"horizon.splitscreen.setup.layout.two": "2 deler: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 deling: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Side om side",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Top og bund",
|
||||
"horizon.splitscreen.setup.layout.three.0": "En bred ovenpå",
|
||||
"horizon.splitscreen.setup.layout.three.1": "En høj til venstre",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Side om side",
|
||||
"horizon.splitscreen.setup.help.player": "Klik eller træk %s for at flytte dem.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (side om side, når vinduet er bredt). Tryk for at ændre.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Tryk for at ændre.",
|
||||
"horizon.splitscreen.setup.help.switch": "Splitscreen til denne computer, til eller fra.",
|
||||
"horizon.splitscreen.setup.help.identify": "Hver spillers visning viser, hvilket vindue den er i.",
|
||||
"horizon.splitscreen.setup.help.names": "Hver spillers navn i hjørnet af deres visning.",
|
||||
"horizon.splitscreen.setup.help.revert": "Fortryd de ændringer, der ikke er anvendt endnu.",
|
||||
"horizon.splitscreen.setup.help.apply": "Spil som kortet viser.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Intet ændret sig endnu.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Giv op Pad",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Controller tilsluttet",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Controller frakoblet",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Tilslut den igen, eller tryk på en knap på en anden",
|
||||
"horizon.splitscreen.prompt.join.one": "%s ønsker at deltage",
|
||||
"horizon.splitscreen.prompt.join.many": "%s controllere ønsker at deltage",
|
||||
"horizon.splitscreen.prompt.join.yes": "Deltag",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Spil som P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Nej",
|
||||
"horizon.splitscreen.prompt.lost": "%s: controller frakoblet",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Brug en anden",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Forlad",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Din controller holder op med at køre dig og bliver tilbudt at deltage (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s til %s: A slipper den her, B lægger den tilbage.",
|
||||
"horizon.splitscreen.setup.note.held": "%s plukket op: venstre/højre vælger hvor, A falder, B sætter den tilbage.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Gemmer modeller bag uigennemsigtige blokke. Asynkron checker off-thread; enheder krydser af.",
|
||||
"Block Entities": "Bloker enheder",
|
||||
"Shulker Boxes": "Shulker kasser",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Genbruger aflejrede kasser; lågets bevægelse forbliver animeret.",
|
||||
"Signs": "Tegn",
|
||||
"Reuses sign text until text, light, or font changes.": "Genbruger tegntekst, indtil tekst, lys eller skrifttype ændres.",
|
||||
"Decorated Pots": "Dekorerede gryder",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Genbruger stadig potter; bryde slingre og skårændringer tegnes om.",
|
||||
"Bells": "Klokker",
|
||||
"Reuses still bells; ringing stays animated.": "Genbruger stadig klokker; ringetonen forbliver animeret.",
|
||||
"Chests": "Kister",
|
||||
"Reuses closed chests; moving lids stay animated.": "Genbruger lukkede kister; bevægelige låg forbliver animerede.",
|
||||
"Banners": "Bannere",
|
||||
"Reuses poles; flags and pattern layers still update.": "Genbruger stænger; flag og mønsterlag opdateres stadig.",
|
||||
"Copper Golem Statues": "Kobber Golem statuer",
|
||||
"Reuses statue geometry until its block state changes.": "Genbruger statuegeometri, indtil dens bloktilstand ændres.",
|
||||
"Leaf Culling": "Nedskæring af blade",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Hvert blad ansigt. Vaniljes flotte blade.",
|
||||
"Distant Plant Dither": "Fjern Plante Dither",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Holder fine planter i højopløsningspakker fra at falme langt væk.",
|
||||
"Screenshot Quality": "Skærmbillede kvalitet",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2-skærmbilleder med så mange gange spillets opløsning. Højere tager længere tid.",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Viser en sammensat ramme mellem hver to rigtige: glattere, lidt mere forsinkelse.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Genbruger pakkeskyggekortet, når sol, udsigt og verden matcher. Fra som standard.",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "På servere finder deres BlueMap-webkort for fjernt terræn. Deltag næste gang.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Hvor langt omkring dig downloades BlueMap-fliser. Deltag næste gang.",
|
||||
"Synchronous": "Synkron",
|
||||
"Asynchronous": "Asynkron",
|
||||
"Solid Aggressive": "Solid aggressiv",
|
||||
"Hollow": "Hul",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Blade helt inde i en baldakin trækker fast: færre gennemsigtige lag.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Blade med en åben side trækker også fast: hurtigere, tykkere kanter.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Dråber ansigter mellem blade: hurtigere, men huller ser igennem.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Tidligere spilleautomat",
|
||||
"Hotbar: Next Slot": "Hotbar: Næste slot",
|
||||
"horizon.agent.connected.one": "Tilsluttet: 1 agentforbindelse",
|
||||
"horizon.agent.connected.many": "Forbundet: %s agentforbindelser",
|
||||
"horizon.agent.connected.one_ago": "Forbundet: 1 agentforbindelse, sidste kommando for %s s siden",
|
||||
"horizon.agent.connected.many_ago": "Forbundet: %s agentforbindelser, sidste kommando for %s s siden",
|
||||
"horizon.agent.waiting": "Venter på en agent.",
|
||||
"horizon.agent.off": "Fra: Agenter kan ikke nå dette spil.",
|
||||
"horizon.agent.session_opened": "Åbnet til denne session af programmet, der startede spillet.",
|
||||
"horizon.agent.socket": "Socket: %s (kopier detaljerne for dens fulde sti)",
|
||||
"horizon.agent.local_only": "Kun lokalt: agenter på denne computer, aldrig netværket.",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Gemmer modeller bag uigennemsigtige blokke. Enheder opdatering. Afsted med brugerdefinerede pakker.",
|
||||
"horizon.extras.repro_keys": "Genveje til optagelse",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R starter eller stopper en repro-optagelse, og F3+E skriver dens bundt. Fra som standard: en optagelse kopierer verden.",
|
||||
"horizon.agent.record_session": "Start optagelsessession",
|
||||
"horizon.agent.record_stop": "Stop optagelsessession",
|
||||
"horizon.agent.export_repro": "Eksporter Repro-pakke",
|
||||
"horizon.repro.started": "Optagelse startet; F3+R stopper det og skriver bundtet.",
|
||||
"horizon.repro.stopped": "Optagelse stoppet: %s",
|
||||
"horizon.repro.bundle": "Repro-pakke: %s",
|
||||
"horizon.repro.no_recording": "Der er endnu ingen optagelse at eksportere.",
|
||||
"horizon.repro.replay_started": "Genafspilning startet: %s",
|
||||
"horizon.pack.addInfo": "Tilføj en ressourcepakke fra Modrinth",
|
||||
"horizon.pack.addWorking": "Spørger Modrinth...",
|
||||
"horizon.pack.addNone": "Ingen ressourcepakker fundet",
|
||||
"horizon.pack.addTitle": "Tilføj en ressourcepakke",
|
||||
"horizon.pack.install": "Installer",
|
||||
"horizon.pack.installing": "Installerer",
|
||||
"horizon.pack.installed": "Installeret",
|
||||
"horizon.pack.search": "Søg",
|
||||
"horizon.pack.searchHint": "Søg i Modrinth...",
|
||||
"horizon.pack.filters": "Filtre",
|
||||
"horizon.pack.filter.realistic": "Realistisk",
|
||||
"horizon.pack.filter.simplistic": "Forsimplet",
|
||||
"horizon.pack.filter.vanillaLike": "Vanilje-agtig",
|
||||
"horizon.pack.addFailed": "Pakken kunne ikke tilføjes: %s",
|
||||
"horizon.extras.header.cosmetics": "Kosmetik",
|
||||
"horizon.extras.cosmetics_mine": "Min kosmetik",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skjult: alle Horizon-spillere ser dig uden din kosmetik, inklusive dig. Den samme kontakt som løfterakettens garderobe.",
|
||||
"horizon.extras.cosmetics_others": "Andre spilleres kosmetik",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Hvad andre Horizon-spillere bærer: alt, kun deres navnemærke eller intet. Skjult spørger spillet aldrig, hvad de har på.",
|
||||
"horizon.extras.cosmetics_auras": "Andres auraer og spor",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auraer og spor, som andre spillere bærer. Fra som standard.",
|
||||
"horizon.extras.cosmetics.shown": "Vist",
|
||||
"horizon.extras.cosmetics.hidden": "Skjult",
|
||||
"horizon.extras.cosmetics.name_tags": "Kun navneskilte"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"horizon.options.controller": "Controller",
|
||||
"horizon.options.three_d_skin_layers": "3D-Skin-Schichten",
|
||||
"horizon.options.vibration": "Vibration",
|
||||
"horizon.options.adaptive_triggers": "Adaptive Trigger",
|
||||
"horizon.options.lightbar": "Lightbar",
|
||||
@@ -332,6 +333,10 @@
|
||||
"horizon.pages.split_settings": "Splitscreen-Einstellungen",
|
||||
"horizon.pages.split_setup": "Splitscreen",
|
||||
"horizon.pages.split_setup.tooltip": "Spieler an diesem Computer: Controller können deiner Welt beitreten.",
|
||||
"Particle Culling": "Partikel-Culling",
|
||||
"Draws every particle.": "Zeichnet alle Partikel.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.": "Überspringt Partikel außerhalb des Bildes, unterhalb eines Pixels oder hinter Terrain.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.": "Überspringt Partikel außerhalb des Bildes, unterhalb eines Pixels oder hinter Terrain und entlastet den Render-Thread.",
|
||||
"horizon.extras.header.cosmetics": "Kosmetik",
|
||||
"horizon.extras.cosmetics_mine": "Meine Kosmetik",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ausgeblendet: Alle Horizon-Spieler sehen dich ohne deine Kosmetik, du selbst auch. Derselbe Schalter wie in der Garderobe des Launchers.",
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
{
|
||||
"horizon.options.controller": "Ελεγκτής",
|
||||
"horizon.options.vibration": "Δόνηση",
|
||||
"horizon.options.adaptive_triggers": "Προσαρμοστικοί ενεργοποιητές",
|
||||
"horizon.options.look_sensitivity": "Ευαισθησία κάμερας",
|
||||
"horizon.options.invert_x": "Αντιστροφή κάμερας X",
|
||||
"horizon.options.invert_y": "Αντιστροφή κάμερας Y",
|
||||
"horizon.options.sneak_button": "Κουμπί σκυψίματος",
|
||||
"horizon.options.move_deadzone": "Νεκρή ζώνη κίνησης",
|
||||
"horizon.options.look_deadzone": "Νεκρή ζώνη κάμερας",
|
||||
"horizon.options.button_glyphs": "Γλυφικά κουμπιών",
|
||||
"horizon.options.menu_navigation": "Πλοήγηση μενού",
|
||||
"horizon.options.on_screen_keyboard": "Πληκτρολόγιο οθόνης",
|
||||
"horizon.options.chat_delay": "Καθυστέρηση συνομιλίας",
|
||||
"horizon.options.lightbar.health": "Υγεία",
|
||||
"horizon.options.lightbar.dimension": "Διάσταση",
|
||||
"horizon.options.glyphs.auto": "Αυτόματο",
|
||||
"horizon.options.menu_focus": "Εστίαση",
|
||||
"horizon.options.menu_pointer": "Δείκτης",
|
||||
"horizon.options.keyboard.auto": "Αυτόματο",
|
||||
"horizon.options.unavailable": "Αυτή η δυνατότητα δεν είναι ακόμη διαθέσιμη στο Horizon.",
|
||||
"horizon.options.search": "Ρυθμίσεις αναζήτησης...",
|
||||
"horizon.pages.shader_packs": "Πακέτα Shader",
|
||||
"horizon.pages.shader_options": "Επιλογές Shader",
|
||||
"horizon.pages.controller": "Ρυθμίσεις ελεγκτή",
|
||||
"horizon.pages.controller_binds": "Δεσμοί ελεγκτή",
|
||||
"horizon.pages.settings": "Ρυθμίσεις Horizon",
|
||||
"horizon.pages.agent": "Πράκτορας AI",
|
||||
"horizon.split.leave": "Φύγε",
|
||||
"horizon.pages.split_settings": "Ρυθμίσεις Splitscreen",
|
||||
"horizon.pages.split_profiles": "Ποιος παίζει;",
|
||||
"horizon.pages.split_setup": "Ρύθμιση Splitscreen",
|
||||
"horizon.pages.music": "Μουσική",
|
||||
"horizon.pages.music.menu": "Μενού",
|
||||
"horizon.pages.music.overworld": "Υπερκόσμος",
|
||||
"horizon.pages.music.creative": "Δημιουργικό",
|
||||
"horizon.pages.music.underwater": "Υποβρύχια",
|
||||
"horizon.pages.music.end": "Το Τέλος",
|
||||
"horizon.pages.music.boss": "Αφεντικό",
|
||||
"horizon.pages.music.credits": "Πιστώσεις",
|
||||
"horizon.pages.music.overworld_biomes": "Υπερκόσμια βιώματα",
|
||||
"horizon.pages.music.nether_biomes": "Ούτε βιώματα",
|
||||
"horizon.pages.music.end_biomes": "Τελικοί βιομοί",
|
||||
"horizon.pages.music.other": "Άλλη μουσική",
|
||||
"horizon.pages.music.empty": "Χωρίς μουσικά κομμάτια: οι ήχοι του παιχνιδιού δεν φορτώνονται.",
|
||||
"horizon.pages.music.tooltip": "Τα ενσωματωμένα κομμάτια, ανά περίπτωση: παίξτε ένα τώρα ή απενεργοποιήστε οποιοδήποτε, ώστε το παιχνίδι να σταματήσει να το επιλέγει.",
|
||||
"horizon.pages.settings.tooltip": "Οι ρυθμίσεις του Horizon: γραφικά, χειριστήρια, πρόσθετα και mods.",
|
||||
"horizon.pages.graphics.tooltip": "Γραφικά, απλά: κάθε ρύθμιση λέει τι κοστίζει.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Κάθε ρύθμιση γραφικών, συμπεριλαμβανομένου του Horizon, με το κόστος του καθενός.",
|
||||
"horizon.pages.controller.tooltip": "Χειριστήρια: τα κουμπιά, οι μοχλοί, οι κραδασμοί και η πλοήγηση στο μενού τους.",
|
||||
"horizon.pages.extras.tooltip": "Προαιρετικές προσθήκες, όλες απενεργοποιημένες από προεπιλογή: το παιχνίδι παραμένει βανίλια μέχρι να το ενεργοποιήσετε.",
|
||||
"horizon.pages.agent.tooltip": "Αφήστε τον δικό σας πράκτορα AI να παίξει: horizon-ctl και MCP πελάτες σε αυτόν τον υπολογιστή.",
|
||||
"horizon.pages.split_profiles.tooltip": "Ποιος παίζει: ένα προφίλ που διατηρείται σε αυτόν τον φάκελο παιχνιδιού.",
|
||||
"horizon.pages.split_setup.tooltip": "Τα παράθυρα και οι οθόνες και ποιος παίζει πού.",
|
||||
"horizon.pages.shader_packs.tooltip": "Πακέτα Shader από το φάκελο shaderpacks: Τα δικά του Horizon και αυτά σε μορφή shaders/. Τρέχει ένας κάθε φορά.",
|
||||
"horizon.pages.mods.tooltip": "Mods που δημιουργήθηκαν για το Horizon, από το φάκελο horizon-mods. Κάθε ένα τρέχει σε ένα sandbox και παραμένει απενεργοποιημένο μέχρι να το ενεργοποιήσετε.",
|
||||
"horizon.pages.open.graphics": "Γραφικά...",
|
||||
"horizon.pages.open.graphics_advanced": "Προηγμένα γραφικά...",
|
||||
"horizon.pages.open.shader_packs": "Πακέτα Shader...",
|
||||
"horizon.pages.open.controller": "Ρυθμίσεις ελεγκτή...",
|
||||
"horizon.pages.open.split_setup": "Διαίρεση οθόνης...",
|
||||
"horizon.pages.open.extras": "Έξτρα...",
|
||||
"horizon.pages.open.agent": "Πράκτορας AI...",
|
||||
"horizon.pages.open.controller_binds": "Ο ελεγκτής δεσμεύει...",
|
||||
"horizon.pages.open.horizon_settings": "Ρυθμίσεις Horizon...",
|
||||
"horizon.pages.open.music": "Μουσική...",
|
||||
"horizon.mods.empty": "Χωρίς mods: τοποθετήστε τα αρχεία .hmod στο φάκελο horizon-mods",
|
||||
"horizon.mods.stopped": " (Σταμάτησε)",
|
||||
"horizon.mods.not_built": " (Δεν έχει κατασκευαστεί)",
|
||||
"horizon.mods.settings": "Ρυθμίσεις",
|
||||
"horizon.mods.in_world": " (άσε τον κόσμο να αλλάξει)",
|
||||
"horizon.mods.open_folder": "Ανοίξτε το φάκελο Mod",
|
||||
"horizon.shader_packs.empty": "Χωρίς πακέτα shader: τοποθετήστε τα στο φάκελο shaderpacks",
|
||||
"horizon.shader_options.open": "Επιλογές Shader...",
|
||||
"horizon.shader_options.profile": "Προφίλ",
|
||||
"horizon.shader_options.loading": "Διαβάζοντας τις επιλογές του πακέτου shader...",
|
||||
"horizon.agent.copy_details": "Αντιγραφή στοιχείων σύνδεσης",
|
||||
"horizon.multiplayer.join": "Εγγραφείτε",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Ακύρωση επανασύνδεσης",
|
||||
"horizon.multiplayer.join_friend": "Γίνετε μέλος ενός φίλου",
|
||||
"horizon.multiplayer.invite_code": "Κωδικός πρόσκλησης",
|
||||
"horizon.multiplayer.relay": "Ρελέ (προαιρετικό)",
|
||||
"Preset": "Προκαθορισμένη",
|
||||
"LOD Distance": "Απόσταση LOD",
|
||||
"Far Detail": "Μακρινή λεπτομέρεια",
|
||||
"Entity Culling": "Σφαγή οντοτήτων",
|
||||
"Chunk Updates": "Ενημερώσεις κομματιών",
|
||||
"Upload Budget": "Μεταφόρτωση προϋπολογισμού",
|
||||
"Sky": "Ουρανός",
|
||||
"Fog": "Ομίχλη",
|
||||
"Weather": "Καιρός",
|
||||
"Animated Textures": "Κινούμενες υφές",
|
||||
"Present Mode": "Παρούσα λειτουργία",
|
||||
"Frame Pacing": "Βηματοδότηση καρέ",
|
||||
"Worker Threads": "Εργατικά νήματα",
|
||||
"Resolution Scale": "Κλίμακα Ανάλυσης",
|
||||
"Upscaling": "Αναβάθμιση",
|
||||
"Paper White": "Λευκό χαρτί",
|
||||
"Peak Brightness": "Μέγιστη φωτεινότητα",
|
||||
"Black Level": "Μαύρο Επίπεδο",
|
||||
"UI Brightness": "Φωτεινότητα διεπαφής χρήστη",
|
||||
"Battery Mode": "Λειτουργία μπαταρίας",
|
||||
"Graphics API": "API γραφικών",
|
||||
"Translucent Batching": "Ημιδιαφανές παρτίδες",
|
||||
"Performance Overlay": "Επικάλυψη απόδοσης",
|
||||
"Full Detail Distance": "Απόσταση πλήρους λεπτομέρειας",
|
||||
"On": "Ενεργό",
|
||||
"Off": "Απενεργοποίηση",
|
||||
"Unlimited": "Απεριόριστο",
|
||||
"Auto": "Αυτόματο",
|
||||
"Custom": "Προσαρμοσμένο",
|
||||
"Performance": "Απόδοση",
|
||||
"Balanced": "Ισορροπημένο",
|
||||
"Quality": "Ποιότητα",
|
||||
"Native": "Εγγενής",
|
||||
"Full": "Γεμάτη",
|
||||
"Reduced": "Μειώθηκε",
|
||||
"Efficient": "Αποτελεσματική",
|
||||
"Precise": "Ακριβές",
|
||||
"Auto (VSync)": "Αυτόματο (VSync)",
|
||||
"Mailbox": "Γραμματοκιβώτιο",
|
||||
"Immediate": "Άμεση",
|
||||
"FPS and Graph": "FPS και Γράφημα",
|
||||
"TAA": "ΤΑΑ",
|
||||
"No cost": "Χωρίς κόστος",
|
||||
"Low cost": "Χαμηλό κόστος",
|
||||
"Medium cost": "Μεσαίου κόστους",
|
||||
"High cost": "Υψηλό κόστος",
|
||||
"Saves time": "Εξοικονομεί χρόνο",
|
||||
"Graphics": "Γραφικά",
|
||||
"Done": "Έγινε",
|
||||
"This feature is not yet available in Horizon.": "Αυτή η δυνατότητα δεν είναι ακόμη διαθέσιμη στο Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Η οθόνη ή το σύστημά σας δεν προσφέρει έξοδο HDR.",
|
||||
"Turn HDR on to use it.": "Ενεργοποιήστε το HDR για να το χρησιμοποιήσετε.",
|
||||
"Your GPU or graphics API doesn't support it.": "Η GPU ή το API γραφικών σας δεν το υποστηρίζουν.",
|
||||
"%s chunks": "%s κομμάτια",
|
||||
"%s per frame": "%s ανά πλαίσιο",
|
||||
"%s MiB/frame": "%s MiB/πλαίσιο",
|
||||
"%s KiB/frame": "%s KiB/πλαίσιο",
|
||||
"Auto (%s)": "Αυτόματο (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Εύρος εδάφους με πλήρη λεπτομέρεια. Η απόσταση LOD μπορεί να επεκτείνει τους τοπικούς κόσμους μακρύτερα.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Χοντρό μακρινό έδαφος σε τοπικούς κόσμους. Οι ενσωματωμένες GPU σταματούν στα 128 κομμάτια.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Μειωμένες έλξεις πολύ πιο χοντρό έδαφος: πιο γρήγορα σε αυτήν την ενσωματωμένη GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Οι μειωμένες έλξεις πολύ πιο τραχύ εδάφους, για τις GPU επιβραδύνουν.",
|
||||
"Field of view. Wider shows more terrain at once.": "Οπτικό πεδίο. Το ευρύτερο δείχνει περισσότερο έδαφος ταυτόχρονα.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Καπάκια καρέ ανά δευτερόλεπτο. ένα καπάκι εξοικονομεί ενέργεια και θερμότητα.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Περιμένει για την ανανέωση της οθόνης: χωρίς σκίσιμο, λίγο λανθάνουσα κατάσταση.",
|
||||
"Size of menus and the HUD.": "Το μέγεθος των μενού και το HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Ρυθμίζει τις παρακάτω επιλογές μαζί. Η αλλαγή ενός το καθιστά Προσαρμοσμένο.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Η λάβα, η φωτιά και το φως του ήλιου λάμπουν πιο φωτεινά από το λευκό. Χρειάζεται οθόνη HDR.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Η οθόνη σας είναι το πιο σκούρο μαύρο. 0 για OLED? Οι LCD λάμπουν λίγο.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Πόσο φωτεινά σημεία αποκτούν. Auto: η κορύφωση που αναφέρει το σύστημά σας.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Πόσο φωτεινό φαίνεται το λευκό χαρτί και τα μενού. Αυτόματο: η επιφάνεια εργασίας σας είναι λευκή.",
|
||||
"How bright the HUD and menus are, against paper white.": "Πόσο φωτεινά είναι τα HUD και τα μενού, σε σχέση με το λευκό χαρτί.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Πόσο μακριά συνεχίζουν να λειτουργούν όχλοι, καλλιέργειες και μηχανές. Χρόνος CPU.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Πόσο μακριά κληρώνονται όχλοι και αντικείμενα, ως ποσοστό του κανονικού.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Παρακάμπτει το έδαφος που κρύβεται πίσω από κλειστά τμήματα. Το off κοστίζει πολύ.",
|
||||
"Hides entities behind opaque blocks.": "Κρύβει οντότητες πίσω από αδιαφανή μπλοκ.",
|
||||
"No entity occlusion culling.": "Δεν υπάρχει σφαγή απόφραξης οντοτήτων.",
|
||||
"CPU occlusion test runs this frame.": "Η δοκιμή απόφραξης CPU εκτελεί αυτό το πλαίσιο.",
|
||||
"Occlusion checks run on a worker thread.": "Οι έλεγχοι απόφραξης εκτελούνται σε ένα νήμα εργασίας.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Διχτυωτές τομές μεταφορτώθηκαν ανά πλαίσιο. Το Lower είναι πιο ομαλό κατά τη φόρτωση.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Απαλές σκιές στις γωνίες. Κόστος μόνο τμηματικός χρόνος κατασκευής.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Μικρότερα αντίγραφα υφής μπλοκ για μακρινό έδαφος: λιγότερη λάμψη.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Αναμειγνύει γρασίδι και χρώματα νερού στα biomes κατά την κατασκευή κομματιών.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Στρώμα σύννεφου: απενεργοποιημένο, επίπεδο (γρήγορο) ή 3D (φανταχτερό).",
|
||||
"How far clouds reach, in chunks.": "Πόσο μακριά φτάνουν τα σύννεφα, σε κομμάτια.",
|
||||
"Draws the sky, sun, moon and stars.": "Ζωγραφίζει τον ουρανό, τον ήλιο, το φεγγάρι και τα αστέρια.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Ομίχλη από απόσταση. Το Off δείχνει την άκρη του φορτωμένου κόσμου.",
|
||||
"Draws rain and snow.": "Ζωγραφίζει βροχή και χιόνι.",
|
||||
"How far rain and snow fall around you.": "Πόσο μακριά βρέχει και χιόνι πέφτει γύρω σας.",
|
||||
"How many particles effects spawn.": "Πόσα αποτελέσματα σωματιδίων γεννιούνται.",
|
||||
"Round shadows under mobs and items.": "Στρογγυλές σκιές κάτω από όχλους και αντικείμενα.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Φύλλα με διαφανή κενά. Off τους τραβάει στερεά.",
|
||||
"Sorts water and glass more exactly behind each other.": "Ταξινομεί το νερό και το ποτήρι με μεγαλύτερη ακρίβεια το ένα πίσω από το άλλο.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Ποιο κομμάτι ανακατασκευάζεται περιμένει το πλαίσιο: κανένα, κοντά ή δικό σας.",
|
||||
"Animates water, lava, fire and other moving textures.": "Ζωντανεύει νερό, λάβα, φωτιά και άλλες κινούμενες υφές.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Παγκόσμια εικονοστοιχεία ανά εικονοστοιχείο οθόνης. Κάτω από το 100% είναι πιο γρήγορο στη GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Λειαίνει τις οδοντωτές άκρες. FXAA: πιο μαλακό. ΤΑΑ: τα πιο ομαλά, κινούμενα πράγματα μπορεί να ακολουθούν.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Καθιστά τον κόσμο μικρότερο και τον ξαναχτίζει με ΤΑΑ. Πιο γρήγορα, πιο μαλακά.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "Έξοδος HDR σε οθόνη HDR: η λάβα, η φωτιά και το φως του ήλιου λάμπουν πιο έντονα.",
|
||||
"How frames reach the screen while VSync is off.": "Πώς τα καρέ φτάνουν στην οθόνη ενώ το VSync είναι απενεργοποιημένο.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Αποτελεσματικοί ύπνοι μέχρι το επόμενο καρέ. Ακριβές περιστροφές, όπως η βανίλια.",
|
||||
"Fills the screen (borderless).": "Γεμίζει την οθόνη (χωρίς περιθώρια).",
|
||||
"Brightens dark places.": "Φωτίζει τα σκοτεινά μέρη.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Μειώνει τον ρυθμό καρέ όταν είναι ελαχιστοποιημένος ή και σε αδράνεια.",
|
||||
"Sways the view while walking.": "Κουνάει τη θέα ενώ περπατάτε.",
|
||||
"Fades newly loaded chunks in.": "Ξεθωριάζει τα πρόσφατα φορτωμένα κομμάτια.",
|
||||
"Darkens the screen's edges.": "Σκουραίνει τα άκρα της οθόνης.",
|
||||
"Shows when the world saves.": "Δείχνει όταν ο κόσμος σώζει.",
|
||||
"Shows attack cooldown.": "Εμφανίζει ψύξη επίθεσης.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "Καπάκι 60 FPS (30 σε μενού ή χωρίς εστίαση), οι μισοί εργαζόμενοι: λιγότερη ισχύς.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Κλωστές που δημιουργούν, φωτίζουν και πλένουν κομμάτια. Επόμενη αρχή.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan και μετά DirectX 12 (Windows) ή OpenGL (Linux) όπου αποτυγχάνει. Επόμενη αρχή.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Νερό και ποτήρι σε ένα buffer: λιγότερες κλήσεις κλήσεων, λίγο περισσότερη VRAM. Επόμενη αρχή.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Τα δεδομένα πλέγματος αποστέλλονται στη GPU ανά πλαίσιο. Το Lower είναι πιο ομαλό κατά τη φόρτωση.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Ρυθμός καρέ και χρόνοι καρέ κατά τη διάρκεια του παιχνιδιού, με ένα γράφημα.",
|
||||
"horizon.multiplayer.finding_friend": "Βρες τον κόσμο του φίλου σου...",
|
||||
"horizon.multiplayer.not_signed_in": "Αυτός ο διακομιστής είναι σε λειτουργία online. Συνδεθείτε με τον λογαριασμό σας Microsoft στο πρόγραμμα εκκίνησης Horizon για να εγγραφείτε.",
|
||||
"horizon.multiplayer.invalid_address": "Μη έγκυρη διεύθυνση διακομιστή: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Συνδέθηκε απευθείας με τον φίλο σας...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Συνδέθηκε με τον φίλο σας μέσω του ρελέ...",
|
||||
"General": "Στρατηγός",
|
||||
"Advanced": "Προχωρημένο",
|
||||
"Undo": "Αναίρεση",
|
||||
"Search options": "Επιλογές αναζήτησης",
|
||||
"Shortcut": "Συντόμευση",
|
||||
"Distance": "Απόσταση",
|
||||
"Display": "Εμφάνιση",
|
||||
"Interface": "Διασύνδεση",
|
||||
"Sky and weather": "Ουρανός και καιρός",
|
||||
"Lighting and entities": "Φωτισμός και οντότητες",
|
||||
"Textures": "Υφές",
|
||||
"Image": "Εικόνα",
|
||||
"Chunk loading": "Φόρτωση κομματιού",
|
||||
"Culling": "Σφαγή",
|
||||
"Power": "Δύναμη",
|
||||
"Frames": "Κορνίζες",
|
||||
"Overlay": "Επικάλυψη",
|
||||
"Output": "Έξοδος",
|
||||
"Brightness": "Φωτεινότητα",
|
||||
"How far you see, the window and the HUD.": "Πόσο μακριά βλέπετε, το παράθυρο και το HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Ο ουρανός και ο καιρός, ο φωτισμός, οι υφές και η εικόνα.",
|
||||
"Chunk loading, culling and power use.": "Φόρτωση κομματιών, σφαγή και χρήση ενέργειας.",
|
||||
"How frames reach the screen, and the performance overlay.": "Πώς φτάνουν τα καρέ στην οθόνη και η επικάλυψη απόδοσης.",
|
||||
"Brighter-than-white output on an HDR display.": "Έξοδος φωτεινότερη από το λευκό σε οθόνη HDR.",
|
||||
"Results for \"%s\"": "Αποτελέσματα για \"%s\"",
|
||||
"No settings match.": "Δεν ταιριάζουν ρυθμίσεις.",
|
||||
"horizon.title.not_affiliated": "Δεν συνδέεται με τη Mojang ή τη Microsoft",
|
||||
"horizon.extras.header.lighting": "Φωτισμός",
|
||||
"horizon.extras.header.third_person": "Τρίτο πρόσωπο",
|
||||
"horizon.extras.header.particles": "Σωματίδια",
|
||||
"horizon.extras.header.inventory": "Απογραφή",
|
||||
"horizon.extras.header.chat": "Συνομιλία",
|
||||
"horizon.extras.header.sound": "Ήχος",
|
||||
"horizon.extras.header.menus": "Μενού",
|
||||
"horizon.extras.dynamic_lights": "Δυναμικά φώτα",
|
||||
"horizon.extras.own_name_tag": "Δική ετικέτα ονόματος",
|
||||
"horizon.extras.hide_block_particles": "Απόκρυψη σωματιδίων θραύσης μπλοκ",
|
||||
"horizon.extras.auto_reconnect": "Αυτόματη επανασύνδεση",
|
||||
"horizon.extras.ui_sound_reserve": "Διατηρήστε τα κανάλια για ήχους διεπαφής χρήστη",
|
||||
"horizon.extras.music_page": "Σελίδα μουσικής",
|
||||
"horizon.extras.mouse_tweaks": "Στοιχεία ελέγχου Mouse Tweaks",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Ο τροχός κύλισης μετακινεί στοιχεία",
|
||||
"horizon.extras.sort": "Κουμπιά ταξινόμησης",
|
||||
"horizon.extras.sort_key": "Πλήκτρο ταξινόμησης",
|
||||
"horizon.extras.no_ai": "Όχι AI",
|
||||
"horizon.extras.agent_control": "Έλεγχος πράκτορα",
|
||||
"horizon.extras.discord_presence": "Discord Πλούσια Παρουσία",
|
||||
"horizon.extras.chat_animation": "Ομαλή συνομιλία",
|
||||
"horizon.extras.chat_indicator": "Ένδειξη μηνύματος",
|
||||
"horizon.extras.wheel_cycles_settings": "Ο τροχός κύλισης αλλάζει τις ρυθμίσεις",
|
||||
"horizon.extras.pack_browser": "Πρόγραμμα περιήγησης πακέτου πόρων",
|
||||
"horizon.extras.fullbright.tooltip": "Εμφάνιση μπλοκ και οντοτήτων σε πλήρη φωτισμό, ακόμη και σε σκοτεινές περιοχές.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Πυρσοί και άλλες πηγές φωτός που συγκρατούνται, πέφτουν ή καίγονται ανάβουν τα μπλοκ γύρω τους.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Δείχνει το όνομά σας πάνω από το κεφάλι σας σε τρίτο πρόσωπο, όπως το βλέπουν άλλοι παίκτες. Κρύβεται όταν ο διακομιστής κρύβει τα ονόματα της ομάδας σας ή όταν είστε αόρατοι.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Κρύβει τα σωματίδια των συντριμμιών που σκάνε από ένα σπασμένο μπλοκ και θρυμματίζονται από αυτό που εξορύσσετε. Άλλα σωματίδια παραμένουν.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Επανέρχεται σε έναν διακομιστή μετά από απώλεια σύνδεσης ή επανεκκίνηση, ποτέ μετά από κλωτσιά ή αποκλεισμό.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Οι ήχοι στον κόσμο αφήνουν ελεύθερα 16 από τα 247 κανάλια ήχου, έτσι μια πλημμύρα από αυτά (μια μεγάλη έκρηξη) δεν μπορεί να σιγήσει τα κλικ του μενού.",
|
||||
"horizon.extras.music_page.tooltip": "Το Music & Sounds αποκτά μια σελίδα Μουσική...: παίξτε οποιοδήποτε ενσωματωμένο κομμάτι τώρα ή απενεργοποιήστε ένα, ώστε το παιχνίδι να σταματήσει να το επιλέγει. Τίποτα δεν αλλάζει μέχρι να απενεργοποιήσετε ένα κομμάτι.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Δεξιά σύρετε ένα ανά υποδοχή, σύρετε αριστερά συγκεντρώνετε το στοιχείο που κρατάτε, Shift + σύρετε αριστερά μετακινείτε κάθε υποδοχή.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Το Down στέλνει ένα στοιχείο της υποδοχής στην άλλη πλευρά, το επάνω φέρνει ένα πίσω.",
|
||||
"horizon.extras.sort.tooltip": "Ταξινομεί το απόθεμα, τα κιβώτια, τα βαρέλια, τα κιβώτια τελικής επεξεργασίας και τα κουτιά shulker κατά σειρά δημιουργικών καρτελών, συγχωνεύοντας στοίβες.",
|
||||
"horizon.extras.sort_key.tooltip": "Ταξινομεί το τμήμα κάτω από τον κέρσορα. Κάντε κλικ και μετά πατήστε ένα πλήκτρο. Η απόδραση το λύνει.",
|
||||
"horizon.extras.no_ai.tooltip": "Αποκρύπτει και απενεργοποιεί κάθε δυνατότητα AI: έλεγχος πράκτορα, horizon-ctl και διακομιστές MCP. Ο διακόπτης του εκτοξευτή επίσης.",
|
||||
"horizon.extras.agent_control.tooltip": "Ο δικός σας πράκτορας AI μπορεί να παίξει αυτό το παιχνίδι μέσω του horizon-ctl ή ενός προγράμματος-πελάτη MCP, μόνο σε αυτόν τον υπολογιστή.",
|
||||
"horizon.extras.discord_presence.tooltip": "Εμφανίζει το Playing Horizon στο προφίλ σας Discord ενώ εκτελείται το Discord: μενού, για έναν παίκτη ή για πολλούς παίκτες, τη διάσταση και την ώρα αναπαραγωγής. Ποτέ το όνομα ενός κόσμου ή η διεύθυνση ενός διακομιστή.",
|
||||
"Calibrate...": "Βαθμονόμηση...",
|
||||
"Calibrate Anyway...": "Βαθμονόμηση πάντως...",
|
||||
"Use Automatic Setup": "Χρησιμοποιήστε την Αυτόματη Ρύθμιση",
|
||||
"Shortcut: %s": "Συντόμευση: %s",
|
||||
"Next": "Επόμενο",
|
||||
"HDR Calibration %s/%s: %s": "Βαθμονόμηση HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Ορίζει τις τρεις παρακάτω πλήρη οθόνη, μία δοκιμαστική εικόνα τη φορά.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Ένα κλειδί που ανοίγει αυτήν την οθόνη από το παιχνίδι. Δεν δεσμεύεται στην αρχή.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Η οθόνη σας δεν έλεγε πόσο φωτεινή γίνεται: βαθμονομήστε για να τη ρυθμίσετε.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Έχει ήδη ρυθμιστεί για την οθόνη σας. Βαθμονόμηση μόνο εάν το HDR φαίνεται λάθος.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Επαναφέρει το Peak Brightness και το Paper White στη ρύθμιση της οθόνης σας.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Ρύθμιση για την οθόνη σας. Αφήστε το \"Αυτόματο\" εκτός εάν φαίνεται λάθος.",
|
||||
"Lower it until the logo just disappears into the black.": "Χαμηλώστε το μέχρι να εξαφανιστεί το λογότυπο στο μαύρο.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Σηκώστε το μέχρι να εξαφανιστεί το λογότυπο στο φωτεινό τετράγωνο.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Κάντε την κάρτα λευκή που είναι άνετη για να την κοιτάτε για πολλή ώρα, στο φως που παίζετε.",
|
||||
"horizon.splitscreen.profiles.forget": "Ξεχάστε",
|
||||
"horizon.splitscreen.profiles.create": "Δημιουργία",
|
||||
"horizon.splitscreen.profiles.you": " - εσύ",
|
||||
"horizon.splitscreen.profiles.forget_named": "Ξεχάστε %s",
|
||||
"horizon.splitscreen.profiles.previous": "Προηγούμενο",
|
||||
"horizon.splitscreen.profiles.next": "Επόμενο",
|
||||
"horizon.splitscreen.profiles.new_button": "Νέο Προφίλ...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Ξεχάστε το προφίλ;",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Φεύγει από τη λίστα αυτής της περίπτωσης.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Η παγκόσμια πρόοδος και οι ρυθμίσεις διατηρούνται.",
|
||||
"horizon.splitscreen.profiles.new_title": "Νέο Προφίλ",
|
||||
"horizon.splitscreen.profiles.name_hint": "Όνομα (3 έως 16 γράμματα, ψηφία, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Αλλαγή προφίλ",
|
||||
"horizon.splitscreen.profiles.offline_note": "Τα προφίλ εκτός σύνδεσης ανήκουν σε αυτήν την περίπτωση",
|
||||
"horizon.splitscreen.profiles.move": "Μετακινηθείτε όπου παίζετε",
|
||||
"horizon.splitscreen.profiles.move_place": "Μετακινηθείτε όπου παίζετε: %s",
|
||||
"horizon.friends.title": "Παίξτε με φίλους",
|
||||
"horizon.friends.hint": "Οι φίλοι επικολλούν τον κώδικα μετά το κουμπί Φίλοι της οθόνης τίτλου, στην Άμεση σύνδεση ή στη σελίδα Αναπαραγωγή του προγράμματος εκκίνησης.",
|
||||
"horizon.friends.copied": "Αντιγράφηκε",
|
||||
"horizon.friends.copy_code": "Αντιγραφή Κωδικού",
|
||||
"horizon.friends.stop_sharing": "Διακοπή κοινής χρήσης",
|
||||
"horizon.friends.none_yet": "Δεν υπάρχουν φίλοι εδώ ακόμα.",
|
||||
"horizon.friends.here_now": "Εδώ τώρα: %s",
|
||||
"horizon.friends.share_intro": "Μοιραστείτε αυτόν τον κόσμο πίσω από έναν κωδικό πρόσκλησης: ανοίγει σε LAN και οι φίλοι στο Διαδίκτυο συμμετέχουν με τον κωδικό. Κανένας διακομιστής μας δεν εμπλέκεται.",
|
||||
"horizon.friends.start_sharing": "Ξεκινήστε την κοινή χρήση",
|
||||
"horizon.friends.troubleshooting": "Αντιμετώπιση προβλημάτων...",
|
||||
"horizon.friends.stopped": "Σταμάτησε η κοινή χρήση του κωδικού πρόσκλησης.",
|
||||
"horizon.friends.short_code_changed": "Κάποιος συνέχισε να δοκιμάζει έναν λάθος σύντομο κωδικό, οπότε άλλαξε.",
|
||||
"horizon.friends.wrong_code": "Κάποιος δοκίμασε λάθος κωδικό.",
|
||||
"horizon.friends.code_ready": "Ο κωδικός πρόσκλησής σας είναι έτοιμος: Μενού παιχνιδιού, Παίξτε με φίλους. Αντιγράψτε το και στείλτε το στους φίλους σας.",
|
||||
"horizon.friends.share_failed": "Δεν μπορούσα να μοιραστώ τον κόσμο: %s",
|
||||
"horizon.friends.finding_route": "Βρείτε πώς μπορούν να σας φτάσουν οι φίλοι…",
|
||||
"horizon.osk.shift": "Μετατόπιση",
|
||||
"horizon.osk.space": "Διάστημα",
|
||||
"horizon.osk.delete": "Διαγραφή",
|
||||
"horizon.osk.type": "Τύπος",
|
||||
"horizon.osk.symbols": "Σύμβολα",
|
||||
"horizon.osk.close": "Κλείσιμο",
|
||||
"horizon.extras.chat_animation.tooltip": "Μια νέα γραμμή συνομιλίας ολισθαίνει από τα αριστερά ενώ σβήνει, αντί να εμφανίζεται ολόκληρη.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Η έγχρωμη γραμμή στα αριστερά μιας γραμμής συνομιλίας.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ο τροχός κύλισης πάνω από ένα κουμπί στις ρυθμίσεις αλλάζει την τιμή του, όπως στη βανίλια. Ανενεργό, ο τροχός κάνει κύλιση μόνο στη λίστα.",
|
||||
"horizon.extras.pack_browser.tooltip": "Το Select Resource Packs παίρνει ένα κουμπί Προσθήκη που αναζητά το Modrinth και εγκαθιστά ένα πακέτο στο φάκελο των πακέτων σας. Εκτός, το παιχνίδι δεν έρχεται ποτέ σε επαφή με τη Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Σε έναν κόσμο, ένας άλλος ελεγκτής μπορεί να εγγραφεί ως νέος παίκτης: πατήστε A όταν σας ζητήσει, μετά επιλέξτε ένα προφίλ και πού θα παίξετε.",
|
||||
"horizon.splitscreen.setup.monitor": "Οθόνη %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "μια οθόνη",
|
||||
"horizon.splitscreen.setup.window": "Παράθυρο %s",
|
||||
"horizon.splitscreen.setup.new_window": "Νέο παράθυρο",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Γέμισμα %s",
|
||||
"horizon.splitscreen.setup.done": "Έγινε",
|
||||
"horizon.splitscreen.setup.on": "Ενεργό",
|
||||
"horizon.splitscreen.setup.off": "Απενεργοποίηση",
|
||||
"horizon.splitscreen.setup.player": "Παίκτης %s",
|
||||
"horizon.splitscreen.setup.revert": "Επαναφορά",
|
||||
"horizon.splitscreen.setup.apply": "Εφαρμόστε",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Κανείς δεν παίζει στο Παράθυρο 1: κρύβεται",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Κανείς δεν παίζει στο Παράθυρο %s: κλείνει στην Εφαρμογή",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Κανείς δεν παίζει στο Παράθυρο %s: δεν ανοίγει",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Το παράθυρο %s γεμίζει την οθόνη του παραθύρου 1: οι παίκτες του παίζουν στο Παράθυρο 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Η οθόνη του παραθύρου %s δεν είναι συνδεδεμένη: οι παίκτες του παίζουν στο Παράθυρο 1",
|
||||
"horizon.splitscreen.setup.status.off": "Η διαχωριστική οθόνη είναι απενεργοποιημένη.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Ενεργοποιήστε το και, στη συνέχεια, πατήστε ένα κουμπί σε άλλο χειριστήριο σε έναν κόσμο.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Πατήστε A για να εγγραφείτε και να επιλέξετε ένα προφίλ.",
|
||||
"horizon.splitscreen.setup.status.idle": "Splitscreen σε αδράνεια",
|
||||
"horizon.splitscreen.setup.status.alone": "Δεν υπάρχουν άλλοι τοπικοί παίκτες.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Τα σόλο χειριστήρια και τα σχόλια του ελεγκτή παραμένουν αμετάβλητα.",
|
||||
"horizon.splitscreen.setup.window_empty": "Παράθυρο %s: κενό",
|
||||
"horizon.splitscreen.setup.note.select": "Κάντε κλικ σε ένα πρόγραμμα αναπαραγωγής ή σύρετέ το σε ένα παράθυρο ή μια οθόνη.",
|
||||
"horizon.splitscreen.setup.note.apply": "Εφαρμογή για αναπαραγωγή με αυτόν τον τρόπο, Επαναφορά για αναίρεση.",
|
||||
"horizon.splitscreen.setup.note.place": "Πού πρέπει να παίξει ο %s;",
|
||||
"horizon.splitscreen.setup.this_player": "αυτός ο παίκτης",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Το Splitscreen απενεργοποιείται μόλις αποχωρήσουν οι υπόλοιποι (μενού παύσης).",
|
||||
"horizon.splitscreen.setup.window_place": "Παράθυρο %s (%s από %s)",
|
||||
"horizon.splitscreen.setup.names": "Ονόματα",
|
||||
"horizon.splitscreen.setup.identify": "Προσδιορίστε",
|
||||
"horizon.splitscreen.setup.layout.two": "2 κοινή χρήση: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 κοινή χρήση: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "Αυτόματο",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Δίπλα δίπλα",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Πάνω και κάτω",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Ένα φαρδύ από πάνω",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Ένας ψηλός στα αριστερά",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Δίπλα δίπλα",
|
||||
"horizon.splitscreen.setup.help.player": "Κάντε κλικ ή σύρετε %s για να τα μετακινήσετε.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (δίπλα όταν το παράθυρο είναι φαρδύ). Πατήστε για αλλαγή.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Πατήστε για αλλαγή.",
|
||||
"horizon.splitscreen.setup.help.switch": "Διαχωρισμένη οθόνη για αυτόν τον υπολογιστή, ενεργοποιημένη ή απενεργοποιημένη.",
|
||||
"horizon.splitscreen.setup.help.identify": "Η προβολή κάθε παίκτη δείχνει σε ποιο παράθυρο βρίσκεται.",
|
||||
"horizon.splitscreen.setup.help.names": "Το όνομα κάθε παίκτη στη γωνία της όψης του.",
|
||||
"horizon.splitscreen.setup.help.revert": "Αναίρεση των αλλαγών που δεν έχουν εφαρμοστεί ακόμα.",
|
||||
"horizon.splitscreen.setup.help.apply": "Παίξτε με τον τρόπο που δείχνει ο χάρτης.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Τίποτα δεν έχει αλλάξει ακόμα.",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Ο ελεγκτής είναι συνδεδεμένος",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Ο ελεγκτής αποσυνδέθηκε",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Συνδέστε το ξανά ή πατήστε ένα κουμπί σε άλλο",
|
||||
"horizon.splitscreen.prompt.join.one": "Ο %s θέλει να συμμετάσχει",
|
||||
"horizon.splitscreen.prompt.join.many": "Οι ελεγκτές %s θέλουν να συμμετάσχουν",
|
||||
"horizon.splitscreen.prompt.join.yes": "Εγγραφείτε",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Παίξτε ως P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Όχι",
|
||||
"horizon.splitscreen.prompt.lost": "%s: ο ελεγκτής αποσυνδέθηκε",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Χρησιμοποιήστε άλλο",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Φύγε",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Ο ελεγκτής σας σταματά να σας οδηγεί και του προσφέρεται να συμμετάσχει (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s σε %s: Ο Α το ρίχνει εδώ, ο Β το βάζει πίσω.",
|
||||
"horizon.splitscreen.setup.note.held": "%s σήκωσε: αριστερά/δεξιά διαλέγει πού, ο Α πέφτει, ο Β το βάζει πίσω.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Κρύβει μοντέλα πίσω από αδιαφανή μπλοκ. Ασύγχρονοι έλεγχοι εκτός νήματος. οντότητες τικ.",
|
||||
"Block Entities": "Αποκλεισμός οντοτήτων",
|
||||
"Shulker Boxes": "Κουτιά Shulker",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Επαναχρησιμοποιεί τα καθιζημένα κουτιά. Η κίνηση του καπακιού παραμένει κινούμενη.",
|
||||
"Signs": "Σημάδια",
|
||||
"Reuses sign text until text, light, or font changes.": "Επαναχρησιμοποιεί το κείμενο της πινακίδας μέχρι να αλλάξει το κείμενο, το φως ή η γραμματοσειρά.",
|
||||
"Decorated Pots": "Διακοσμημένες γλάστρες",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Επαναχρησιμοποιεί γλάστρες. σπάσιμο ταλάντευση και όστρακα αλλαγές εκ νέου σχεδίαση.",
|
||||
"Bells": "Καμπάνες",
|
||||
"Reuses still bells; ringing stays animated.": "Επαναχρησιμοποιεί ακίνητα κουδούνια. Το κουδούνισμα παραμένει κινούμενο.",
|
||||
"Chests": "Σεντούκια",
|
||||
"Reuses closed chests; moving lids stay animated.": "Επαναχρησιμοποιεί τα κλειστά σεντούκια. Τα κινούμενα καπάκια παραμένουν κινούμενα.",
|
||||
"Banners": "Πανό",
|
||||
"Reuses poles; flags and pattern layers still update.": "Επαναχρησιμοποιεί τους πόλους. Οι σημαίες και τα επίπεδα μοτίβων εξακολουθούν να ενημερώνονται.",
|
||||
"Copper Golem Statues": "Χάλκινα αγάλματα Golem",
|
||||
"Reuses statue geometry until its block state changes.": "Επαναχρησιμοποιεί τη γεωμετρία του αγάλματος μέχρι να αλλάξει η κατάσταση μπλοκ.",
|
||||
"Leaf Culling": "Αποκοπή φύλλων",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Κάθε πρόσωπο φύλλου. Φανταχτά φύλλα βανίλιας.",
|
||||
"Distant Plant Dither": "Μακρινό Φυτό Dither",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Διατηρεί τα εκλεκτά φυτά σε συσκευασίες υψηλής ανάλυσης από το να ξεθωριάσουν μακριά.",
|
||||
"Screenshot Quality": "Ποιότητα στιγμιότυπου οθόνης",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "Στιγμιότυπα οθόνης F2 σε αυτό πολλές φορές την ανάλυση του παιχνιδιού. Το υψηλότερο απαιτεί περισσότερο χρόνο.",
|
||||
"Frame Generation": "Γενιά πλαισίων",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Εμφανίζει ένα φτιαγμένο καρέ ανάμεσα σε καθένα από τα δύο πραγματικά: πιο ομαλό, λίγο μεγαλύτερη καθυστέρηση.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Επαναχρησιμοποιεί τον χάρτη σκιάς πακέτου όταν ο ήλιος, η θέα και ο κόσμος ταιριάζουν. Απενεργοποίηση από προεπιλογή.",
|
||||
"Find BlueMap": "Βρείτε το BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Στους διακομιστές, βρίσκει τον ιστό χάρτη τους BlueMap για μακρινό έδαφος. Επόμενη συμμετοχή.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Πόσο μακριά γίνεται λήψη των πλακιδίων BlueMap. Επόμενη συμμετοχή.",
|
||||
"Synchronous": "Σύγχρονη",
|
||||
"Asynchronous": "Ασύγχρονη",
|
||||
"Solid": "Στερεά",
|
||||
"Solid Aggressive": "Στερεά επιθετικά",
|
||||
"Hollow": "Κούφιο",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Τα φύλλα πλήρως μέσα σε ένα θόλο τραβούν στερεά: λιγότερα διαφανή στρώματα.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Τα φύλλα με μια ανοιχτή πλευρά τραβούν επίσης στερεά: πιο γρήγορες, πιο χοντρές άκρες.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Πέφτει όψεις ανάμεσα στα φύλλα: πιο γρήγορα, αλλά τα κενά φαίνονται μέσα.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Προηγούμενη υποδοχή",
|
||||
"Hotbar: Next Slot": "Hotbar: Επόμενη κουλοχέρη",
|
||||
"horizon.agent.connected.one": "Συνδεδεμένο: 1 σύνδεση πράκτορα",
|
||||
"horizon.agent.connected.many": "Συνδεδεμένοι: %s συνδέσεις πρακτόρων",
|
||||
"horizon.agent.connected.one_ago": "Συνδεδεμένο: 1 σύνδεση πράκτορα, τελευταία εντολή πριν από %s s",
|
||||
"horizon.agent.connected.many_ago": "Συνδέθηκε: %s συνδέσεις πράκτορα, τελευταία εντολή %s πριν",
|
||||
"horizon.agent.waiting": "Περιμένοντας έναν πράκτορα.",
|
||||
"horizon.agent.off": "Off: οι πράκτορες δεν μπορούν να φτάσουν σε αυτό το παιχνίδι.",
|
||||
"horizon.agent.session_opened": "Άνοιξε για αυτήν την περίοδο λειτουργίας από το πρόγραμμα που ξεκίνησε το παιχνίδι.",
|
||||
"horizon.agent.socket": "Υποδοχή: %s (αντιγράψτε τα στοιχεία για την πλήρη διαδρομή του)",
|
||||
"horizon.agent.local_only": "Μόνο τοπικά: πράκτορες σε αυτόν τον υπολογιστή, ποτέ στο δίκτυο.",
|
||||
"%s nits": "%s κόνιδες",
|
||||
"Auto (%s nits)": "Αυτόματο (%s nits)",
|
||||
"%.2f nits": "%.2f κόνιδες",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Κρύβει μοντέλα πίσω από αδιαφανή μπλοκ. Ενημέρωση οντοτήτων. Εκτός με προσαρμοσμένα πακέτα.",
|
||||
"horizon.extras.repro_keys": "Συντομεύσεις εγγραφής",
|
||||
"horizon.extras.repro_keys.tooltip": "Το F3+R ξεκινά ή σταματά μια επαναληπτική εγγραφή και το F3+E γράφει το πακέτο του. Απενεργοποίηση από προεπιλογή: μια εγγραφή αντιγράφει τον κόσμο.",
|
||||
"horizon.agent.record_session": "Έναρξη εγγραφής συνεδρίας",
|
||||
"horizon.agent.record_stop": "Διακοπή εγγραφής συνεδρίας",
|
||||
"horizon.agent.export_repro": "Εξαγωγή Repro Bundle",
|
||||
"horizon.repro.started": "Η εγγραφή ξεκίνησε. Το F3+R το σταματά και γράφει το bundle.",
|
||||
"horizon.repro.stopped": "Η εγγραφή σταμάτησε: %s",
|
||||
"horizon.repro.bundle": "Πακέτο Repro: %s",
|
||||
"horizon.repro.no_recording": "Δεν υπάρχει ακόμα εγγραφή για εξαγωγή.",
|
||||
"horizon.repro.replay_started": "Έναρξη επανάληψης: %s",
|
||||
"horizon.pack.addInfo": "Προσθέστε ένα πακέτο πόρων από το Modrinth",
|
||||
"horizon.pack.addWorking": "Ρωτώντας τον Modrinth...",
|
||||
"horizon.pack.addNone": "Δεν βρέθηκαν πακέτα πόρων",
|
||||
"horizon.pack.addTitle": "Προσθέστε ένα πακέτο πόρων",
|
||||
"horizon.pack.install": "Εγκατάσταση",
|
||||
"horizon.pack.installing": "Εγκατάσταση",
|
||||
"horizon.pack.installed": "Εγκατεστημένο",
|
||||
"horizon.pack.search": "Αναζήτηση",
|
||||
"horizon.pack.searchHint": "Αναζήτηση στο Modrinth...",
|
||||
"horizon.pack.filters": "Φίλτρα",
|
||||
"horizon.pack.filter.realistic": "Ρεαλιστική",
|
||||
"horizon.pack.filter.simplistic": "Απλουστευτική",
|
||||
"horizon.pack.filter.vanillaLike": "Σαν βανίλια",
|
||||
"horizon.pack.addFailed": "Δεν ήταν δυνατή η προσθήκη του πακέτου: %s",
|
||||
"horizon.extras.header.cosmetics": "Καλλυντικά",
|
||||
"horizon.extras.cosmetics_mine": "Τα καλλυντικά μου",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Κρυφό: κάθε παίκτης του Horizon σας βλέπει χωρίς τα καλλυντικά σας, συμπεριλαμβανομένων εσείς. Ο ίδιος διακόπτης με την ντουλάπα του εκτοξευτή.",
|
||||
"horizon.extras.cosmetics_others": "Καλλυντικά άλλων παικτών",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Τι φοράνε οι άλλοι παίκτες του Horizon: τα πάντα, μόνο τα στυλ των ετικετών τους ή τίποτα. Κρυφά, το παιχνίδι δεν ρωτά ποτέ τι φοράνε.",
|
||||
"horizon.extras.cosmetics_auras": "Οι αύρες και τα μονοπάτια των άλλων",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Αύρες και μονοπάτια φορούν άλλοι παίκτες. Απενεργοποίηση από προεπιλογή.",
|
||||
"horizon.extras.cosmetics.shown": "Εμφανίζεται",
|
||||
"horizon.extras.cosmetics.hidden": "Κρυφό",
|
||||
"horizon.extras.cosmetics.name_tags": "Μόνο ετικέτες ονομάτων"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"horizon.options.controller": "Controller",
|
||||
"horizon.options.three_d_skin_layers": "3D Skin Layers",
|
||||
"horizon.options.vibration": "Vibration",
|
||||
"horizon.options.adaptive_triggers": "Adaptive Triggers",
|
||||
"horizon.options.lightbar": "Lightbar",
|
||||
@@ -413,6 +414,9 @@
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Your controller stops driving you and is offered to join (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s to %s: A drops it here, B puts it back.",
|
||||
"horizon.splitscreen.setup.note.held": "%s picked up: left/right picks where, A drops, B puts it back.",
|
||||
"horizon.splitscreen.online_only": "This server is in online mode: offline profiles can't join it, each player needs its own Minecraft account.",
|
||||
"horizon.splitscreen.own_account": "This server needs each player's own Minecraft account",
|
||||
"horizon.splitscreen.local_only": "Other players can join this server only with their own Minecraft account: offline profiles play in your world, on LAN or on a server on your local network.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Hides models behind opaque blocks. Async checks off-thread; entities tick.",
|
||||
"Block Entities": "Block Entities",
|
||||
"Shulker Boxes": "Shulker Boxes",
|
||||
@@ -445,6 +449,8 @@
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "How far around you BlueMap tiles are downloaded. Next join.",
|
||||
"Synchronous": "Synchronous",
|
||||
"Asynchronous": "Asynchronous",
|
||||
"Hidden Faces": "Hidden Faces",
|
||||
"Drops leaf faces behind two leaf layers: faster, same crowns.": "Drops leaf faces behind two leaf layers: faster, same crowns.",
|
||||
"Solid": "Solid",
|
||||
"Solid Aggressive": "Solid Aggressive",
|
||||
"Hollow": "Hollow",
|
||||
@@ -477,6 +483,10 @@
|
||||
"horizon.repro.error": "Repro: %s",
|
||||
"horizon.repro.no_recording": "There is no recording to export yet.",
|
||||
"horizon.repro.replay_started": "Replay started: %s",
|
||||
"Particle Culling": "Particle Culling",
|
||||
"Draws every particle.": "Draws every particle.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.": "Skips offscreen, subpixel and terrain-hidden particles.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.": "Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.",
|
||||
"horizon.pack.add": "+",
|
||||
"horizon.pack.addInfo": "Add a resource pack from Modrinth",
|
||||
"horizon.pack.addWorking": "Asking Modrinth...",
|
||||
|
||||
@@ -199,6 +199,10 @@
|
||||
"horizon.shader_options.open": "Opciones de sombreadores…",
|
||||
"horizon.shader_options.profile": "Perfil",
|
||||
"horizon.shader_options.loading": "Leyendo las opciones del paquete de sombreadores…",
|
||||
"Particle Culling": "Particle Culling",
|
||||
"Draws every particle.": "Draws every particle.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.": "Skips offscreen, subpixel and terrain-hidden particles.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.": "Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.",
|
||||
"General": "General",
|
||||
"Advanced": "Avanzado",
|
||||
"Undo": "Deshacer",
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
{
|
||||
"horizon.options.controller": "کنترل کننده",
|
||||
"horizon.options.vibration": "لرزش",
|
||||
"horizon.options.adaptive_triggers": "محرک های تطبیقی",
|
||||
"horizon.options.lightbar": "نوار نور",
|
||||
"horizon.options.look_sensitivity": "حساسیت نگاه",
|
||||
"horizon.options.invert_x": "معکوس کردن محور X نگاه",
|
||||
"horizon.options.invert_y": "معکوس کردن محور Y نگاه",
|
||||
"horizon.options.sneak_button": "دکمه دزدکی رفتن",
|
||||
"horizon.options.move_deadzone": "ناحیه مرده حرکت",
|
||||
"horizon.options.look_deadzone": "ناحیه مرده نگاه",
|
||||
"horizon.options.button_glyphs": "گلیف دکمه",
|
||||
"horizon.options.menu_navigation": "پیمایش منو",
|
||||
"horizon.options.on_screen_keyboard": "صفحه کلید روی صفحه",
|
||||
"horizon.options.chat_delay": "تاخیر چت",
|
||||
"horizon.options.lightbar.health": "سلامتی",
|
||||
"horizon.options.lightbar.dimension": "بعد",
|
||||
"horizon.options.glyphs.auto": "خودکار",
|
||||
"horizon.options.menu_focus": "تمرکز کنید",
|
||||
"horizon.options.menu_pointer": "اشاره گر",
|
||||
"horizon.options.keyboard.auto": "خودکار",
|
||||
"horizon.options.keyboard.steam": "بخار",
|
||||
"horizon.options.unavailable": "این ویژگی هنوز در Horizon موجود نیست.",
|
||||
"horizon.options.search": "تنظیمات جستجو...",
|
||||
"horizon.pages.mods": "مدها",
|
||||
"horizon.pages.shader_packs": "بسته های سایه زن",
|
||||
"horizon.pages.shader_options": "گزینه های سایه زن",
|
||||
"horizon.pages.controller": "تنظیمات کنترلر",
|
||||
"horizon.pages.controller_binds": "کنترلر متصل می شود",
|
||||
"horizon.pages.agent": "عامل هوش مصنوعی",
|
||||
"horizon.split.leave": "ترک کنید",
|
||||
"horizon.pages.split_settings": "تنظیمات Splitscreen",
|
||||
"horizon.pages.split_profiles": "کی بازی میکنه",
|
||||
"horizon.pages.music": "موسیقی",
|
||||
"horizon.pages.music.menu": "منو",
|
||||
"horizon.pages.music.overworld": "فرا جهان",
|
||||
"horizon.pages.music.creative": "خلاق",
|
||||
"horizon.pages.music.underwater": "زیر آب",
|
||||
"horizon.pages.music.end": "پایان",
|
||||
"horizon.pages.music.boss": "رئیس",
|
||||
"horizon.pages.music.credits": "اعتبارات",
|
||||
"horizon.pages.music.overworld_biomes": "بیوم های فراجهانی",
|
||||
"horizon.pages.music.nether_biomes": "بیوم های نه",
|
||||
"horizon.pages.music.end_biomes": "پایان دادن به بیوم ها",
|
||||
"horizon.pages.music.other": "موسیقی دیگر",
|
||||
"horizon.pages.music.empty": "بدون آهنگ موسیقی: صداهای بازی بارگذاری نمی شوند.",
|
||||
"horizon.pages.music.tooltip": "آهنگهای داخلی، بر اساس موقعیت: اکنون یکی را بازی کنید، یا هر کدام را خاموش کنید تا بازی انتخاب آن را متوقف کند.",
|
||||
"horizon.pages.settings.tooltip": "تنظیمات خود Horizon: گرافیک، کنترلرها، موارد اضافی و مدها.",
|
||||
"horizon.pages.graphics.tooltip": "گرافیک، به سادگی: هر تنظیم می گوید که چه هزینه ای دارد.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "هر تنظیمات گرافیکی، شامل Horizon، با هزینه هر کدام.",
|
||||
"horizon.pages.controller.tooltip": "کنترلکنندهها: دکمهها، چوبها، لرزش و ناوبری منو.",
|
||||
"horizon.pages.extras.tooltip": "افزودنیهای اختیاری، همه بهطور پیشفرض خاموش هستند: بازی تا زمانی که یکی را روشن نکنید، باقی میماند.",
|
||||
"horizon.pages.agent.tooltip": "اجازه دهید عامل هوش مصنوعی شما بازی کند: کلاینت های horizon-ctl و MCP در این رایانه.",
|
||||
"horizon.pages.split_profiles.tooltip": "چه کسی بازی می کند: نمایه ای که در این پوشه بازی نگهداری می شود.",
|
||||
"horizon.pages.split_setup.tooltip": "پنجره ها و مانیتورها و اینکه چه کسی کجا بازی می کند.",
|
||||
"horizon.pages.shader_packs.tooltip": "بسته های سایه زن از پوشه shaderpacks: متعلق به Horizon و آنهایی که در قالب shaders/ هستند. یکی در یک زمان می دود.",
|
||||
"horizon.pages.mods.tooltip": "مدهای ساخته شده برای Horizon، از پوشه horizon-mods. هر کدام در یک جعبه شنی اجرا می شود و تا زمانی که آن را روشن نکنید خاموش می ماند.",
|
||||
"horizon.pages.open.graphics": "گرافیک ...",
|
||||
"horizon.pages.open.graphics_advanced": "گرافیک پیشرفته ...",
|
||||
"horizon.pages.open.shader_packs": "بسته های سایه زن...",
|
||||
"horizon.pages.open.controller": "تنظیمات کنترلر...",
|
||||
"horizon.pages.open.split_setup": "تقسیم صفحه...",
|
||||
"horizon.pages.open.extras": "موارد اضافی...",
|
||||
"horizon.pages.open.mods": "مدهای ...",
|
||||
"horizon.pages.open.agent": "عامل هوش مصنوعی ...",
|
||||
"horizon.pages.open.controller_binds": "کنترلر باند ...",
|
||||
"horizon.pages.open.music": "موسیقی...",
|
||||
"horizon.mods.empty": "بدون مد: فایل های hmod را در پوشه horizon-mods قرار دهید",
|
||||
"horizon.mods.stopped": " (توقف شد)",
|
||||
"horizon.mods.not_built": " (ساخته نشده)",
|
||||
"horizon.mods.dev": " (توسعه دهنده)",
|
||||
"horizon.mods.settings": "تنظیمات",
|
||||
"horizon.mods.in_world": " (دنیا را رها کن تا تغییر کند)",
|
||||
"horizon.mods.open_folder": "پوشه Mod را باز کنید",
|
||||
"horizon.shader_packs.empty": "بدون بسته های سایه زن: آنها را در پوشه shaderpacks قرار دهید",
|
||||
"horizon.shader_options.open": "گزینه های سایه زن...",
|
||||
"horizon.shader_options.profile": "نمایه",
|
||||
"horizon.shader_options.loading": "خواندن گزینه های بسته سایه زن...",
|
||||
"horizon.agent.copy_details": "کپی جزئیات اتصال",
|
||||
"horizon.multiplayer.join": "بپیوندید",
|
||||
"horizon.multiplayer.cancel_reconnecting": "لغو اتصال مجدد",
|
||||
"horizon.multiplayer.join_friend": "به یک دوست بپیوندید",
|
||||
"horizon.multiplayer.invite_code": "کد دعوت",
|
||||
"horizon.multiplayer.relay": "رله (اختیاری)",
|
||||
"Preset": "از پیش تعیین شده",
|
||||
"LOD Distance": "فاصله LOD",
|
||||
"Far Detail": "جزئیات دور",
|
||||
"Cave Culling": "کندن غار",
|
||||
"Entity Culling": "کالینگ موجودیت",
|
||||
"Chunk Updates": "به روز رسانی های تکه ای",
|
||||
"Upload Budget": "بارگذاری بودجه",
|
||||
"Sky": "آسمان",
|
||||
"Fog": "مه",
|
||||
"Weather": "آب و هوا",
|
||||
"Animated Textures": "بافت های متحرک",
|
||||
"Present Mode": "حالت حاضر",
|
||||
"Frame Pacing": "سرعت قاب",
|
||||
"Worker Threads": "موضوعات کارگری",
|
||||
"Resolution Scale": "مقیاس وضوح",
|
||||
"Anti-aliasing": "ضد آلیاسینگ",
|
||||
"Upscaling": "ارتقاء مقیاس",
|
||||
"Paper White": "کاغذ سفید",
|
||||
"Peak Brightness": "اوج روشنایی",
|
||||
"Black Level": "سطح سیاه",
|
||||
"UI Brightness": "روشنایی رابط کاربری",
|
||||
"Battery Mode": "حالت باتری",
|
||||
"Translucent Batching": "دسته بندی شفاف",
|
||||
"Performance Overlay": "پوشش عملکرد",
|
||||
"Full Detail Distance": "فاصله کامل جزئیات",
|
||||
"On": "روشن",
|
||||
"Off": "خاموش",
|
||||
"Unlimited": "نامحدود",
|
||||
"Auto": "خودکار",
|
||||
"Custom": "سفارشی",
|
||||
"Performance": "عملکرد",
|
||||
"Balanced": "متعادل",
|
||||
"Quality": "کیفیت",
|
||||
"Ultra": "فوق العاده",
|
||||
"Ultra Performance": "عملکرد فوق العاده",
|
||||
"Native": "بومی",
|
||||
"Full": "کامل",
|
||||
"Reduced": "کاهش یافته است",
|
||||
"Efficient": "کارآمد",
|
||||
"Precise": "دقیق",
|
||||
"Auto (VSync)": "خودکار (VSync)",
|
||||
"Mailbox": "صندوق پستی",
|
||||
"Immediate": "فوری",
|
||||
"FPS and Graph": "FPS و نمودار",
|
||||
"No cost": "بدون هزینه",
|
||||
"Low cost": "هزینه کم",
|
||||
"Medium cost": "هزینه متوسط",
|
||||
"High cost": "هزینه بالا",
|
||||
"Saves time": "باعث صرفه جویی در زمان می شود",
|
||||
"Graphics": "گرافیک",
|
||||
"Done": "انجام شد",
|
||||
"This feature is not yet available in Horizon.": "این ویژگی هنوز در Horizon موجود نیست.",
|
||||
"Your display or system doesn't offer HDR output.": "نمایشگر یا سیستم شما خروجی HDR را ارائه نمی دهد.",
|
||||
"Turn HDR on to use it.": "برای استفاده از HDR را روشن کنید.",
|
||||
"Your GPU or graphics API doesn't support it.": "GPU یا API گرافیکی شما از آن پشتیبانی نمی کند.",
|
||||
"%s chunks": "%s تکه ها",
|
||||
"%s per frame": "%s در هر فریم",
|
||||
"%s MiB/frame": "%s مگابایت/فریم",
|
||||
"%s KiB/frame": "%s کیلوبایت/قاب",
|
||||
"Auto (%s)": "خودکار (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "محدوده زمین با جزئیات کامل فاصله LOD می تواند جهان های محلی را دورتر گسترش دهد.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "زمین های درشت دور در جهان های محلی. پردازنده های گرافیکی مجتمع در 128 تکه متوقف می شوند.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "کشش های کمتر در زمین های دور درشت تر: در این GPU یکپارچه سریع تر.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "کشش های کمتر در زمین های دور درشت تر است، برای پردازنده های گرافیکی سرعت آن کاهش می یابد.",
|
||||
"Field of view. Wider shows more terrain at once.": "میدان دید. عریض تر، زمین های بیشتری را به یکباره نشان می دهد.",
|
||||
"Caps frames per second; a cap saves power and heat.": "فریم بر ثانیه یک کلاه باعث صرفه جویی در انرژی و گرما می شود.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "منتظر تازه شدن نمایشگر است: بدون پارگی، کمی تأخیر.",
|
||||
"Size of menus and the HUD.": "اندازه منوها و HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "گزینه های زیر را با هم تنظیم می کند. تغییر یکی آن را سفارشی می کند.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "گدازه، آتش و نور خورشید درخشان تر از سفید است. به نمایشگر HDR نیاز دارد.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "سیاه ترین رنگ صفحه نمایش شماست. 0 برای OLED؛ ال سی دی ها کمی می درخشند.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "هایلایت ها چقدر روشن می شوند. خودکار: اوج گزارش سیستم شما.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "کاغذ سفید و منوها چقدر روشن به نظر می رسند. خودکار: دسکتاپ شما سفید است.",
|
||||
"How bright the HUD and menus are, against paper white.": "HUD و منوها در برابر سفید کاغذ چقدر روشن هستند.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "اوباش، محصولات زراعی و ماشینآلات تا کجا به کار خود ادامه میدهند. زمان CPU",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "اوباش و اقلام چقدر دور کشیده شده اند، به عنوان یک سهم عادی.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "از زمین های پنهان شده در پشت بخش های بسته می گذرد. هزینه های خاموش بسیار زیاد است.",
|
||||
"Hides entities behind opaque blocks.": "موجودیت ها را در پشت بلوک های مات پنهان می کند.",
|
||||
"No entity occlusion culling.": "حذف انسداد موجودیت وجود ندارد.",
|
||||
"CPU occlusion test runs this frame.": "تست انسداد CPU این فریم را اجرا می کند.",
|
||||
"Occlusion checks run on a worker thread.": "بررسی های انسداد روی یک موضوع کارگر اجرا می شود.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "بخش های مشبک آپلود شده در هر فریم. Lower در هنگام بارگیری نرم تر است.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "سایه های نرم در گوشه ها. هزینه های تکه ای فقط زمان ساخت.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "کپی های کوچکتر از بافت های بلوک برای زمین های دور: درخشش کمتر.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "هنگام ساختن تکهها، رنگهای چمن و آب را در بیومها ترکیب میکند.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "لایه ابری: خاموش، مسطح (سریع) یا سه بعدی (فانتزی).",
|
||||
"How far clouds reach, in chunks.": "ابرها به چه اندازه می رسند، تکه تکه.",
|
||||
"Draws the sky, sun, moon and stars.": "آسمان، خورشید، ماه و ستاره ها را ترسیم می کند.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "مه از راه دور. خاموش لبه دنیای بارگذاری شده را نشان می دهد.",
|
||||
"Draws rain and snow.": "باران و برف را ترسیم می کند.",
|
||||
"How far rain and snow fall around you.": "چقدر دورت باران و برف می بارد.",
|
||||
"How many particles effects spawn.": "چند اثر ذرات تخم ریزی می کنند.",
|
||||
"Round shadows under mobs and items.": "سایه های گرد زیر اوباش و اقلام.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "برگ هایی با شکاف های دید. خاموش آنها را محکم می کشد.",
|
||||
"Sorts water and glass more exactly behind each other.": "آب و لیوان را دقیقا پشت سر هم مرتب می کند.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "کدام قطعه بازسازی شده منتظر فریم است: هیچ کدام، در نزدیکی یا شما.",
|
||||
"Animates water, lava, fire and other moving textures.": "آب، گدازه، آتش و سایر بافت های متحرک را متحرک می کند.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "پیکسل جهانی در هر پیکسل صفحه نمایش. زیر 100٪ در GPU سریعتر است.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "لبه های دندانه دار را صاف می کند. FXAA: نرم تر. TAA: نرم ترین و متحرک ترین چیزها ممکن است دنبال شوند.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "جهان را کوچکتر می کند و آن را با TAA بازسازی می کند. سریع تر، نرم تر.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "خروجی HDR در صفحه نمایش HDR: گدازه، آتش و نور خورشید روشن تر می تابد.",
|
||||
"How frames reach the screen while VSync is off.": "چگونه فریمها در حالی که VSync خاموش است به صفحه نمایش میرسند.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "خواب های کارآمد تا فریم بعدی. چرخش دقیق، مانند وانیل.",
|
||||
"Fills the screen (borderless).": "صفحه را پر می کند (بدون حاشیه).",
|
||||
"Brightens dark places.": "مکان های تاریک را روشن می کند.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "در صورت به حداقل رساندن یا بیکار بودن، نرخ فریم را کاهش می دهد.",
|
||||
"Sways the view while walking.": "هنگام راه رفتن منظره را تغییر می دهد.",
|
||||
"Fades newly loaded chunks in.": "تکه های تازه بارگذاری شده را محو می کند.",
|
||||
"Darkens the screen's edges.": "لبه های صفحه نمایش را تیره می کند.",
|
||||
"Shows when the world saves.": "زمانی نشان می دهد که جهان نجات می دهد.",
|
||||
"Shows attack cooldown.": "خنک شدن حمله را نشان می دهد.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "کلاهک 60 FPS (30 در منو یا بدون تمرکز)، نیمی از کارگران: توان کمتر.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "رشته هایی که تکه هایی را ایجاد می کنند، نور و مشبک می کنند. شروع بعدی",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan، سپس DirectX 12 (ویندوز) یا OpenGL (لینوکس) در جایی که خراب می شود. شروع بعدی",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "آب و لیوان در یک بافر: تماسهای کمتر، VRAM کمی بیشتر. شروع بعدی",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "داده های مش ارسال شده به GPU در هر فریم. Lower در هنگام بارگیری نرم تر است.",
|
||||
"Frame rate and frame times over the game, with a graph.": "نرخ فریم و زمان فریم در بازی، با یک نمودار.",
|
||||
"horizon.multiplayer.finding_friend": "پیدا کردن دنیای دوستت...",
|
||||
"horizon.multiplayer.invalid_address": "آدرس سرور نامعتبر: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "مستقیم به دوستت وصل شد...",
|
||||
"horizon.multiplayer.connected_friend_relay": "از طریق رله به دوستت وصل شد...",
|
||||
"General": "ژنرال",
|
||||
"Advanced": "پیشرفته",
|
||||
"Undo": "واگرد",
|
||||
"Search options": "گزینه های جستجو",
|
||||
"Shortcut": "میانبر",
|
||||
"Distance": "فاصله",
|
||||
"Display": "نمایش",
|
||||
"Interface": "رابط",
|
||||
"Sky and weather": "آسمان و آب و هوا",
|
||||
"Lighting and entities": "روشنایی و موجودیت",
|
||||
"Textures": "بافت ها",
|
||||
"Image": "تصویر",
|
||||
"Chunk loading": "بارگیری تکه",
|
||||
"Culling": "قتل عام",
|
||||
"Power": "قدرت",
|
||||
"Frames": "قاب ها",
|
||||
"Overlay": "پوشش",
|
||||
"Output": "خروجی",
|
||||
"Brightness": "روشنایی",
|
||||
"How far you see, the window and the HUD.": "چقدر دور می بینید، پنجره و HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "آسمان و آب و هوا، نور، بافت و تصویر.",
|
||||
"Chunk loading, culling and power use.": "بارگیری قطعات، حذف و استفاده از برق.",
|
||||
"How frames reach the screen, and the performance overlay.": "نحوه رسیدن فریم ها به صفحه و پوشش عملکرد.",
|
||||
"Brighter-than-white output on an HDR display.": "خروجی روشن تر از سفید در صفحه نمایش HDR.",
|
||||
"Results for \"%s\"": "نتایج برای \"%s\"",
|
||||
"No settings match.": "هیچ تنظیماتی مطابقت ندارد.",
|
||||
"horizon.title.not_affiliated": "به Mojang یا Microsoft وابسته نیست",
|
||||
"horizon.extras.header.lighting": "نورپردازی",
|
||||
"horizon.extras.header.third_person": "نفر سوم",
|
||||
"horizon.extras.header.particles": "ذرات",
|
||||
"horizon.extras.header.inventory": "موجودی",
|
||||
"horizon.extras.header.chat": "چت کنید",
|
||||
"horizon.extras.header.multiplayer": "چند نفره",
|
||||
"horizon.extras.header.sound": "صدا",
|
||||
"horizon.extras.header.menus": "منوها",
|
||||
"horizon.extras.fullbright": "فول برایت",
|
||||
"horizon.extras.dynamic_lights": "چراغ های دینامیک",
|
||||
"horizon.extras.own_name_tag": "برچسب نام خود",
|
||||
"horizon.extras.hide_block_particles": "ذرات بلوک شکستن را پنهان کنید",
|
||||
"horizon.extras.auto_reconnect": "اتصال مجدد خودکار",
|
||||
"horizon.extras.ui_sound_reserve": "کانال ها را برای صداهای رابط کاربری نگه دارید",
|
||||
"horizon.extras.music_page": "صفحه موسیقی",
|
||||
"horizon.extras.mouse_tweaks": "کنترلهای ماوس توییک",
|
||||
"horizon.extras.mouse_tweaks_wheel": "چرخ اسکرول آیتم ها را جابجا می کند",
|
||||
"horizon.extras.sort": "دکمه های مرتب سازی",
|
||||
"horizon.extras.sort_key": "کلید مرتب سازی",
|
||||
"horizon.extras.no_ai": "بدون هوش مصنوعی",
|
||||
"horizon.extras.agent_control": "کنترل عامل",
|
||||
"horizon.extras.splitscreen": "تقسیم صفحه",
|
||||
"horizon.extras.chat_animation": "چت روان",
|
||||
"horizon.extras.chat_indicator": "نشانگر پیام",
|
||||
"horizon.extras.wheel_cycles_settings": "چرخ اسکرول تنظیمات را تغییر می دهد",
|
||||
"horizon.extras.pack_browser": "مرورگر بسته منابع",
|
||||
"horizon.extras.fullbright.tooltip": "بلوکها و موجودیتها را در نور کامل، حتی در مناطق تاریک نشان دهید.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "مشعل ها و دیگر منابع نوری که نگه داشته، افتاده اند یا آتش می گیرند بلوک های اطراف خود را روشن می کنند.",
|
||||
"horizon.extras.own_name_tag.tooltip": "همانطور که سایر بازیکنان آن را می بینند، نام شما را بالای سرتان به صورت سوم شخص نشان می دهد. هنگامی که سرور نام تیم شما را پنهان می کند یا شما نامرئی هستید، پنهان می شود.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "ذرات باقی مانده را که از یک بلوک شکسته ترکیده و از بلوک شما فرو می ریزند پنهان می کند. ذرات دیگر باقی می مانند.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "پس از قطع شدن اتصال یا راه اندازی مجدد، هرگز پس از ضربه زدن یا ممنوعیت، دوباره به سرور می پیوندد.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "صداهای موجود در جهان 16 کانال از 247 کانال صوتی را آزاد میگذارند، بنابراین سیل آنها (یک انفجار بزرگ) نمیتواند کلیکهای منو را خاموش کند.",
|
||||
"horizon.extras.music_page.tooltip": "Music & Sounds یک صفحه Music... دریافت میکند: اکنون هر آهنگ داخلی را پخش کنید، یا یکی را خاموش کنید تا بازی انتخاب آن را متوقف کند. تا زمانی که یک آهنگ را خاموش نکنید، هیچ چیز تغییر نمی کند.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "با کشیدن سمت راست یک مورد در هر شکاف قرار میگیرد، با کشیدن چپ آیتم نگهداشته شده را جمعآوری میکند، Shift + کشیدن چپ هر شکاف را حرکت میدهد.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Down یک مورد از شکاف را به طرف دیگر می فرستد، بالا یکی را برمی گرداند.",
|
||||
"horizon.extras.sort.tooltip": "موجودی، سینهها، بشکهها، صندوقهای انتهایی و جعبههای شالکر را به ترتیب برگه خلاقانه مرتب میکند و پشتهها را ادغام میکند.",
|
||||
"horizon.extras.sort_key.tooltip": "قسمت زیر مکان نما را مرتب می کند. کلیک کنید، سپس یک کلید را فشار دهید؛ فرار آن را باز می کند.",
|
||||
"horizon.extras.no_ai.tooltip": "هر ویژگی هوش مصنوعی را پنهان و خاموش می کند: کنترل عامل، horizon-ctl و سرورهای MCP. سوئیچ پرتاب کننده نیز.",
|
||||
"horizon.extras.agent_control.tooltip": "عامل هوش مصنوعی شما می تواند این بازی را از طریق horizon-ctl یا یک کلاینت MCP، فقط در این رایانه انجام دهد.",
|
||||
"horizon.extras.discord_presence.tooltip": "در حالی که Discord اجرا می شود، Playing Horizon را در نمایه Discord شما نشان می دهد: منوها، تک نفره یا چند نفره، ابعاد و زمان بازی. هرگز نام یک جهان یا آدرس سرور.",
|
||||
"Calibrate...": "کالیبره کردن...",
|
||||
"Calibrate Anyway...": "به هر حال کالیبره کردن...",
|
||||
"Use Automatic Setup": "از تنظیم خودکار استفاده کنید",
|
||||
"Shortcut: %s": "میانبر: %s",
|
||||
"Next": "بعدی",
|
||||
"HDR Calibration %s/%s: %s": "کالیبراسیون HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "سه عکس زیر تمام صفحه را در یک زمان یک عکس آزمایشی تنظیم می کند.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "کلیدی که این صفحه را از بازی باز می کند. ابتدا مقید نیست.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "صفحه نمایش شما میزان روشنایی آن را بیان نکرده است: برای تنظیم آن کالیبره کنید.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "قبلاً برای نمایشگر شما تنظیم شده است. فقط در صورتی کالیبره کنید که HDR اشتباه به نظر برسد.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "حداکثر روشنایی و کاغذ سفید را به تنظیمات صفحه نمایش شما باز می گرداند.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "برای نمایشگر خود تنظیم کنید. روی خودکار بگذارید مگر اینکه اشتباه به نظر برسد.",
|
||||
"Lower it until the logo just disappears into the black.": "آن را پایین بیاورید تا زمانی که لوگو در سیاهی محو شود.",
|
||||
"Raise it until the logo just disappears into the bright square.": "آن را بلند کنید تا لوگو در مربع روشن ناپدید شود.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "کارت را به رنگ سفیدی در آورید که در نوری که در آن بازی می کنید، برای مدت طولانی راحت به آن نگاه کنید.",
|
||||
"horizon.splitscreen.profiles.forget": "فراموش کن",
|
||||
"horizon.splitscreen.profiles.create": "ایجاد کنید",
|
||||
"horizon.splitscreen.profiles.you": " - تو",
|
||||
"horizon.splitscreen.profiles.forget_named": "فراموش کردن %s",
|
||||
"horizon.splitscreen.profiles.previous": "قبلی",
|
||||
"horizon.splitscreen.profiles.next": "بعدی",
|
||||
"horizon.splitscreen.profiles.new_button": "نمایه جدید...",
|
||||
"horizon.splitscreen.profiles.forget_title": "نمایه را فراموش کنید؟",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "از لیست این نمونه خارج می شود.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "پیشرفت جهانی و تنظیمات حفظ می شود.",
|
||||
"horizon.splitscreen.profiles.new_title": "نمایه جدید",
|
||||
"horizon.splitscreen.profiles.name_hint": "نام (3 تا 16 حرف، رقم، _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "تغییر نمایه",
|
||||
"horizon.splitscreen.profiles.offline_note": "پروفایل های آفلاین متعلق به این نمونه است",
|
||||
"horizon.splitscreen.profiles.move": "جایی که بازی می کنید حرکت کنید",
|
||||
"horizon.splitscreen.profiles.move_place": "جایی را که بازی می کنید حرکت دهید: %s",
|
||||
"horizon.friends.title": "با دوستان بازی کنید",
|
||||
"horizon.friends.hint": "دوستان کد را بعد از دکمه Friends صفحه عنوان، در اتصال مستقیم یا در صفحه Play راهانداز قرار میدهند.",
|
||||
"horizon.friends.copied": "کپی شده است",
|
||||
"horizon.friends.copy_code": "کد را کپی کنید",
|
||||
"horizon.friends.stop_sharing": "اشتراک گذاری را متوقف کنید",
|
||||
"horizon.friends.none_yet": "هنوز دوستی اینجا نیست",
|
||||
"horizon.friends.here_now": "اکنون اینجا: %s",
|
||||
"horizon.friends.share_intro": "این دنیا را پشت یک کد دعوت به اشتراک بگذارید: به شبکه LAN باز می شود و دوستانی که در اینترنت هستند با این کد ملحق می شوند. هیچ سروری از ما درگیر نیست.",
|
||||
"horizon.friends.start_sharing": "اشتراک گذاری را شروع کنید",
|
||||
"horizon.friends.troubleshooting": "عیب یابی ...",
|
||||
"horizon.friends.stopped": "اشتراکگذاری کد دعوت متوقف شد.",
|
||||
"horizon.friends.short_code_changed": "کسی مدام یک کد کوتاه اشتباه را امتحان میکرد، بنابراین تغییر کرد.",
|
||||
"horizon.friends.wrong_code": "شخصی کد اشتباهی را امتحان کرده است.",
|
||||
"horizon.friends.code_ready": "کد دعوت شما آماده است: منوی بازی، بازی با دوستان. کپی کنید و برای دوستانتان ارسال کنید.",
|
||||
"horizon.friends.share_failed": "نمی توان جهان را به اشتراک گذاشت: %s",
|
||||
"horizon.friends.finding_route": "یافتن نحوه ارتباط دوستان با شما…",
|
||||
"horizon.osk.shift": "شیفت",
|
||||
"horizon.osk.space": "فضا",
|
||||
"horizon.osk.delete": "حذف کنید",
|
||||
"horizon.osk.type": "تایپ کنید",
|
||||
"horizon.osk.symbols": "نمادها",
|
||||
"horizon.osk.close": "بستن",
|
||||
"horizon.extras.chat_animation.tooltip": "یک خط چت جدید به جای اینکه کامل نشان داده شود، از سمت چپ وارد می شود و در حالی که محو می شود.",
|
||||
"horizon.extras.chat_indicator.tooltip": "نوار رنگی در سمت چپ یک خط چت.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "چرخ اسکرول روی یک دکمه در تنظیمات مقدار آن را تغییر می دهد، مانند وانیل. خاموش، چرخ فقط فهرست را پیمایش می کند.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs یک دکمه Add دریافت می کند که Modrinth را جستجو می کند و یک بسته را در پوشه بسته های شما نصب می کند. خاموش، بازی هرگز با Modrinth تماس نمی گیرد.",
|
||||
"horizon.extras.splitscreen.tooltip": "در یک دنیا، کنترلکننده دیگری میتواند بهعنوان یک بازیکن جدید ملحق شود: وقتی A را درخواست کرد، A را فشار دهید، سپس یک نمایه و مکان بازی را انتخاب کنید.",
|
||||
"horizon.splitscreen.setup.monitor": "مانیتور %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "یک مانیتور",
|
||||
"horizon.splitscreen.setup.window": "پنجره %s",
|
||||
"horizon.splitscreen.setup.new_window": "پنجره جدید",
|
||||
"horizon.splitscreen.setup.fill_monitor": "پر کردن %s",
|
||||
"horizon.splitscreen.setup.done": "انجام شد",
|
||||
"horizon.splitscreen.setup.on": "روشن",
|
||||
"horizon.splitscreen.setup.off": "خاموش",
|
||||
"horizon.splitscreen.setup.player": "بازیکن %s",
|
||||
"horizon.splitscreen.setup.revert": "برگردانید",
|
||||
"horizon.splitscreen.setup.apply": "درخواست کنید",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "هیچ کس در پنجره 1 بازی نمی کند: پنهان می شود",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "هیچ کس در پنجره %s بازی نمی کند: در Apply بسته می شود",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "هیچ کس در پنجره %s بازی نمی کند: باز نمی شود",
|
||||
"horizon.splitscreen.setup.warning.on_main": "پنجره %s مانیتور پنجره 1 را پر می کند: بازیکنان آن در پنجره 1 بازی می کنند",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "مانیتور Window %s متصل نیست: پخش کننده های آن در پنجره 1 بازی می کنند",
|
||||
"horizon.splitscreen.setup.status.off": "Splitscreen خاموش است.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "آن را روشن کنید، سپس دکمه ای را روی کنترلر دیگری در یک دنیا فشار دهید.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "برای پیوستن و انتخاب یک نمایه، A را فشار دهید.",
|
||||
"horizon.splitscreen.setup.status.idle": "Splitscreen بیکار",
|
||||
"horizon.splitscreen.setup.status.alone": "بازیکن محلی دیگری وجود ندارد.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "کنترلهای انفرادی و بازخورد کنترلکننده بدون تغییر هستند.",
|
||||
"horizon.splitscreen.setup.window_empty": "پنجره %s: خالی است",
|
||||
"horizon.splitscreen.setup.note.select": "روی یک پخش کننده کلیک کنید یا آن را به پنجره یا مانیتور بکشید.",
|
||||
"horizon.splitscreen.setup.note.apply": "برای پخش از این طریق درخواست دهید، Revert to undo.",
|
||||
"horizon.splitscreen.setup.note.place": "کجا باید %s بازی کند؟",
|
||||
"horizon.splitscreen.setup.this_player": "این بازیکن",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen پس از خروج دیگران خاموش می شود (منوی مکث آنها).",
|
||||
"horizon.splitscreen.setup.window_place": "پنجره %s (%s از %s)",
|
||||
"horizon.splitscreen.setup.names": "نام ها",
|
||||
"horizon.splitscreen.setup.identify": "شناسایی کنید",
|
||||
"horizon.splitscreen.setup.layout.two": "2 اشتراک گذاری: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 اشتراک گذاری: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "خودکار",
|
||||
"horizon.splitscreen.setup.layout.two.1": "کنار هم",
|
||||
"horizon.splitscreen.setup.layout.two.2": "بالا و پایین",
|
||||
"horizon.splitscreen.setup.layout.three.0": "یکی پهن در بالا",
|
||||
"horizon.splitscreen.setup.layout.three.1": "یکی قدش سمت چپ",
|
||||
"horizon.splitscreen.setup.layout.three.2": "کنار هم",
|
||||
"horizon.splitscreen.setup.help.player": "برای جابجایی آنها روی %s کلیک کنید یا بکشید.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (در کنار هم وقتی پنجره پهن است). برای تغییر فشار دهید.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. برای تغییر فشار دهید.",
|
||||
"horizon.splitscreen.setup.help.switch": "صفحه تقسیم برای این رایانه، روشن یا خاموش.",
|
||||
"horizon.splitscreen.setup.help.identify": "نمای هر بازیکن نشان می دهد که در کدام پنجره قرار دارد.",
|
||||
"horizon.splitscreen.setup.help.names": "نام هر بازیکن در گوشه ای از دید آنها.",
|
||||
"horizon.splitscreen.setup.help.revert": "تغییراتی که هنوز اعمال نشده اند را لغو کنید.",
|
||||
"horizon.splitscreen.setup.help.apply": "همانطور که نقشه نشان می دهد بازی کنید.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "هنوز چیزی تغییر نکرده است.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "پد تسلیم شدن",
|
||||
"horizon.splitscreen.prompt.controller_connected": "کنترلر متصل است",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "کنترلر قطع شد",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "آن را دوباره وصل کنید یا دکمه دیگری را فشار دهید",
|
||||
"horizon.splitscreen.prompt.join.one": "%s می خواهد بپیوندد",
|
||||
"horizon.splitscreen.prompt.join.many": "%s کنترلرها می خواهند ملحق شوند",
|
||||
"horizon.splitscreen.prompt.join.yes": "بپیوندید",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "به عنوان P1 بازی کنید",
|
||||
"horizon.splitscreen.prompt.join.no": "خیر",
|
||||
"horizon.splitscreen.prompt.lost": "%s: کنترلر قطع شده است",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "از دیگری استفاده کنید",
|
||||
"horizon.splitscreen.prompt.lost.leave": "ترک کنید",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "کنترلر شما رانده نمی شود و به او پیشنهاد می شود (A) بپیوندد.",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s به %s: A آن را اینجا رها می کند، B آن را برمی گرداند.",
|
||||
"horizon.splitscreen.setup.note.held": "%s برداشت: چپ/راست جایی را انتخاب میکند، A رها میکند، B آن را برمیگرداند.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "مدل ها را پشت بلوک های مات پنهان می کند. Async بررسی های خارج از موضوع. موجودیت ها تیک می زنند.",
|
||||
"Block Entities": "مسدود کردن نهادها",
|
||||
"Shulker Boxes": "جعبه های شولکر",
|
||||
"Reuses settled boxes; lid movement stays animated.": "از جعبه های ته نشین شده مجدد استفاده می کند. حرکت درب متحرک باقی می ماند.",
|
||||
"Signs": "نشانه ها",
|
||||
"Reuses sign text until text, light, or font changes.": "از متن علامت تا زمانی که متن، نور یا فونت تغییر کند دوباره استفاده میکند.",
|
||||
"Decorated Pots": "گلدان های تزئین شده",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "استفاده مجدد از گلدان های ثابت شکستن تلو تلو خوردن و تغییرات sherd دوباره ترسیم می شود.",
|
||||
"Bells": "زنگ ها",
|
||||
"Reuses still bells; ringing stays animated.": "از زنگ های ثابت استفاده مجدد می کند. زنگ متحرک می ماند.",
|
||||
"Chests": "سینه ها",
|
||||
"Reuses closed chests; moving lids stay animated.": "از سینه های بسته استفاده مجدد می کند. درب های متحرک متحرک می مانند.",
|
||||
"Banners": "بنرها",
|
||||
"Reuses poles; flags and pattern layers still update.": "استفاده مجدد از قطب. پرچم ها و لایه های الگو هنوز به روز می شوند.",
|
||||
"Copper Golem Statues": "مجسمه های مسی گولم",
|
||||
"Reuses statue geometry until its block state changes.": "هندسه مجسمه را دوباره استفاده می کند تا زمانی که حالت بلوک آن تغییر کند.",
|
||||
"Leaf Culling": "کندن برگ",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "هر صورت برگی برگ های فانتزی وانیل.",
|
||||
"Distant Plant Dither": "دیتر گیاهی دوردست",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "گیاهان خوب را در بسته های با وضوح بالا از محو شدن دور نگه می دارد.",
|
||||
"Screenshot Quality": "کیفیت اسکرین شات",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "اسکرین شات های F2 در این چند برابر وضوح بازی. بالاتر زمان بیشتری می برد.",
|
||||
"Frame Generation": "نسل فریم",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "یک قاب ساخته شده را بین هر دو قاب واقعی نشان می دهد: صاف تر، کمی تاخیر بیشتر.",
|
||||
"Shadow Cache": "کش سایه",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "هنگامی که خورشید، نما و جهان مطابقت دارند، از نقشه سایه بسته دوباره استفاده می کند. به طور پیش فرض خاموش است.",
|
||||
"Find BlueMap": "BlueMap را پیدا کنید",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "در سرورها، نقشه وب BlueMap خود را برای مناطق دوردست پیدا می کند. عضویت بعدی",
|
||||
"BlueMap Radius": "شعاع بلومپ",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "کاشی های BlueMap تا چه اندازه در اطراف شما دانلود شده اند. عضویت بعدی",
|
||||
"Synchronous": "همزمان",
|
||||
"Asynchronous": "ناهمزمان",
|
||||
"Solid": "جامد",
|
||||
"Solid Aggressive": "تهاجمی جامد",
|
||||
"Hollow": "توخالی",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "برگها به طور کامل در داخل یک سایبان جامد می شوند: لایه های دید کمتر.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "برگهایی که یک سمت باز دارند نیز جامد میکشند: لبههای سریعتر و درشتتر.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "صورت ها را بین برگ ها می ریزد: سریع تر، اما شکاف ها از بین می روند.",
|
||||
"Hotbar: Previous Slot": "هاتبار: اسلات قبلی",
|
||||
"Hotbar: Next Slot": "هاتبار: اسلات بعدی",
|
||||
"horizon.agent.connected.one": "متصل: 1 اتصال عامل",
|
||||
"horizon.agent.connected.many": "متصل: %s اتصالات عامل",
|
||||
"horizon.agent.connected.one_ago": "متصل: 1 اتصال عامل، آخرین فرمان %s قبل",
|
||||
"horizon.agent.connected.many_ago": "متصل: %s اتصالات عامل، آخرین فرمان %s قبل",
|
||||
"horizon.agent.waiting": "در انتظار یک نماینده",
|
||||
"horizon.agent.off": "خاموش: عوامل نمی توانند به این بازی برسند.",
|
||||
"horizon.agent.session_opened": "برای این جلسه توسط برنامه شروع کننده بازی باز شد.",
|
||||
"horizon.agent.socket": "سوکت: %s (جزئیات را برای مسیر کامل آن کپی کنید)",
|
||||
"horizon.agent.local_only": "فقط محلی: عوامل روی این رایانه، هرگز شبکه.",
|
||||
"%s nits": "%s نیت",
|
||||
"Auto (%s nits)": "خودکار (%s نیت)",
|
||||
"%.2f nits": "%.2f نیت",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "مدل ها را پشت بلوک های مات پنهان می کند. به روز رسانی نهادها خاموش با بسته های سفارشی.",
|
||||
"horizon.extras.repro_keys": "میانبرهای ضبط",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R یک ضبط مجدد را شروع یا متوقف می کند و F3+E بسته آن را می نویسد. خاموش به طور پیشفرض: ضبط از جهان کپی میکند.",
|
||||
"horizon.agent.record_session": "شروع به ضبط جلسه",
|
||||
"horizon.agent.record_stop": "توقف ضبط جلسه",
|
||||
"horizon.agent.export_repro": "صادرات Repro باندل",
|
||||
"horizon.repro.started": "ضبط شروع شد؛ F3+R آن را متوقف می کند و باندل را می نویسد.",
|
||||
"horizon.repro.stopped": "ضبط متوقف شد: %s",
|
||||
"horizon.repro.bundle": "بسته Repro: %s",
|
||||
"horizon.repro.error": "تکرار: %s",
|
||||
"horizon.repro.no_recording": "هنوز ضبطی برای صادرات وجود ندارد.",
|
||||
"horizon.repro.replay_started": "شروع مجدد: %s",
|
||||
"horizon.pack.addInfo": "یک بسته منبع از Modrinth اضافه کنید",
|
||||
"horizon.pack.addNone": "هیچ بسته منبعی یافت نشد",
|
||||
"horizon.pack.addTitle": "یک بسته منبع اضافه کنید",
|
||||
"horizon.pack.install": "نصب کنید",
|
||||
"horizon.pack.installing": "در حال نصب",
|
||||
"horizon.pack.installed": "نصب شده است",
|
||||
"horizon.pack.search": "جستجو کنید",
|
||||
"horizon.pack.searchHint": "جستجوی Modrinth...",
|
||||
"horizon.pack.filters": "فیلترها",
|
||||
"horizon.pack.filter.realistic": "واقع بینانه",
|
||||
"horizon.pack.filter.simplistic": "ساده انگارانه",
|
||||
"horizon.pack.filter.vanillaLike": "شبیه وانیل",
|
||||
"horizon.pack.filter.x16": "16 برابر",
|
||||
"horizon.pack.filter.x32": "32 برابر",
|
||||
"horizon.pack.filter.x64": "64 برابر",
|
||||
"horizon.pack.addFailed": "بسته اضافه نشد: %s",
|
||||
"horizon.extras.header.cosmetics": "لوازم آرایشی",
|
||||
"horizon.extras.cosmetics_mine": "لوازم آرایش من",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "پنهان: هر بازیکن Horizon شما را بدون لوازم آرایشی شما، از جمله شما، می بیند. همان سوئیچ کمد لانچر.",
|
||||
"horizon.extras.cosmetics_others": "لوازم آرایشی دیگر بازیکنان",
|
||||
"horizon.extras.cosmetics_others.tooltip": "سایر بازیکنان Horizon چه می پوشند: همه چیز، فقط سبک برچسب نام آنها یا هیچ چیز. پنهان، بازی هرگز نمی پرسد آنها چه می پوشند.",
|
||||
"horizon.extras.cosmetics_auras": "هاله ها و مسیرهای دیگران",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "هاله ها و دنباله های دیگر بازیکنان می پوشند. به طور پیش فرض خاموش است.",
|
||||
"horizon.extras.cosmetics.shown": "نشان داده شده است",
|
||||
"horizon.extras.cosmetics.hidden": "پنهان شده است",
|
||||
"horizon.extras.cosmetics.name_tags": "فقط برچسب های نام"
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"horizon.options.controller": "Ohjain",
|
||||
"horizon.options.vibration": "Tärinä",
|
||||
"horizon.options.adaptive_triggers": "Mukautuvat triggerit",
|
||||
"horizon.options.look_sensitivity": "Katseen herkkyys",
|
||||
"horizon.options.invert_x": "Käännä katseen X-akseli",
|
||||
"horizon.options.invert_y": "Käännä katseen Y-akseli",
|
||||
"horizon.options.sneak_button": "Hiipimispainike",
|
||||
"horizon.options.move_deadzone": "Liikkeen kuollut alue",
|
||||
"horizon.options.look_deadzone": "Katseen kuollut alue",
|
||||
"horizon.options.button_glyphs": "Painikkeiden kuviot",
|
||||
"horizon.options.menu_navigation": "Valikkonavigointi",
|
||||
"horizon.options.on_screen_keyboard": "Näyttönäppäimistö",
|
||||
"horizon.options.chat_delay": "Chatin viive",
|
||||
"horizon.options.lightbar.health": "Terveys",
|
||||
"horizon.options.lightbar.dimension": "Mitat",
|
||||
"horizon.options.menu_focus": "Keskity",
|
||||
"horizon.options.menu_pointer": "Osoitin",
|
||||
"horizon.options.unavailable": "Tämä ominaisuus ei ole vielä saatavilla Horizonissa.",
|
||||
"horizon.options.search": "Hakuasetukset...",
|
||||
"horizon.pages.mods": "Modit",
|
||||
"horizon.pages.shader_packs": "Shader-paketit",
|
||||
"horizon.pages.shader_options": "Varjostimen asetukset",
|
||||
"horizon.pages.controller": "Ohjaimen asetukset",
|
||||
"horizon.pages.controller_binds": "Ohjain sitoo",
|
||||
"horizon.pages.extras": "Horizon Extrat",
|
||||
"horizon.pages.agent": "AI-agentti",
|
||||
"horizon.split.leave": "Lähde",
|
||||
"horizon.pages.split_settings": "Jaetun näytön asetukset",
|
||||
"horizon.pages.split_profiles": "Kuka pelaa?",
|
||||
"horizon.pages.split_setup": "Jaetun näytön asetukset",
|
||||
"horizon.pages.music": "Musiikki",
|
||||
"horizon.pages.music.menu": "Valikko",
|
||||
"horizon.pages.music.overworld": "Ylimaailma",
|
||||
"horizon.pages.music.creative": "Luova",
|
||||
"horizon.pages.music.underwater": "Vedenalainen",
|
||||
"horizon.pages.music.end": "Loppu",
|
||||
"horizon.pages.music.boss": "Pomo",
|
||||
"horizon.pages.music.credits": "Krediitit",
|
||||
"horizon.pages.music.overworld_biomes": "Ylimaailman biomit",
|
||||
"horizon.pages.music.nether_biomes": "Ei kumpikaan biomit",
|
||||
"horizon.pages.music.end_biomes": "Lopeta biomit",
|
||||
"horizon.pages.music.other": "Muu musiikki",
|
||||
"horizon.pages.music.empty": "Ei musiikkikappaleita: pelin ääniä ei ladata.",
|
||||
"horizon.pages.music.tooltip": "Sisäänrakennetut kappaleet tilanteen mukaan: pelaa yksi nyt tai sammuta mikä tahansa, jotta peli lopettaa sen valitsemisen.",
|
||||
"horizon.pages.settings.tooltip": "Horizonin omat asetukset: grafiikka, ohjaimet, extrat ja modit.",
|
||||
"horizon.pages.graphics.tooltip": "Grafiikka yksinkertaisesti: jokainen asetus kertoo, mitä se maksaa.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Jokainen grafiikka-asetus, Horizon mukaan lukien, ja mitä kukin maksaa.",
|
||||
"horizon.pages.controller.tooltip": "Ohjaimet: niiden painikkeet, sauvat, värinä ja valikkojen navigointi.",
|
||||
"horizon.pages.extras.tooltip": "Valinnaiset lisäykset, kaikki oletuksena pois päältä: peli pysyy vaniljana, kunnes kytket sen päälle.",
|
||||
"horizon.pages.agent.tooltip": "Anna oman AI-agenttisi pelata: horizon-ctl- ja MCP-asiakkaasi tällä tietokoneella.",
|
||||
"horizon.pages.split_profiles.tooltip": "Kuka pelaa: tässä pelikansiossa säilytettävä profiili.",
|
||||
"horizon.pages.split_setup.tooltip": "Ikkunat ja näytöt ja kuka pelaa missä.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader-paketit shaderpacks-kansiosta: Horizonin omat ja Shader/-muodossa olevat. Yksi juoksee kerrallaan.",
|
||||
"horizon.pages.mods.tooltip": "Horizonille tehdyt modit horizon-mods-kansiosta. Jokainen toimii hiekkalaatikossa ja pysyy pois päältä, kunnes kytket sen päälle.",
|
||||
"horizon.pages.open.graphics": "Grafiikka...",
|
||||
"horizon.pages.open.graphics_advanced": "Edistynyt grafiikka...",
|
||||
"horizon.pages.open.shader_packs": "Shader Packit...",
|
||||
"horizon.pages.open.controller": "Ohjaimen asetukset...",
|
||||
"horizon.pages.open.split_setup": "Jaettu näyttö...",
|
||||
"horizon.pages.open.extras": "Ekstrat...",
|
||||
"horizon.pages.open.mods": "Modit...",
|
||||
"horizon.pages.open.agent": "AI-agentti...",
|
||||
"horizon.pages.open.controller_binds": "Ohjain sitoo...",
|
||||
"horizon.pages.open.music": "Musiikki...",
|
||||
"horizon.mods.empty": "Ei modeja: laita .hmod-tiedostot horizon-mods-kansioon",
|
||||
"horizon.mods.stopped": " (Pysäytetty)",
|
||||
"horizon.mods.not_built": " (Ei rakennettu)",
|
||||
"horizon.mods.dev": " (Kehittäjä)",
|
||||
"horizon.mods.settings": "Asetukset",
|
||||
"horizon.mods.in_world": " (jätä maailma muuttumaan)",
|
||||
"horizon.mods.open_folder": "Avaa Mod-kansio",
|
||||
"horizon.shader_packs.empty": "Ei Shader-paketteja: laita ne Shaderpacks-kansioon",
|
||||
"horizon.shader_options.open": "Varjostimen asetukset...",
|
||||
"horizon.shader_options.profile": "Profiili",
|
||||
"horizon.shader_options.loading": "Shader-paketin vaihtoehtojen lukeminen...",
|
||||
"horizon.agent.copy_details": "Kopioi yhteystiedot",
|
||||
"horizon.multiplayer.join": "Liity",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Peruuta yhteyden muodostaminen",
|
||||
"horizon.multiplayer.join_friend": "Liity ystäväksi",
|
||||
"horizon.multiplayer.invite_code": "Kutsukoodi",
|
||||
"horizon.multiplayer.relay": "Rele (valinnainen)",
|
||||
"Preset": "Esiasetus",
|
||||
"LOD Distance": "LOD Etäisyys",
|
||||
"Far Detail": "Kaukainen yksityiskohta",
|
||||
"Chunk Updates": "Kappaleen päivitykset",
|
||||
"Upload Budget": "Lataa budjetti",
|
||||
"Fog": "Sumua",
|
||||
"Weather": "Sää",
|
||||
"Animated Textures": "Animoidut tekstuurit",
|
||||
"Present Mode": "Nykyinen tila",
|
||||
"Frame Pacing": "Kehyksen tahdistus",
|
||||
"Worker Threads": "Työntekijän langat",
|
||||
"Resolution Scale": "Resoluutioasteikko",
|
||||
"Upscaling": "Skaalaus",
|
||||
"Paper White": "Paperi Valkoinen",
|
||||
"Peak Brightness": "Huippukirkkaus",
|
||||
"Black Level": "Musta taso",
|
||||
"UI Brightness": "Käyttöliittymän kirkkaus",
|
||||
"Battery Mode": "Akkutila",
|
||||
"Graphics API": "Grafiikka API",
|
||||
"Translucent Batching": "Läpinäkyvä erä",
|
||||
"Performance Overlay": "Suorituskyvyn peittokuva",
|
||||
"Full Detail Distance": "Täydellinen etäisyys",
|
||||
"On": "Päällä",
|
||||
"Off": "Pois päältä",
|
||||
"Unlimited": "Rajoittamaton",
|
||||
"Custom": "Mukautettu",
|
||||
"Performance": "Suorituskyky",
|
||||
"Balanced": "Tasapainoinen",
|
||||
"Quality": "Laatu",
|
||||
"Ultra Performance": "Ultra suorituskyky",
|
||||
"Native": "Alkuperäinen",
|
||||
"Full": "Täysi",
|
||||
"Reduced": "Vähennetty",
|
||||
"Efficient": "Tehokas",
|
||||
"Precise": "Tarkkaa",
|
||||
"Auto (VSync)": "Automaattinen (VSync)",
|
||||
"Mailbox": "Postilaatikko",
|
||||
"Immediate": "Välitön",
|
||||
"FPS and Graph": "FPS ja Graph",
|
||||
"No cost": "Ei kuluja",
|
||||
"Low cost": "Alhaiset kustannukset",
|
||||
"Medium cost": "Keskihintainen",
|
||||
"High cost": "Korkea hinta",
|
||||
"Saves time": "Säästää aikaa",
|
||||
"Graphics": "Grafiikka",
|
||||
"Done": "Valmis",
|
||||
"This feature is not yet available in Horizon.": "Tämä ominaisuus ei ole vielä saatavilla Horizonissa.",
|
||||
"Your display or system doesn't offer HDR output.": "Näyttösi tai järjestelmäsi ei tarjoa HDR-lähtöä.",
|
||||
"Turn HDR on to use it.": "Ota HDR käyttöön käyttääksesi sitä.",
|
||||
"Your GPU or graphics API doesn't support it.": "GPU- tai grafiikkasovellusliittymäsi ei tue sitä.",
|
||||
"%s chunks": "%s paloja",
|
||||
"%s per frame": "%s kehystä kohti",
|
||||
"%s MiB/frame": "%s MiB/kehys",
|
||||
"%s KiB/frame": "%s KiB/kehys",
|
||||
"Auto (%s)": "Automaattinen (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Täysi yksityiskohtainen maastovalikoima. LOD Distance voi laajentaa paikallisia maailmoja pidemmälle.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Karkea etäinen maasto paikallisissa maailmoissa. Integroidut GPU:t pysähtyvät 128 palaan.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Vähentynyt piirtää paljon karkeampaa maastoa: nopeampi tällä integroidulla grafiikkasuorittimella.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Reduced piirtää paljon maastoa karkeammin, GPU:illa se hidastaa.",
|
||||
"Field of view. Wider shows more terrain at once.": "Näkökenttä. Leveämpi näyttää enemmän maastoa kerralla.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Caps ruutua sekunnissa; korkki säästää sähköä ja lämpöä.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Odottaa näytön päivitystä: ei repeytymistä, pieni latenssi.",
|
||||
"Size of menus and the HUD.": "Valikkojen koko ja HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Asettaa alla olevat vaihtoehdot yhdessä. Vaihtaminen tekee siitä mukautetun.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Laava, tuli ja auringonvalo loistavat kirkkaammin kuin valkoinen. Vaatii HDR-näytön.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Näyttösi on tummin musta. 0 OLEDille; LCD-näytöt hehkuvat hieman.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Kuinka kirkkaat kohokohdat saavat. Auto: huippu, jonka järjestelmäsi raportoi.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Miltä kirkkaan valkoinen paperi ja valikot näyttävät. Automaattinen: työpöytäsi on valkoinen.",
|
||||
"How bright the HUD and menus are, against paper white.": "Kuinka kirkkaita HUD ja valikot ovat paperinvalkoisia vastaan.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Kuinka pitkälle väkijoukot, viljat ja koneet toimivat. CPU-aika.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Kuinka kauas väkijoukot ja esineet piirretään, osuus normaalista.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Ohittaa suljettujen osien taakse piilotetun maaston. Poisto maksaa paljon.",
|
||||
"Hides entities behind opaque blocks.": "Piilottaa kokonaisuudet läpinäkymättömien lohkojen taakse.",
|
||||
"No entity occlusion culling.": "Ei kokonaisuuden tukkeutumista.",
|
||||
"CPU occlusion test runs this frame.": "CPU:n tukostesti suorittaa tämän kehyksen.",
|
||||
"Occlusion checks run on a worker thread.": "Tukostarkistukset suoritetaan työntekijäsäikeessä.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Verkkokohtaiset osiot ladattu kehystä kohti. Alempi on tasaisempi lastauksen aikana.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Pehmeät varjot kulmissa. Maksaa vain osan rakennusajasta.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Pienemmät kopiot lohkokuvioista kaukaiseen maastoon: vähemmän hohtoa.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Yhdistää ruohon ja veden värejä biomeihin rakentaessaan paloja.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Pilvikerros: pois päältä, tasainen (nopea) tai 3D (fancy).",
|
||||
"How far clouds reach, in chunks.": "Kuinka pitkälle pilvet ulottuvat paloina.",
|
||||
"Draws the sky, sun, moon and stars.": "Piirtää taivaan, auringon, kuun ja tähdet.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Etäisyys sumua. Off näyttää ladatun maailman reunan.",
|
||||
"Draws rain and snow.": "Piirtää sadetta ja lunta.",
|
||||
"How far rain and snow fall around you.": "Kuinka pitkälle sataa ja sataa lunta ympärilläsi.",
|
||||
"How many particles effects spawn.": "Kuinka monta hiukkasvaikutusta syntyy.",
|
||||
"Round shadows under mobs and items.": "Pyöreät varjot väkijoukon ja esineiden alla.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Lehdet läpinäkyvillä rakoilla. Off vetää ne kiinteästi.",
|
||||
"Sorts water and glass more exactly behind each other.": "Lajittelee veden ja lasin tarkemmin toistensa taakse.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Mitkä palaset odottavat kehystä: ei yhtään, lähellä vai sinun.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animoi vettä, laavaa, tulta ja muita liikkuvia tekstuureja.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Maailman pikseleitä näytön pikseliä kohti. Alle 100 % on nopeampi GPU:ssa.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Tasoittaa rosoiset reunat. FXAA: pehmeämpi. TAA: Tasaisimmat, liikkuvat asiat voivat seurata.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Pienentää maailmaa ja rakentaa sen uudelleen TAA:lla. Nopeampi, pehmeämpi.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR-lähtö HDR-näytöllä: laava, tuli ja auringonvalo loistavat kirkkaammin.",
|
||||
"How frames reach the screen while VSync is off.": "Kuinka kehykset saavuttavat näytön, kun VSync on pois päältä.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Tehokkaat unet seuraavaan ruutuun asti; Tarkat pyöräytykset, kuten vanilja.",
|
||||
"Fills the screen (borderless).": "Täyttää näytön (reunaton).",
|
||||
"Brightens dark places.": "Kirkastaa pimeitä paikkoja.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Alentaa kuvataajuutta minimoituna tai myös tyhjäkäynnillä.",
|
||||
"Sways the view while walking.": "Heiluttaa näkymää kävellessä.",
|
||||
"Fades newly loaded chunks in.": "Häivyttää juuri ladatut palat sisään.",
|
||||
"Darkens the screen's edges.": "Tummentaa näytön reunoja.",
|
||||
"Shows when the world saves.": "Näyttää, milloin maailma pelastaa.",
|
||||
"Shows attack cooldown.": "Näyttää hyökkäyksen jäähtymisen.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS cap (30 valikoissa tai tarkentamattomina), puolet työntekijöistä: vähemmän tehoa.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Langat, jotka luovat, keventävät ja yhdistävät paloja. Seuraava aloitus.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, sitten DirectX 12 (Windows) tai OpenGL (Linux), jos se epäonnistuu. Seuraava aloitus.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Vesi ja lasi samassa puskurissa: vähemmän piirtokutsuja, hieman enemmän VRAM-muistia. Seuraava aloitus.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Grafiikkasuorittimelle lähetetty mesh-data kehystä kohden. Alempi on tasaisempi lastauksen aikana.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Kuvataajuus ja kuvaajat pelin aikana kaaviolla.",
|
||||
"horizon.multiplayer.finding_friend": "Ystäväsi maailman löytäminen...",
|
||||
"horizon.multiplayer.not_signed_in": "Tämä palvelin on online-tilassa. Kirjaudu sisään Microsoft-tililläsi Horizon-käynnistysohjelmassa liittyäksesi siihen.",
|
||||
"horizon.multiplayer.invalid_address": "Virheellinen palvelimen osoite: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Yhdistetty suoraan ystäväsi...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Yhdistetty ystäväisi releen kautta...",
|
||||
"General": "Kenraali",
|
||||
"Advanced": "Edistynyt",
|
||||
"Undo": "Kumoa",
|
||||
"Search options": "Hakuvaihtoehdot",
|
||||
"Shortcut": "Pikakuvake",
|
||||
"Distance": "Etäisyys",
|
||||
"Display": "Näyttö",
|
||||
"Interface": "Käyttöliittymä",
|
||||
"Textures": "Tekstuurit",
|
||||
"Image": "Kuva",
|
||||
"Power": "Tehoa",
|
||||
"Frames": "Kehykset",
|
||||
"Output": "Lähtö",
|
||||
"Brightness": "Kirkkaus",
|
||||
"How far you see, the window and the HUD.": "Kuinka kauas näet, ikkuna ja HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Taivas ja sää, valaistus, tekstuurit ja kuva.",
|
||||
"Chunk loading, culling and power use.": "Palalataus, teurastus ja tehonkäyttö.",
|
||||
"How frames reach the screen, and the performance overlay.": "Kuinka kehykset saavuttavat näytön ja suorituskyvyn peittokuva.",
|
||||
"Brighter-than-white output on an HDR display.": "Valkoista kirkkaampi lähtö HDR-näytöllä.",
|
||||
"horizon.title.not_affiliated": "Ei sidoksissa Mojangiin tai Microsoftiin",
|
||||
"horizon.extras.header.third_person": "Kolmas henkilö",
|
||||
"horizon.extras.header.particles": "Hiukkaset",
|
||||
"horizon.extras.header.inventory": "Varasto",
|
||||
"horizon.extras.header.multiplayer": "Moninpeli",
|
||||
"horizon.extras.header.sound": "Ääni",
|
||||
"horizon.extras.header.menus": "Valikot",
|
||||
"horizon.extras.dynamic_lights": "Dynaamiset valot",
|
||||
"horizon.extras.own_name_tag": "Oma nimilappu",
|
||||
"horizon.extras.hide_block_particles": "Piilota lohkon rikkoutuvat hiukkaset",
|
||||
"horizon.extras.auto_reconnect": "Yhdistä automaattisesti",
|
||||
"horizon.extras.ui_sound_reserve": "Säilytä kanavat käyttöliittymäääniä varten",
|
||||
"horizon.extras.music_page": "Musiikki sivu",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks -ohjaimet",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Vierityspyörä siirtää kohteita",
|
||||
"horizon.extras.sort": "Lajittelupainikkeet",
|
||||
"horizon.extras.sort_key": "Lajitteluavain",
|
||||
"horizon.extras.no_ai": "Ei AI",
|
||||
"horizon.extras.agent_control": "Agentin hallinta",
|
||||
"horizon.extras.splitscreen": "Jaettu näyttö",
|
||||
"horizon.extras.chat_animation": "Sujuva chat",
|
||||
"horizon.extras.chat_indicator": "Viestin ilmaisin",
|
||||
"horizon.extras.wheel_cycles_settings": "Vierityspyörä muuttaa asetuksia",
|
||||
"horizon.extras.pack_browser": "Resurssipaketti selain",
|
||||
"horizon.extras.fullbright.tooltip": "Näytä lohkot ja kokonaisuudet täydessä valaistuksessa, jopa tummilla alueilla.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Taskulamput ja muut valonlähteet pitivät, putosivat tai syttyivät tuleen, sytyttivät ympärillään olevat lohkot.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Näyttää nimesi pään yläpuolella kolmannessa persoonassa, kuten muut pelaajat näkevät sen. Piilotettu, kun palvelin piilottaa tiimisi nimet tai olet näkymätön.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Piilottaa roskapartikkelit, jotka räjähtävät rikkoutuneesta lohkosta ja murenevat irti siitä, jonka loit. Muut hiukkaset jäävät.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Liittyy uudelleen palvelimeen yhteyden katkeamisen tai uudelleenkäynnistyksen jälkeen, ei koskaan potkaisun tai kiellon jälkeen.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Äänet maailmassa jättävät 16 247 äänikanavasta vapaaksi, joten niiden tulva (iso räjähdys) ei voi hiljentää valikon napsautuksia.",
|
||||
"horizon.extras.music_page.tooltip": "Music & Sounds saa Musiikki... -sivun: toista mitä tahansa sisäänrakennettua raitaa nyt tai sammuta se, jotta peli ei enää valitse sitä. Mikään ei muutu ennen kuin sammutat kappaleen.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Oikeanpuoleinen vetäminen asettaa yhden per paikka, vasen vetäminen kokoaa pidetyn kohteen, Vaihto + vasen vetäminen siirtää jokaista paikkaa.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Alas lähettää yhden esineen korttipaikasta toiselle puolelle, ylös tuo yhden takaisin.",
|
||||
"horizon.extras.sort.tooltip": "Lajittelee varaston, arkut, tynnyrit, ender-arkut ja shulker-laatikot luovaan välilehtijärjestykseen yhdistäen pinot.",
|
||||
"horizon.extras.sort_key.tooltip": "Lajittelee kohdistimen alla olevan osan. Napsauta ja paina sitten näppäintä; Pakeneminen vapauttaa sen.",
|
||||
"horizon.extras.no_ai.tooltip": "Piilottaa ja sammuttaa kaikki tekoälyominaisuudet: agentin ohjauksen, horizon-ctl:n ja MCP-palvelimet. Myös kantoraketin kytkin.",
|
||||
"horizon.extras.agent_control.tooltip": "Oma tekoälyagentti voi pelata tätä peliä horizon-ctl- tai MCP-asiakassovelluksen kautta vain tällä tietokoneella.",
|
||||
"horizon.extras.discord_presence.tooltip": "Näyttää Playing Horizonin Discord-profiilissasi, kun Discord on käynnissä: valikot, yksin- tai moninpeli, ulottuvuus ja peliaika. Ei koskaan maailman nimeä tai palvelimen osoitetta.",
|
||||
"Calibrate...": "Kalibroi...",
|
||||
"Calibrate Anyway...": "Kalibroi joka tapauksessa...",
|
||||
"Use Automatic Setup": "Käytä automaattista asetusta",
|
||||
"Shortcut: %s": "Pikanäppäin: %s",
|
||||
"Next": "Seuraavaksi",
|
||||
"HDR Calibration %s/%s: %s": "HDR-kalibrointi %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Asettaa kolme koko näytön alapuolelle yhden testikuvan kerrallaan.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Avain, joka avaa tämän näytön pelistä. Ei aluksi sidottu.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Näyttösi ei ilmoittanut, kuinka kirkkaaksi se tulee: kalibroi se määrittääksesi sen.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Näyttöäsi varten on jo määritetty. Kalibroi vain, jos HDR näyttää väärältä.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Palauttaa huippukirkkauden ja paperin valkoisen näytön asetuksiin.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Määritä näyttöäsi varten. Jätä automaattitilaan, ellei se näytä väärältä.",
|
||||
"Lower it until the logo just disappears into the black.": "Laske sitä, kunnes logo vain katoaa mustaksi.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Nosta sitä, kunnes logo vain katoaa kirkkaaseen neliöön.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Tee kortista valkoinen, jota on mukava katsella pitkään pelissä pelaamassasi valossa.",
|
||||
"horizon.splitscreen.profiles.forget": "Unohda",
|
||||
"horizon.splitscreen.profiles.create": "Luo",
|
||||
"horizon.splitscreen.profiles.you": " - sinä",
|
||||
"horizon.splitscreen.profiles.forget_named": "Unohda %s",
|
||||
"horizon.splitscreen.profiles.previous": "Edellinen",
|
||||
"horizon.splitscreen.profiles.next": "Seuraavaksi",
|
||||
"horizon.splitscreen.profiles.new_button": "Uusi profiili...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Unohditko profiilin?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Se poistuu tämän esiintymän luettelosta.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Maailman edistyminen ja asetukset säilyvät.",
|
||||
"horizon.splitscreen.profiles.new_title": "Uusi profiili",
|
||||
"horizon.splitscreen.profiles.name_hint": "Nimi (3-16 kirjainta, numeroa, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Vaihda profiilia",
|
||||
"horizon.splitscreen.profiles.offline_note": "Offline-profiilit kuuluvat tähän esiintymään",
|
||||
"horizon.splitscreen.profiles.move": "Muuta missä pelaat",
|
||||
"horizon.splitscreen.profiles.move_place": "Siirry paikkaan, jossa pelaat: %s",
|
||||
"horizon.friends.title": "Pelaa ystävien kanssa",
|
||||
"horizon.friends.hint": "Ystävät liittävät koodin otsikkonäytön Ystävät-painikkeen perään, Suoraan yhteyteen tai käynnistysohjelman Play-sivulle.",
|
||||
"horizon.friends.copied": "Kopioitu",
|
||||
"horizon.friends.copy_code": "Kopioi koodi",
|
||||
"horizon.friends.stop_sharing": "Lopeta jakaminen",
|
||||
"horizon.friends.none_yet": "Täällä ei ole vielä ystäviä.",
|
||||
"horizon.friends.here_now": "Täällä nyt: %s",
|
||||
"horizon.friends.share_intro": "Jaa tämä maailma kutsukoodin takana: se avautuu lähiverkkoon, ja ystävät Internetissä liittyvät koodin avulla. Yksikään palvelin ei ole mukana.",
|
||||
"horizon.friends.start_sharing": "Aloita jakaminen",
|
||||
"horizon.friends.troubleshooting": "Vianmääritys...",
|
||||
"horizon.friends.stopped": "Kutsukoodin jakaminen lopetettiin.",
|
||||
"horizon.friends.short_code_changed": "Joku yritti jatkuvasti käyttää väärää lyhytkoodia, joten se muuttui.",
|
||||
"horizon.friends.wrong_code": "Joku yritti väärää koodia.",
|
||||
"horizon.friends.code_ready": "Kutsukoodisi on valmis: Pelivalikko, Pelaa ystävien kanssa. Kopioi se ja lähetä se ystävillesi.",
|
||||
"horizon.friends.share_failed": "Ei voinut jakaa maailmaa: %s",
|
||||
"horizon.friends.finding_route": "Selvitetään, kuinka ystävät voivat tavoittaa sinut…",
|
||||
"horizon.osk.shift": "Vaihto",
|
||||
"horizon.osk.space": "Avaruus",
|
||||
"horizon.osk.delete": "Poista",
|
||||
"horizon.osk.type": "Kirjoita",
|
||||
"horizon.osk.symbols": "Symbolit",
|
||||
"horizon.osk.close": "Sulje",
|
||||
"horizon.extras.chat_animation.tooltip": "Uusi chat-linja liukuu sisään vasemmalta sen häipyessä sen sijaan, että se näyttäisi kokonaisena.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Värillinen palkki keskustelupalkin vasemmalla puolella.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Asetuksissa painikkeen päällä oleva vierityspyörä muuttaa sen arvoa, kuten vaniljassa. Pois päältä, pyörä vain vierittää luetteloa.",
|
||||
"horizon.extras.pack_browser.tooltip": "Valitse Resurssipaketit saa Lisää-painikkeen, joka etsii Modrinthista ja asentaa paketin pakettikansioosi. Pois päältä, peli ei koskaan saa yhteyttä Modrinthiin.",
|
||||
"horizon.extras.splitscreen.tooltip": "Maailmassa toinen ohjain voi liittyä uutena pelaajana: paina A, kun se pyytää, ja valitse sitten profiili ja pelipaikka.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitori %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitori",
|
||||
"horizon.splitscreen.setup.window": "Ikkuna %s",
|
||||
"horizon.splitscreen.setup.new_window": "Uusi ikkuna",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Täytä %s",
|
||||
"horizon.splitscreen.setup.done": "Valmis",
|
||||
"horizon.splitscreen.setup.on": "Päällä",
|
||||
"horizon.splitscreen.setup.off": "Pois päältä",
|
||||
"horizon.splitscreen.setup.player": "Pelaaja %s",
|
||||
"horizon.splitscreen.setup.revert": "Palauta",
|
||||
"horizon.splitscreen.setup.apply": "Käytä",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Kukaan ei pelaa ikkunassa 1: se piiloutuu",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Kukaan ei pelaa ikkunassa %s: se sulkeutuu Applyssa",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Kukaan ei pelaa ikkunassa %s: se ei avaudu",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Ikkuna %s täyttää Window 1:n näytön: sen pelaajat pelaavat ikkunassa 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Ikkunan %s näyttöä ei ole kytketty: sen pelaajat pelaavat ikkunassa 1",
|
||||
"horizon.splitscreen.setup.status.off": "Jaettu näyttö on pois päältä.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Kytke se päälle ja paina sitten painiketta toisessa ohjaimessa maailmassa.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Liity ja valitse profiili painamalla A.",
|
||||
"horizon.splitscreen.setup.status.idle": "Jaettu näyttö tyhjäkäynnillä",
|
||||
"horizon.splitscreen.setup.status.alone": "Ei muita paikallisia pelaajia.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Yksittäiset säätimet ja ohjaimen palaute ovat ennallaan.",
|
||||
"horizon.splitscreen.setup.window_empty": "Ikkuna %s: tyhjä",
|
||||
"horizon.splitscreen.setup.note.select": "Napsauta soitinta tai vedä se ikkunaan tai näyttöön.",
|
||||
"horizon.splitscreen.setup.note.apply": "Käytä pelataksesi tällä tavalla, Palauta kumoaksesi.",
|
||||
"horizon.splitscreen.setup.note.place": "Missä %s pitäisi pelata?",
|
||||
"horizon.splitscreen.setup.this_player": "tämä pelaaja",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Jaettu näyttö sammuu, kun muut ovat lähteneet (heidän taukovalikkonsa).",
|
||||
"horizon.splitscreen.setup.window_place": "Ikkuna %s (%s/%s)",
|
||||
"horizon.splitscreen.setup.names": "Nimet",
|
||||
"horizon.splitscreen.setup.identify": "Tunnista",
|
||||
"horizon.splitscreen.setup.layout.two": "2 jakoa: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 jakoa: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Vierekkäin",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Ylä ja ala",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Yksi leveä ylhäältä",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Yksi korkea vasemmalla",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Vierekkäin",
|
||||
"horizon.splitscreen.setup.help.player": "Siirrä ne napsauttamalla tai vetämällä %s.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (vieressä, kun ikkuna on leveä). Paina muuttaaksesi.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Paina muuttaaksesi.",
|
||||
"horizon.splitscreen.setup.help.switch": "Jaettu näyttö tälle tietokoneelle, päälle tai pois päältä.",
|
||||
"horizon.splitscreen.setup.help.identify": "Jokaisen pelaajan näkymä näyttää, missä ikkunassa se on.",
|
||||
"horizon.splitscreen.setup.help.names": "Jokaisen pelaajan nimi näkymän kulmassa.",
|
||||
"horizon.splitscreen.setup.help.revert": "Kumoa muutokset, joita ei ole vielä otettu käyttöön.",
|
||||
"horizon.splitscreen.setup.help.apply": "Pelaa niin kuin kartta näyttää.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Mikään ei ole vielä muuttunut.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Luovu Padista",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Ohjain kytketty",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Ohjain irrotettu",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Liitä se uudelleen tai paina toisen painiketta",
|
||||
"horizon.splitscreen.prompt.join.one": "%s haluaa liittyä",
|
||||
"horizon.splitscreen.prompt.join.many": "%s ohjaajat haluavat liittyä",
|
||||
"horizon.splitscreen.prompt.join.yes": "Liity",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Pelaa nimellä P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Ei",
|
||||
"horizon.splitscreen.prompt.lost": "%s: ohjain irrotettu",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Käytä toista",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Lähde",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Ohjaimesi lopettaa ajamisen ja sinulle tarjotaan liittymistä (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s - %s: A pudottaa sen tähän, B laittaa sen takaisin.",
|
||||
"horizon.splitscreen.setup.note.held": "%s poimii: vasen/oikea valitsee minne, A pudottaa, B laittaa takaisin.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Piilottaa mallit läpinäkymättömien lohkojen taakse. Async tarkistaa säiettä; entiteetit tikittää.",
|
||||
"Block Entities": "Estä entiteetit",
|
||||
"Shulker Boxes": "Shulker laatikot",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Käyttää uudelleen laskeutuneita laatikoita; kannen liike pysyy animoituna.",
|
||||
"Signs": "Merkkejä",
|
||||
"Reuses sign text until text, light, or font changes.": "Käyttää merkkitekstiä uudelleen, kunnes teksti, valo tai fontti muuttuvat.",
|
||||
"Decorated Pots": "Koristetut ruukut",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Uudelleen käyttää still-ruukkuja; katkaise huojunnan ja särmän muutokset piirtyvät uudelleen.",
|
||||
"Bells": "Kellot",
|
||||
"Reuses still bells; ringing stays animated.": "Käyttää uudelleen still-kelloja; soittoääni pysyy animoituna.",
|
||||
"Chests": "Arkut",
|
||||
"Reuses closed chests; moving lids stay animated.": "Käyttää uudelleen suljettuja arkkuja; liikkuvat kannet pysyvät animoituina.",
|
||||
"Banners": "Bannerit",
|
||||
"Reuses poles; flags and pattern layers still update.": "Käyttää uudelleen pylväitä; liput ja kuviotasot päivittyvät edelleen.",
|
||||
"Copper Golem Statues": "Kupariset Golem-patsaat",
|
||||
"Reuses statue geometry until its block state changes.": "Käyttää patsaan geometriaa uudelleen, kunnes sen lohkotila muuttuu.",
|
||||
"Leaf Culling": "Lehtien teurastus",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Jokaisen lehden kasvot. Vaniljan upeat lehdet.",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Pitää hienot kasvit korkearesoluutioisissa pakkauksissa haalistumasta kauas.",
|
||||
"Screenshot Quality": "Kuvakaappauksen laatu",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2-kuvakaappauksia tällä monta kertaa pelin resoluutiolla. Korkeampi kestää kauemmin.",
|
||||
"Frame Generation": "Kehyksen sukupolvi",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Näyttää sovitetun kehyksen kahden todellisen kehyksen välillä: tasaisempi, hieman enemmän viivettä.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Käyttää uudelleen pakkausvarjokartan, kun aurinko, näkymä ja maailma kohtaavat. Oletuksena pois päältä.",
|
||||
"Find BlueMap": "Etsi BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Palvelimilla löytää BlueMap-verkkokartan kaukaiselle maastolle. Liity seuraavaksi.",
|
||||
"BlueMap Radius": "BlueMap-säde",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Kuinka pitkälle ympärilläsi BlueMap-laattoja ladataan. Liity seuraavaksi.",
|
||||
"Synchronous": "Synkroninen",
|
||||
"Asynchronous": "Asynkroninen",
|
||||
"Solid": "Kiinteä",
|
||||
"Solid Aggressive": "Kiinteä Aggressiivinen",
|
||||
"Hollow": "Ontto",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Lehdet kokonaan katoksen sisällä vetävät kiinteästi: vähemmän läpinäkyviä kerroksia.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Lehdet, joissa on yksi avoin puoli, ovat myös kiinteät: nopeammat, paksummat reunat.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Pudottaa kasvot lehtien väliin: nopeammin, mutta aukot näyttävät läpi.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Edellinen paikka",
|
||||
"Hotbar: Next Slot": "Hotbar: Seuraava paikka",
|
||||
"horizon.agent.connected.one": "Yhdistetty: 1 agenttiyhteys",
|
||||
"horizon.agent.connected.many": "Yhdistetty: %s agenttiyhteydet",
|
||||
"horizon.agent.connected.one_ago": "Yhdistetty: 1 agenttiyhteys, viimeisin komento %s s sitten",
|
||||
"horizon.agent.connected.many_ago": "Yhdistetty: %s agenttiyhteydet, viimeisin komento %s s sitten",
|
||||
"horizon.agent.waiting": "Agenttia odotellessa.",
|
||||
"horizon.agent.off": "Pois päältä: agentit eivät tavoita tätä peliä.",
|
||||
"horizon.agent.session_opened": "Pelin aloittaneen ohjelman avaa tälle istunnolle.",
|
||||
"horizon.agent.socket": "Socket: %s (kopioi sen koko polun tiedot)",
|
||||
"horizon.agent.local_only": "Vain paikalliset: agentit tällä tietokoneella, ei koskaan verkko.",
|
||||
"Auto (%s nits)": "Automaattinen (%s nits)",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Piilottaa mallit läpinäkymättömien lohkojen taakse. Kokonaisuudet päivitys. Pois mukautetuilla pakkauksilla.",
|
||||
"horizon.extras.repro_keys": "Tallennuksen pikakuvakkeet",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R aloittaa tai lopettaa repronauhoituksen ja F3+E kirjoittaa sen nipun. Oletuksena pois päältä: tallenne kopioi maailmaa.",
|
||||
"horizon.agent.record_session": "Aloita tallennusistunto",
|
||||
"horizon.agent.record_stop": "Lopeta tallennusistunto",
|
||||
"horizon.agent.export_repro": "Vie Repro Bundle",
|
||||
"horizon.repro.started": "Tallennus aloitettu; F3+R pysäyttää sen ja kirjoittaa nipun.",
|
||||
"horizon.repro.stopped": "Tallennus pysäytetty: %s",
|
||||
"horizon.repro.bundle": "Repro-paketti: %s",
|
||||
"horizon.repro.no_recording": "Vietävää tallennetta ei vielä ole.",
|
||||
"horizon.repro.replay_started": "Toisto aloitettu: %s",
|
||||
"horizon.pack.addInfo": "Lisää resurssipaketti Modrinthista",
|
||||
"horizon.pack.addWorking": "Modrinthilta kysytään...",
|
||||
"horizon.pack.addNone": "Resurssipaketteja ei löytynyt",
|
||||
"horizon.pack.addTitle": "Lisää resurssipaketti",
|
||||
"horizon.pack.install": "Asenna",
|
||||
"horizon.pack.installing": "Asennus",
|
||||
"horizon.pack.installed": "Asennettu",
|
||||
"horizon.pack.search": "Etsi",
|
||||
"horizon.pack.searchHint": "Etsi Modrinth...",
|
||||
"horizon.pack.filters": "Suodattimet",
|
||||
"horizon.pack.filter.realistic": "Realistinen",
|
||||
"horizon.pack.filter.simplistic": "Yksinkertaista",
|
||||
"horizon.pack.filter.vanillaLike": "Vaniljamainen",
|
||||
"horizon.pack.addFailed": "Pakettia ei voitu lisätä: %s",
|
||||
"horizon.extras.header.cosmetics": "Kosmetiikka",
|
||||
"horizon.extras.cosmetics_mine": "Minun kosmetiikkani",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Piilotettu: jokainen Horizon-pelaaja näkee sinut ilman kosmetiikkaasi, sinä mukaan lukien. Sama kytkin kuin kantoraketin vaatekaappi.",
|
||||
"horizon.extras.cosmetics_others": "Muiden pelaajien kosmetiikkaa",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Mitä muut Horizon-pelaajat käyttävät: kaikki, vain heidän nimilaputyylinsä tai ei mitään. Piilotettu, peli ei koskaan kysy, mitä he käyttävät.",
|
||||
"horizon.extras.cosmetics_auras": "Toisten aurat ja polut",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aurat ja jäljet, joita muut pelaajat käyttävät. Oletuksena pois päältä.",
|
||||
"horizon.extras.cosmetics.shown": "Näytetään",
|
||||
"horizon.extras.cosmetics.hidden": "Piilotettu",
|
||||
"horizon.extras.cosmetics.name_tags": "Vain nimilaput"
|
||||
}
|
||||
@@ -321,6 +321,9 @@
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Votre manette ne vous contrôle plus et peut rejoindre la partie (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s vers %s : A le dépose ici, B le remet en place.",
|
||||
"horizon.splitscreen.setup.note.held": "%s est pris : gauche/droite choisit où, A dépose, B remet en place.",
|
||||
"horizon.splitscreen.online_only": "Ce serveur est en mode en ligne : les profils hors ligne ne peuvent pas le rejoindre, chaque joueur a besoin de son propre compte Minecraft.",
|
||||
"horizon.splitscreen.own_account": "Ce serveur demande un compte Minecraft par joueur",
|
||||
"horizon.splitscreen.local_only": "Les autres joueurs ne peuvent rejoindre ce serveur qu'avec leur propre compte Minecraft : les profils hors ligne jouent dans votre monde, en LAN ou sur un serveur de votre réseau local.",
|
||||
"Culling": "Occlusion",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Masque les modèles derrière les blocs opaques. Vérification en arrière-plan ; les entités continuent.",
|
||||
"Leaf Culling": "Occlusion feuillage",
|
||||
@@ -367,6 +370,10 @@
|
||||
"horizon.extras.splitscreen.tooltip": "Dans un monde, une autre manette peut rejoindre en tant que nouveau joueur : appuyez sur A quand c'est demandé, puis choisissez un profil et où jouer.",
|
||||
"horizon.extras.ui_sound_reserve": "Canaux pour sons UI",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Les sons du monde laissent 16 des 247 canaux sonores libres, pour qu'un déluge de sons (une grosse explosion) ne couvre pas les clics de menu.",
|
||||
"Particle Culling": "Occlusion des particules",
|
||||
"Draws every particle.": "Dessine toutes les particules.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.": "Ignore les particules hors écran, trop petites ou masquées par le terrain.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.": "Ignore les particules hors écran, trop petites ou masquées par le terrain avec moins de travail sur le thread de rendu.",
|
||||
"horizon.extras.header.cosmetics": "Cosmétiques",
|
||||
"horizon.extras.cosmetics_mine": "Mes cosmétiques",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Masqués : tous les joueurs Horizon vous voient sans vos cosmétiques, vous compris. Le même réglage que la garde-robe du launcher.",
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
{
|
||||
"horizon.options.controller": "בקר",
|
||||
"horizon.options.vibration": "רטט",
|
||||
"horizon.options.adaptive_triggers": "טריגרים אדפטיביים",
|
||||
"horizon.options.lightbar": "סרגל תאורה",
|
||||
"horizon.options.look_sensitivity": "רגישות המבט",
|
||||
"horizon.options.invert_x": "היפוך ציר X של המבט",
|
||||
"horizon.options.invert_y": "היפוך ציר Y של המבט",
|
||||
"horizon.options.sneak_button": "כפתור התגנבות",
|
||||
"horizon.options.move_deadzone": "אזור מת של התנועה",
|
||||
"horizon.options.look_deadzone": "אזור מת של המבט",
|
||||
"horizon.options.button_glyphs": "גליפים של כפתורים",
|
||||
"horizon.options.menu_navigation": "ניווט בתפריט",
|
||||
"horizon.options.on_screen_keyboard": "מקלדת על המסך",
|
||||
"horizon.options.chat_delay": "עיכוב בצ'אט",
|
||||
"horizon.options.lightbar.health": "בריאות",
|
||||
"horizon.options.lightbar.dimension": "מימד",
|
||||
"horizon.options.glyphs.auto": "אוטומטי",
|
||||
"horizon.options.menu_focus": "פוקוס",
|
||||
"horizon.options.menu_pointer": "מצביע",
|
||||
"horizon.options.keyboard.auto": "אוטומטי",
|
||||
"horizon.options.keyboard.steam": "קיטור",
|
||||
"horizon.options.unavailable": "תכונה זו עדיין לא זמינה ב-Horizon.",
|
||||
"horizon.options.search": "הגדרות חיפוש...",
|
||||
"horizon.pages.mods": "מודים",
|
||||
"horizon.pages.shader_packs": "חבילות Shader",
|
||||
"horizon.pages.shader_options": "אפשרויות הצללה",
|
||||
"horizon.pages.controller": "הגדרות הבקר",
|
||||
"horizon.pages.controller_binds": "הבקר מחייב",
|
||||
"horizon.pages.agent": "סוכן AI",
|
||||
"horizon.split.leave": "עזוב",
|
||||
"horizon.pages.split_settings": "הגדרות מסך מפוצל",
|
||||
"horizon.pages.split_profiles": "מי משחק?",
|
||||
"horizon.pages.split_setup": "הגדרת מסך מפוצל",
|
||||
"horizon.pages.music": "מוזיקה",
|
||||
"horizon.pages.music.menu": "תפריט",
|
||||
"horizon.pages.music.overworld": "עולם על",
|
||||
"horizon.pages.music.creative": "יצירתי",
|
||||
"horizon.pages.music.underwater": "מתחת למים",
|
||||
"horizon.pages.music.end": "הסוף",
|
||||
"horizon.pages.music.boss": "בוס",
|
||||
"horizon.pages.music.credits": "קרדיטים",
|
||||
"horizon.pages.music.overworld_biomes": "ביומות בעולם העל",
|
||||
"horizon.pages.music.nether_biomes": "ביומות תחתיות",
|
||||
"horizon.pages.music.end_biomes": "קץ לביומות",
|
||||
"horizon.pages.music.other": "מוזיקה אחרת",
|
||||
"horizon.pages.music.empty": "אין רצועות מוזיקה: צלילי המשחק אינם נטענים.",
|
||||
"horizon.pages.music.tooltip": "הרצועות המובנות, לפי סיטואציה: שחק אחת עכשיו, או כבה רצועה כלשהי כדי שהמשחק יפסיק לבחור בה.",
|
||||
"horizon.pages.settings.tooltip": "ההגדרות של Horizon עצמו: גרפיקה, בקרים, תוספות ומודים.",
|
||||
"horizon.pages.graphics.tooltip": "גרפיקה, פשוט: כל הגדרה אומרת מה היא עולה.",
|
||||
"horizon.pages.controller.tooltip": "בקרים: הכפתורים, המקלות, הרטט וניווט התפריטים שלהם.",
|
||||
"horizon.pages.extras.tooltip": "תוספות אופציונליות, כולן כבויות כברירת מחדל: המשחק נשאר וניל עד שתדליק אחד.",
|
||||
"horizon.pages.agent.tooltip": "תן לסוכן AI שלך לשחק: לקוחות horizon-ctl ו-MCP במחשב הזה.",
|
||||
"horizon.pages.split_profiles.tooltip": "מי משחק: פרופיל שמור בתיקיית המשחק הזו.",
|
||||
"horizon.pages.split_setup.tooltip": "החלונות והמוניטורים, ומי משחק איפה.",
|
||||
"horizon.pages.shader_packs.tooltip": "חבילות Shader מתיקיית shaderpacks: משל Horizon ואלה בפורמט Shaders/. אחד רץ בכל פעם.",
|
||||
"horizon.pages.mods.tooltip": "מודים שנוצרו עבור Horizon, מתיקיית horizon-mods. כל אחד פועל בארגז חול ונשאר כבוי עד שאתה מפעיל אותו.",
|
||||
"horizon.pages.open.graphics": "גרפיקה...",
|
||||
"horizon.pages.open.graphics_advanced": "גרפיקה מתקדמת...",
|
||||
"horizon.pages.open.shader_packs": "חבילות Shader...",
|
||||
"horizon.pages.open.controller": "הגדרות בקר...",
|
||||
"horizon.pages.open.split_setup": "מסך מפוצל...",
|
||||
"horizon.pages.open.extras": "תוספות...",
|
||||
"horizon.pages.open.mods": "מודים...",
|
||||
"horizon.pages.open.agent": "סוכן AI...",
|
||||
"horizon.pages.open.controller_binds": "הבקר מקשר...",
|
||||
"horizon.pages.open.music": "מוזיקה...",
|
||||
"horizon.mods.empty": "ללא אופנים: שים קבצי .hmod בתיקייה horizon-mods",
|
||||
"horizon.mods.stopped": " (נעצר)",
|
||||
"horizon.mods.not_built": " (לא בנוי)",
|
||||
"horizon.mods.dev": " (מפתח)",
|
||||
"horizon.mods.settings": "הגדרות",
|
||||
"horizon.mods.in_world": " (עזוב את העולם להשתנות)",
|
||||
"horizon.mods.open_folder": "פתח את תיקיית Mod",
|
||||
"horizon.shader_packs.empty": "ללא חבילות shader: הכניסו אותן לתיקיית shaderpacks",
|
||||
"horizon.shader_options.open": "אפשרויות הצללה...",
|
||||
"horizon.shader_options.profile": "פרופיל",
|
||||
"horizon.shader_options.loading": "קורא את האפשרויות של חבילת הצללה...",
|
||||
"horizon.agent.copy_details": "העתק את פרטי החיבור",
|
||||
"horizon.multiplayer.join": "הצטרף",
|
||||
"horizon.multiplayer.cancel_reconnecting": "בטל חיבור מחדש",
|
||||
"horizon.multiplayer.join_friend": "הצטרף לחבר",
|
||||
"horizon.multiplayer.invite_code": "קוד הזמנה",
|
||||
"horizon.multiplayer.relay": "ממסר (אופציונלי)",
|
||||
"Preset": "מוגדר מראש",
|
||||
"LOD Distance": "מרחק LOD",
|
||||
"Far Detail": "פרט רחוק",
|
||||
"Cave Culling": "חיתוך מערות",
|
||||
"Entity Culling": "חיתוך ישות",
|
||||
"Chunk Updates": "עדכוני נתח",
|
||||
"Upload Budget": "העלה תקציב",
|
||||
"Sky": "שמיים",
|
||||
"Fog": "ערפל",
|
||||
"Weather": "מזג אוויר",
|
||||
"Animated Textures": "טקסטורות מונפשות",
|
||||
"Present Mode": "מצב הווה",
|
||||
"Frame Pacing": "קצב מסגרת",
|
||||
"Worker Threads": "חוטי עובדים",
|
||||
"Resolution Scale": "סולם רזולוציה",
|
||||
"Anti-aliasing": "אנטי-aliasing",
|
||||
"Upscaling": "העלאת קנה מידה",
|
||||
"Paper White": "נייר לבן",
|
||||
"Peak Brightness": "בהירות שיא",
|
||||
"Black Level": "רמה שחורה",
|
||||
"UI Brightness": "בהירות ממשק משתמש",
|
||||
"Battery Mode": "מצב סוללה",
|
||||
"Graphics API": "גרפיקה API",
|
||||
"Translucent Batching": "אצווה שקוף",
|
||||
"Performance Overlay": "שכבת ביצועים",
|
||||
"Full Detail Distance": "מרחק פירוט מלא",
|
||||
"On": "פועל",
|
||||
"Off": "כבוי",
|
||||
"Unlimited": "ללא הגבלה",
|
||||
"Auto": "אוטומטי",
|
||||
"Custom": "מותאם אישית",
|
||||
"Performance": "ביצועים",
|
||||
"Balanced": "מאוזן",
|
||||
"Quality": "איכות",
|
||||
"Ultra": "אולטרה",
|
||||
"Ultra Performance": "ביצועים אולטרה",
|
||||
"Native": "יליד",
|
||||
"Full": "מלא",
|
||||
"Reduced": "מופחת",
|
||||
"Efficient": "יעיל",
|
||||
"Precise": "מדויק",
|
||||
"Auto (VSync)": "אוטומטי (VSync)",
|
||||
"Mailbox": "תיבת דואר",
|
||||
"Immediate": "מיידי",
|
||||
"FPS and Graph": "FPS וגרף",
|
||||
"No cost": "ללא עלות",
|
||||
"Low cost": "עלות נמוכה",
|
||||
"Medium cost": "עלות בינונית",
|
||||
"High cost": "עלות גבוהה",
|
||||
"Saves time": "חוסך זמן",
|
||||
"Graphics": "גרפיקה",
|
||||
"Done": "בוצע",
|
||||
"This feature is not yet available in Horizon.": "תכונה זו עדיין לא זמינה ב-Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "הצג או המערכת שלך אינם מציעים פלט HDR.",
|
||||
"Turn HDR on to use it.": "הפעל את HDR כדי להשתמש בו.",
|
||||
"Your GPU or graphics API doesn't support it.": "ה-GPU או ה-API הגרפי שלך לא תומכים בזה.",
|
||||
"%s chunks": "%s נתחים",
|
||||
"%s per frame": "%s לכל מסגרת",
|
||||
"%s MiB/frame": "%s MiB/פריים",
|
||||
"%s KiB/frame": "%s KiB/מסגרת",
|
||||
"Auto (%s)": "אוטומטי (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "טווח שטח מלא. מרחק LOD יכול להרחיב את העולמות המקומיים רחוק יותר.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "שטח מרוחק גס בעולמות מקומיים. GPUs משולבים עוצרים ב-128 נתחים.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "מצומצם שואב שטח גס יותר: מהיר יותר ב-GPU המשולב הזה.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "מצומצם מושך פני שטח גס יותר, עבור GPUs הוא מאט.",
|
||||
"Field of view. Wider shows more terrain at once.": "שדה ראיה. רחב יותר מציג יותר שטח בבת אחת.",
|
||||
"Caps frames per second; a cap saves power and heat.": "מכסים פריימים לשנייה; מכסה חוסך חשמל וחום.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "ממתין לרענון התצוגה: ללא קריעה, מעט השהייה.",
|
||||
"Size of menus and the HUD.": "גודל התפריטים וה-HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "מגדיר את האפשרויות למטה ביחד. שינוי אחד הופך אותו למותאם אישית.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "לבה, אש ואור שמש זוהרים יותר מלבן. צריך מסך HDR.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "הצג שלך השחור הכי כהה. 0 עבור OLED; צגי LCD זוהרים מעט.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "כמה מדגישים בהירים מקבלים. אוטומטי: השיא שהמערכת שלך מדווחת עליו.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "כמה נייר לבן ותפריטים נראים בהירים. אוטומטי: שולחן העבודה שלך לבן.",
|
||||
"How bright the HUD and menus are, against paper white.": "כמה בהירים ה-HUD והתפריטים, מול נייר לבן.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "עד כמה המון, יבולים ומכונות ממשיכים לעבוד. זמן מעבד.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "כמה רחוק נמשכים אספסוף ופריטים, כחלק מהרגיל.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "מדלג על שטח מוסתר מאחורי קטעים סגורים. חופש עולה הרבה.",
|
||||
"Hides entities behind opaque blocks.": "מסתיר ישויות מאחורי בלוקים אטומים.",
|
||||
"No entity occlusion culling.": "ללא חסימת ישות.",
|
||||
"CPU occlusion test runs this frame.": "בדיקת חסימת מעבד מריץ את המסגרת הזו.",
|
||||
"Occlusion checks run on a worker thread.": "בדיקות חסימה פועלות על שרשור עובד.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "קטעי רשת שהועלו לכל מסגרת. התחתון חלק יותר בזמן הטעינה.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "צללים רכים בפינות. עולה נתח זמן בנייה בלבד.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "עותקים קטנים יותר של מרקמי בלוקים לשטח רחוק: פחות שימר.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "משלב צבעי דשא ומים על פני ביומות בעת בניית נתחים.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "שכבת ענן: כבויה, שטוחה (מהירה) או תלת מימדית (מהודרת).",
|
||||
"How far clouds reach, in chunks.": "כמה רחוק מגיעים עננים, בחתיכות.",
|
||||
"Draws the sky, sun, moon and stars.": "מצייר את השמים, השמש, הירח והכוכבים.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "ערפל למרחקים. Off מציג את קצה העולם העמוס.",
|
||||
"Draws rain and snow.": "מושך גשם ושלג.",
|
||||
"How far rain and snow fall around you.": "עד כמה גשם ושלג יורדים סביבך.",
|
||||
"How many particles effects spawn.": "כמה השפעות חלקיקים נוצרות.",
|
||||
"Round shadows under mobs and items.": "צללים עגולים מתחת להמונים ופריטים.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "עלים עם רווחים שקופים. כבוי מושך אותם מוצקים.",
|
||||
"Sorts water and glass more exactly behind each other.": "ממיין מים וזכוכית בצורה מדויקת יותר אחד אחרי השני.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "איזה נתח בונים מחדש מחכים לפריים: אף אחד, בקרבת מקום או שלך.",
|
||||
"Animates water, lava, fire and other moving textures.": "מחייה מים, לבה, אש ומרקמים נעים אחרים.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "פיקסלים בעולם לכל פיקסל מסך. מתחת ל-100% מהיר יותר ב-GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "מחליק קצוות משוננים. FXAA: רך יותר. TAA: הדברים הנעים והחלקים ביותר עשויים להיגרר.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "הופך את העולם לקטן יותר ובונה אותו מחדש עם TAA. מהר יותר, רך יותר.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "פלט HDR על צג HDR: לבה, אש ואור שמש זוהרים יותר.",
|
||||
"How frames reach the screen while VSync is off.": "איך מסגרות מגיעות למסך בזמן ש-VSync כבוי.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "שינה יעילה עד הפריים הבא; סיבובים מדויקים, כמו וניל.",
|
||||
"Fills the screen (borderless).": "ממלא את המסך (ללא שוליים).",
|
||||
"Brightens dark places.": "מאיר מקומות חשוכים.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "מוריד את קצב הפריימים כאשר הוא ממוזער, או גם במצב סרק.",
|
||||
"Sways the view while walking.": "מטלטל את הנוף תוך כדי הליכה.",
|
||||
"Fades newly loaded chunks in.": "דוהה פנימה נתחים שנטענו לאחרונה.",
|
||||
"Darkens the screen's edges.": "מכהה את קצוות המסך.",
|
||||
"Shows when the world saves.": "מראה מתי העולם מציל.",
|
||||
"Shows attack cooldown.": "מראה התקררות התקפה.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "מכסה של 60 FPS (30 בתפריטים או לא ממוקד), חצי מהעובדים: פחות כוח.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "חוטים שיוצרים, קלילים ורשתים. ההתחלה הבאה.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. ההתחלה הבאה.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "מים וזכוכית במאגר אחד: פחות שיחות משיכה, קצת יותר VRAM. ההתחלה הבאה.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "נתוני רשת שנשלחו ל-GPU לכל מסגרת. התחתון חלק יותר בזמן הטעינה.",
|
||||
"Frame rate and frame times over the game, with a graph.": "קצב פריימים וזמני פריימים במהלך המשחק, עם גרף.",
|
||||
"horizon.multiplayer.finding_friend": "למצוא את עולמו של חברך...",
|
||||
"horizon.multiplayer.not_signed_in": "שרת זה נמצא במצב מקוון. היכנס עם חשבון Microsoft שלך במפעיל Horizon כדי להצטרף אליו.",
|
||||
"horizon.multiplayer.invalid_address": "כתובת שרת לא חוקית: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "מחובר ישר לחבר שלך...",
|
||||
"horizon.multiplayer.connected_friend_relay": "מחובר לחבר שלך דרך הממסר...",
|
||||
"General": "כללי",
|
||||
"Advanced": "מתקדם",
|
||||
"Undo": "בטל",
|
||||
"Search options": "אפשרויות חיפוש",
|
||||
"Shortcut": "קיצור דרך",
|
||||
"Distance": "מרחק",
|
||||
"Display": "תצוגה",
|
||||
"Interface": "ממשק",
|
||||
"Sky and weather": "שמיים ומזג אוויר",
|
||||
"Lighting and entities": "תאורה וגופים",
|
||||
"Textures": "טקסטורות",
|
||||
"Image": "תמונה",
|
||||
"Chunk loading": "טעינת נתחים",
|
||||
"Culling": "גרירה",
|
||||
"Power": "כוח",
|
||||
"Frames": "מסגרות",
|
||||
"Overlay": "שכבת-על",
|
||||
"Output": "פלט",
|
||||
"Brightness": "בהירות",
|
||||
"How far you see, the window and the HUD.": "כמה רחוק אתה רואה, החלון וה-HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "השמיים ומזג האוויר, התאורה, הטקסטורות והתמונה.",
|
||||
"Chunk loading, culling and power use.": "טעינת נתחים, חילוץ ושימוש בכוח.",
|
||||
"How frames reach the screen, and the performance overlay.": "איך מסגרות מגיעות למסך, ושכבת הביצועים.",
|
||||
"Brighter-than-white output on an HDR display.": "פלט בהיר יותר מלבן בצג HDR.",
|
||||
"Results for \"%s\"": "תוצאות עבור \"%s\"",
|
||||
"No settings match.": "אין הגדרות תואמות.",
|
||||
"horizon.title.not_affiliated": "לא מזוהה עם Mojang או Microsoft",
|
||||
"horizon.extras.header.lighting": "תאורה",
|
||||
"horizon.extras.header.third_person": "אדם שלישי",
|
||||
"horizon.extras.header.particles": "חלקיקים",
|
||||
"horizon.extras.header.inventory": "מלאי",
|
||||
"horizon.extras.header.chat": "צ'אט",
|
||||
"horizon.extras.header.multiplayer": "מרובה משתתפים",
|
||||
"horizon.extras.header.sound": "צליל",
|
||||
"horizon.extras.header.menus": "תפריטים",
|
||||
"horizon.extras.dynamic_lights": "אורות דינמיים",
|
||||
"horizon.extras.own_name_tag": "תג שם משלו",
|
||||
"horizon.extras.hide_block_particles": "הסתר חלקיקי שבירת בלוק",
|
||||
"horizon.extras.auto_reconnect": "חיבור אוטומטי",
|
||||
"horizon.extras.ui_sound_reserve": "שמור ערוצים עבור צלילי ממשק משתמש",
|
||||
"horizon.extras.music_page": "עמוד מוזיקה",
|
||||
"horizon.extras.mouse_tweaks": "פקדים של Mouse Tweaks",
|
||||
"horizon.extras.mouse_tweaks_wheel": "גלגל גלילה מזיז פריטים",
|
||||
"horizon.extras.sort": "כפתורי מיון",
|
||||
"horizon.extras.sort_key": "מפתח מיון",
|
||||
"horizon.extras.no_ai": "אין AI",
|
||||
"horizon.extras.agent_control": "בקרת סוכן",
|
||||
"horizon.extras.splitscreen": "מסך מפוצל",
|
||||
"horizon.extras.chat_animation": "צ'אט חלק",
|
||||
"horizon.extras.chat_indicator": "מחוון הודעה",
|
||||
"horizon.extras.wheel_cycles_settings": "גלגל הגלילה משנה הגדרות",
|
||||
"horizon.extras.pack_browser": "דפדפן חבילת משאבים",
|
||||
"horizon.extras.fullbright.tooltip": "הצג בלוקים וישויות באור מלא, אפילו באזורים חשוכים.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "לפידים ומקורות אור אחרים המוחזקים, נפלו או עולים באש מדליקים את הבלוקים סביבם.",
|
||||
"horizon.extras.own_name_tag.tooltip": "מציג את שמך מעל ראשך בגוף שלישי, כפי ששחקנים אחרים רואים זאת. מוסתר כאשר השרת מסתיר את שמות הצוות שלך או שאתה בלתי נראה.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "מסתיר את חלקיקי הפסולת שמתפרצים מבלוק שבור ומתפוררים מזה שאתה מכרה. חלקיקים אחרים נשארים.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "מצטרף מחדש לשרת לאחר איבוד חיבור או הפעלה מחדש, לעולם לא לאחר בעיטה או איסור.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "צלילים בעולם משאירים 16 מתוך 247 ערוצי הסאונד חופשיים, כך שמבול שלהם (פיצוץ גדול) לא יכול להשתיק קליקים בתפריט.",
|
||||
"horizon.extras.music_page.tooltip": "מוסיקה וצלילים מקבלים עמוד מוזיקה...: הפעל כעת כל רצועה מובנית, או כבה אחת כדי שהמשחק יפסיק לבחור בה. שום דבר לא משתנה עד שתכבה רצועה.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "גרור ימני ממקם אחד בכל משבצת, גרירה שמאלה אוספת את הפריט המוחזק, Shift + גרירה שמאלה מזיזה כל משבצת.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "למטה שולח פריט אחד מהחריץ לצד השני, למעלה מחזיר אחד.",
|
||||
"horizon.extras.sort.tooltip": "ממיין את המלאי, השידות, החביות, שידות הקצה ותיבות השולקר לפי סדר כרטיסיות יצירתי, מיזוג ערימות.",
|
||||
"horizon.extras.sort_key.tooltip": "ממיין את החלק מתחת לסמן. לחץ ולאחר מכן הקש על מקש; הבריחה משחררת את זה.",
|
||||
"horizon.extras.no_ai.tooltip": "מסתיר ומכבה כל תכונת AI: בקרת סוכן, horizon-ctl ושרתי MCP. גם המתג של המשגר.",
|
||||
"horizon.extras.agent_control.tooltip": "סוכן AI משלך יכול לשחק במשחק הזה דרך horizon-ctl או לקוח MCP, במחשב זה בלבד.",
|
||||
"Calibrate...": "כיול...",
|
||||
"Calibrate Anyway...": "כייל בכל מקרה...",
|
||||
"Use Automatic Setup": "השתמש בהגדרה אוטומטית",
|
||||
"Shortcut: %s": "קיצור דרך: %s",
|
||||
"Next": "הבא",
|
||||
"HDR Calibration %s/%s: %s": "כיול HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "מגדיר את שלושת המסך המלא מתחת, תמונת בדיקה אחת בכל פעם.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "מפתח שפותח את המסך הזה מהמשחק. לא קשור בהתחלה.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "הצג שלך לא אמר כמה הוא בהיר: כייל כדי להגדיר אותו.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "כבר מוגדר לתצוגה שלך. כייל רק אם HDR נראה שגוי.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "מחזיר את Peak Brightness ואת נייר לבן להגדרות התצוגה.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "הגדר עבור התצוגה שלך. השאר במצב אוטומטי אלא אם כן זה נראה שגוי.",
|
||||
"Lower it until the logo just disappears into the black.": "הורד אותו עד שהלוגו פשוט ייעלם לתוך השחור.",
|
||||
"Raise it until the logo just disappears into the bright square.": "הרם אותו עד שהלוגו פשוט ייעלם בריבוע הבהיר.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "הפוך את הקלף לבן שנוח להסתכל עליו לאורך זמן, באור שאתה משחק בו.",
|
||||
"horizon.splitscreen.profiles.forget": "תשכח",
|
||||
"horizon.splitscreen.profiles.create": "צור",
|
||||
"horizon.splitscreen.profiles.you": " - אתה",
|
||||
"horizon.splitscreen.profiles.forget_named": "תשכח %s",
|
||||
"horizon.splitscreen.profiles.previous": "הקודם",
|
||||
"horizon.splitscreen.profiles.next": "הבא",
|
||||
"horizon.splitscreen.profiles.new_button": "פרופיל חדש...",
|
||||
"horizon.splitscreen.profiles.forget_title": "לשכוח פרופיל?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "זה יוצא מהרשימה של מופע זה.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "ההתקדמות וההגדרות העולמיות נשמרות.",
|
||||
"horizon.splitscreen.profiles.new_title": "פרופיל חדש",
|
||||
"horizon.splitscreen.profiles.name_hint": "שם (3 עד 16 אותיות, ספרות, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "החלף פרופיל",
|
||||
"horizon.splitscreen.profiles.offline_note": "פרופילים לא מקוונים שייכים למופע הזה",
|
||||
"horizon.splitscreen.profiles.move": "זז לאן שאתה משחק",
|
||||
"horizon.splitscreen.profiles.move_place": "זז לאן שאתה משחק: %s",
|
||||
"horizon.friends.title": "שחקו עם חברים",
|
||||
"horizon.friends.hint": "חברים מדביקים את הקוד אחרי כפתור החברים של מסך הכותרת, בחיבור ישיר או בדף ההפעלה של המשגר.",
|
||||
"horizon.friends.copied": "הועתק",
|
||||
"horizon.friends.copy_code": "העתק קוד",
|
||||
"horizon.friends.stop_sharing": "תפסיק לשתף",
|
||||
"horizon.friends.none_yet": "אין כאן חברים עדיין.",
|
||||
"horizon.friends.here_now": "כאן עכשיו: %s",
|
||||
"horizon.friends.share_intro": "שתף את העולם הזה מאחורי קוד הזמנה: הוא נפתח ל-LAN, וחברים באינטרנט מצטרפים עם הקוד. אף שרת שלנו לא מעורב.",
|
||||
"horizon.friends.start_sharing": "התחל לשתף",
|
||||
"horizon.friends.troubleshooting": "פתרון בעיות...",
|
||||
"horizon.friends.stopped": "הפסיק לשתף את קוד ההזמנה.",
|
||||
"horizon.friends.short_code_changed": "מישהו כל הזמן ניסה קוד קצר שגוי, אז זה השתנה.",
|
||||
"horizon.friends.wrong_code": "מישהו ניסה קוד שגוי.",
|
||||
"horizon.friends.code_ready": "קוד ההזמנה שלך מוכן: תפריט משחק, שחק עם חברים. העתק אותו ושלח אותו לחברים שלך.",
|
||||
"horizon.friends.share_failed": "לא יכולתי לשתף את העולם: %s",
|
||||
"horizon.friends.finding_route": "מוצא כיצד חברים יכולים להגיע אליך...",
|
||||
"horizon.osk.space": "חלל",
|
||||
"horizon.osk.delete": "מחק",
|
||||
"horizon.osk.type": "הקלד",
|
||||
"horizon.osk.symbols": "סמלים",
|
||||
"horizon.osk.close": "סגור",
|
||||
"horizon.extras.chat_animation.tooltip": "קו צ'אט חדש גולש פנימה משמאל בזמן שהוא נמוג פנימה, במקום להיראות שלם.",
|
||||
"horizon.extras.chat_indicator.tooltip": "הסרגל הצבעוני בצד שמאל של קו צ'אט.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "גלגל הגלילה מעל כפתור בהגדרות משנה את ערכו, כמו בוניל. כבוי, הגלגל רק גולל את הרשימה.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs מקבל כפתור הוסף שמחפש את Modrinth ומתקין חבילה בתיקיית החבילות שלך. כבוי, המשחק אף פעם לא יוצר קשר עם Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "בעולם, בקר אחר יכול להצטרף כשחקן חדש: הקש A כאשר הוא שואל, ואז בחר פרופיל ואיפה לשחק.",
|
||||
"horizon.splitscreen.setup.monitor": "צג %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "מוניטור",
|
||||
"horizon.splitscreen.setup.window": "חלון %s",
|
||||
"horizon.splitscreen.setup.new_window": "חלון חדש",
|
||||
"horizon.splitscreen.setup.fill_monitor": "מלא %s",
|
||||
"horizon.splitscreen.setup.done": "בוצע",
|
||||
"horizon.splitscreen.setup.on": "פועל",
|
||||
"horizon.splitscreen.setup.off": "כבוי",
|
||||
"horizon.splitscreen.setup.player": "שחקן %s",
|
||||
"horizon.splitscreen.setup.revert": "חזור",
|
||||
"horizon.splitscreen.setup.apply": "החל",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "אף אחד לא משחק בחלון 1: הוא מתחבא",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "אף אחד לא משחק בחלון %s: הוא נסגר ב-Apply",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "אף אחד לא משחק בחלון %s: הוא לא ייפתח",
|
||||
"horizon.splitscreen.setup.warning.on_main": "חלון %s ממלא את הצג של חלון 1: השחקנים שלו משחקים בחלון 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "המסך של חלון %s אינו מחובר: השחקנים שלו משחקים בחלון 1",
|
||||
"horizon.splitscreen.setup.status.off": "מסך מפוצל כבוי.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "הפעל אותו, ולאחר מכן לחץ על כפתור בבקר אחר בעולם.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "לחץ על A כדי להצטרף ולבחור פרופיל.",
|
||||
"horizon.splitscreen.setup.status.idle": "מסך מפוצל סרק",
|
||||
"horizon.splitscreen.setup.status.alone": "אין שחקנים מקומיים אחרים.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "בקרות סולו ומשוב של הבקר אינם משתנים.",
|
||||
"horizon.splitscreen.setup.window_empty": "חלון %s: ריק",
|
||||
"horizon.splitscreen.setup.note.select": "לחץ על נגן, או גרור אותו לחלון או לצג.",
|
||||
"horizon.splitscreen.setup.note.apply": "החל לשחק בדרך זו, חזור לביטול.",
|
||||
"horizon.splitscreen.setup.note.place": "איפה %s צריך לשחק?",
|
||||
"horizon.splitscreen.setup.this_player": "השחקן הזה",
|
||||
"horizon.splitscreen.setup.switch_disabled": "מסך מפוצל נכבה ברגע שהאחרים עזבו (תפריט ההשהיה שלהם).",
|
||||
"horizon.splitscreen.setup.window_place": "חלון %s (%s מתוך %s)",
|
||||
"horizon.splitscreen.setup.names": "שמות",
|
||||
"horizon.splitscreen.setup.identify": "לזהות",
|
||||
"horizon.splitscreen.setup.layout.two": "2 שיתוף: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 שיתוף: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "אוטומטי",
|
||||
"horizon.splitscreen.setup.layout.two.1": "זה לצד זה",
|
||||
"horizon.splitscreen.setup.layout.two.2": "למעלה ולמטה",
|
||||
"horizon.splitscreen.setup.layout.three.0": "אחד רחב מלמעלה",
|
||||
"horizon.splitscreen.setup.layout.three.1": "אחד גבוה משמאל",
|
||||
"horizon.splitscreen.setup.layout.three.2": "זה לצד זה",
|
||||
"horizon.splitscreen.setup.help.player": "לחץ או גרור %s כדי להזיז אותם.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (זה לצד זה כשהחלון רחב). לחץ כדי לשנות.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. לחץ כדי לשנות.",
|
||||
"horizon.splitscreen.setup.help.switch": "מסך מפוצל עבור מחשב זה, מופעל או כבוי.",
|
||||
"horizon.splitscreen.setup.help.identify": "התצוגה של כל שחקן מראה באיזה חלון הוא נמצא.",
|
||||
"horizon.splitscreen.setup.help.names": "שם כל שחקן בפינת התצוגה שלו.",
|
||||
"horizon.splitscreen.setup.help.revert": "בטל את השינויים שעדיין לא הוחלו.",
|
||||
"horizon.splitscreen.setup.help.apply": "שחק כמו שהמפה מציגה.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "שום דבר לא השתנה עדיין.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "לוותר על פד",
|
||||
"horizon.splitscreen.prompt.controller_connected": "בקר מחובר",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "הבקר מנותק",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "חבר אותו מחדש, או לחץ על כפתור באחר",
|
||||
"horizon.splitscreen.prompt.join.one": "%s רוצה להצטרף",
|
||||
"horizon.splitscreen.prompt.join.many": "%s בקרים רוצים להצטרף",
|
||||
"horizon.splitscreen.prompt.join.yes": "הצטרף",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "שחק בתור P1",
|
||||
"horizon.splitscreen.prompt.join.no": "לא",
|
||||
"horizon.splitscreen.prompt.lost": "%s: הבקר מנותק",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "השתמש באחר",
|
||||
"horizon.splitscreen.prompt.lost.leave": "עזוב",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "הבקר שלך מפסיק להסיע אותך ומוצע להצטרף (א').",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s עד %s: A מפיל אותו כאן, B מחזיר אותו.",
|
||||
"horizon.splitscreen.setup.note.held": "%s הרים: שמאל/ימין בוחר היכן, A נופל, B מחזיר אותו.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "מסתיר דגמים מאחורי בלוקים אטומים. א-סינכרון בודק מחוץ לשרשור; ישויות מתקתקות.",
|
||||
"Block Entities": "חסום ישויות",
|
||||
"Shulker Boxes": "שולקר קופסאות",
|
||||
"Reuses settled boxes; lid movement stays animated.": "עושה שימוש חוזר בקופסאות מיושבות; תנועת המכסה נשארת מונפשת.",
|
||||
"Signs": "סימנים",
|
||||
"Reuses sign text until text, light, or font changes.": "עושה שימוש חוזר בטקסט של סימן עד לשינוי טקסט, אור או גופן.",
|
||||
"Decorated Pots": "סירים מעוצבים",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "עושה שימוש חוזר בסירים דוממים; לשבור נדנוד ושינויים לגזור לצייר מחדש.",
|
||||
"Bells": "פעמונים",
|
||||
"Reuses still bells; ringing stays animated.": "עושה שימוש חוזר בפעמונים דוממים; הצלצול נשאר מונפש.",
|
||||
"Chests": "שידות",
|
||||
"Reuses closed chests; moving lids stay animated.": "שימוש חוזר בחזה סגור; מכסים נעים נשארים מונפשים.",
|
||||
"Banners": "באנרים",
|
||||
"Reuses poles; flags and pattern layers still update.": "שימוש חוזר במוטות; דגלים ושכבות דפוס עדיין מתעדכנים.",
|
||||
"Copper Golem Statues": "נחושת פסלי גולם",
|
||||
"Reuses statue geometry until its block state changes.": "עושה שימוש חוזר בגיאומטריית הפסל עד שמצב הבלוק שלו משתנה.",
|
||||
"Leaf Culling": "חיתוך עלים",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "כל פרצוף עלה. העלים המהודרים של וניל.",
|
||||
"Distant Plant Dither": "דיתר צמח רחוק",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "שומר על צמחים עדינים באריזות ברזולוציה גבוהה מלדעוך רחוק.",
|
||||
"Screenshot Quality": "איכות צילום מסך",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "צילומי מסך F2 ברזולוציית המשחק הרבה פעמים. גבוה יותר לוקח יותר זמן.",
|
||||
"Frame Generation": "יצירת מסגרת",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "מציג מסגרת מורכבת בין כל שני אמיתיים: חלק יותר, קצת יותר השהייה.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "עושה שימוש חוזר במפת הצללים של החבילה כאשר השמש, הנוף והעולם תואמים. כבוי כברירת מחדל.",
|
||||
"Find BlueMap": "מצא את BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "בשרתים, מוצא את מפת האינטרנט BlueMap שלהם לשטח רחוק. הבא להצטרף.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "עד כמה מסביבך מורידים אריחי BlueMap. הבא להצטרף.",
|
||||
"Synchronous": "סינכרוני",
|
||||
"Asynchronous": "אסינכרוני",
|
||||
"Solid": "מוצק",
|
||||
"Solid Aggressive": "אגרסיבי מוצק",
|
||||
"Hollow": "חלול",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "עלים במלואם בתוך חופה מושכים מוצקים: פחות שכבות שקופות.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "עלים עם צד אחד פתוח מושכים גם הם מוצקים: קצוות מהירים יותר, שמנמנים יותר.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "טיפות פרצופים בין עלים: מהר יותר, אבל הרווחים מביטים מבעד.",
|
||||
"Hotbar: Previous Slot": "Hotbar: משבצת קודמת",
|
||||
"Hotbar: Next Slot": "Hotbar: המשבצת הבאה",
|
||||
"horizon.agent.connected.one": "מחובר: חיבור סוכן אחד",
|
||||
"horizon.agent.connected.many": "מחוברים: %s חיבורי סוכן",
|
||||
"horizon.agent.connected.one_ago": "מחובר: חיבור סוכן אחד, פקודה אחרונה לפני %s שניות",
|
||||
"horizon.agent.connected.many_ago": "מחובר: %s חיבורי סוכן, פקודה אחרונה לפני %s שניות",
|
||||
"horizon.agent.waiting": "מחכה לסוכן.",
|
||||
"horizon.agent.off": "כבוי: סוכנים לא יכולים להגיע למשחק הזה.",
|
||||
"horizon.agent.session_opened": "נפתח להפעלה זו על ידי התוכנית שהתחילה את המשחק.",
|
||||
"horizon.agent.socket": "שקע: %s (העתק את הפרטים לנתיב המלא שלו)",
|
||||
"horizon.agent.local_only": "מקומי בלבד: סוכנים במחשב זה, לעולם לא ברשת.",
|
||||
"%s nits": "%s ניטים",
|
||||
"Auto (%s nits)": "אוטומטי (%s ניטים)",
|
||||
"%.2f nits": "%.2f ניטים",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "מסתיר דגמים מאחורי בלוקים אטומים. עדכון ישויות. כבוי עם חבילות מותאמות אישית.",
|
||||
"horizon.extras.repro_keys": "הקלטת קיצורי דרך",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R מתחיל או עוצר הקלטת רפרו ו-F3+E כותב את החבילה שלו. כבוי כברירת מחדל: הקלטה מעתיקה את העולם.",
|
||||
"horizon.agent.record_session": "התחל מושב ההקלטה",
|
||||
"horizon.agent.record_stop": "עצור הקלטת הפעלה",
|
||||
"horizon.agent.export_repro": "ייצוא חבילת Repro",
|
||||
"horizon.repro.started": "ההקלטה החלה; F3+R עוצר אותו וכותב את החבילה.",
|
||||
"horizon.repro.stopped": "ההקלטה הופסקה: %s",
|
||||
"horizon.repro.bundle": "חבילת רפרו: %s",
|
||||
"horizon.repro.error": "רפרו: %s",
|
||||
"horizon.repro.no_recording": "עדיין אין הקלטה לייצוא.",
|
||||
"horizon.repro.replay_started": "השידור החוזר התחיל: %s",
|
||||
"horizon.pack.addInfo": "הוסף חבילת משאבים מ- Modrinth",
|
||||
"horizon.pack.addNone": "לא נמצאו חבילות משאבים",
|
||||
"horizon.pack.addTitle": "הוסף חבילת משאבים",
|
||||
"horizon.pack.install": "התקן",
|
||||
"horizon.pack.installing": "מתקין",
|
||||
"horizon.pack.installed": "מותקן",
|
||||
"horizon.pack.search": "חפש",
|
||||
"horizon.pack.filters": "מסננים",
|
||||
"horizon.pack.filter.realistic": "מציאותי",
|
||||
"horizon.pack.filter.simplistic": "פשטני",
|
||||
"horizon.pack.filter.vanillaLike": "דמוי וניל",
|
||||
"horizon.pack.addFailed": "לא ניתן להוסיף את החבילה: %s",
|
||||
"horizon.extras.header.cosmetics": "קוסמטיקה",
|
||||
"horizon.extras.cosmetics_mine": "הקוסמטיקה שלי",
|
||||
"horizon.extras.cosmetics_others": "מוצרי קוסמטיקה של שחקנים אחרים",
|
||||
"horizon.extras.cosmetics_others.tooltip": "מה שחקני Horizon אחרים לובשים: הכל, רק סגנונות תג השם שלהם, או כלום. נסתר, המשחק אף פעם לא שואל מה הם לובשים.",
|
||||
"horizon.extras.cosmetics_auras": "ההילות והשבילים של אחרים",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "הילות ושבילים ששחקנים אחרים לובשים. כבוי כברירת מחדל.",
|
||||
"horizon.extras.cosmetics.shown": "מוצג",
|
||||
"horizon.extras.cosmetics.hidden": "מוסתר",
|
||||
"horizon.extras.cosmetics.name_tags": "תגי שם בלבד"
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
{
|
||||
"horizon.options.controller": "Vezérlő",
|
||||
"horizon.options.vibration": "Rezgés",
|
||||
"horizon.options.adaptive_triggers": "Adaptív triggerek",
|
||||
"horizon.options.look_sensitivity": "Nézet érzékenysége",
|
||||
"horizon.options.invert_x": "Nézet X tengelyének megfordítása",
|
||||
"horizon.options.invert_y": "Nézet Y tengelyének megfordítása",
|
||||
"horizon.options.sneak_button": "Lopakodás gomb",
|
||||
"horizon.options.move_deadzone": "Mozgás holtzónája",
|
||||
"horizon.options.look_deadzone": "Nézet holtzónája",
|
||||
"horizon.options.button_glyphs": "Gomb jelek",
|
||||
"horizon.options.menu_navigation": "Menü navigáció",
|
||||
"horizon.options.on_screen_keyboard": "Képernyő-billentyűzet",
|
||||
"horizon.options.chat_delay": "Chat késleltetés",
|
||||
"horizon.options.lightbar.health": "Egészség",
|
||||
"horizon.options.lightbar.dimension": "Méret",
|
||||
"horizon.options.menu_focus": "Fókusz",
|
||||
"horizon.options.menu_pointer": "Mutató",
|
||||
"horizon.options.keyboard.horizon": "Horizont",
|
||||
"horizon.options.unavailable": "Ez a funkció még nem érhető el a Horizonban.",
|
||||
"horizon.options.search": "Keresési beállítások...",
|
||||
"horizon.pages.controller": "Vezérlő beállításai",
|
||||
"horizon.pages.extras": "Horizon Extrák",
|
||||
"horizon.pages.settings": "Horizont beállítások",
|
||||
"horizon.pages.agent": "AI ügynök",
|
||||
"horizon.split.leave": "Hagyja",
|
||||
"horizon.pages.split_settings": "Osztott képernyő beállításai",
|
||||
"horizon.pages.split_profiles": "Ki játszik?",
|
||||
"horizon.pages.split_setup": "Osztott képernyő beállítása",
|
||||
"horizon.pages.music": "Zene",
|
||||
"horizon.pages.music.menu": "Menü",
|
||||
"horizon.pages.music.overworld": "Túlvilág",
|
||||
"horizon.pages.music.creative": "Kreatív",
|
||||
"horizon.pages.music.underwater": "Víz alatt",
|
||||
"horizon.pages.music.end": "A Vég",
|
||||
"horizon.pages.music.boss": "Főnök",
|
||||
"horizon.pages.music.credits": "Kredit",
|
||||
"horizon.pages.music.overworld_biomes": "Túlvilági életközösségek",
|
||||
"horizon.pages.music.nether_biomes": "Nem biomák",
|
||||
"horizon.pages.music.end_biomes": "Vége a biomoknak",
|
||||
"horizon.pages.music.other": "Egyéb zene",
|
||||
"horizon.pages.music.empty": "Nincsenek zeneszámok: a játék hangjai nem töltődnek be.",
|
||||
"horizon.pages.music.tooltip": "A beépített számok helyzet szerint: játssz egyet most, vagy kapcsold ki bármelyiket, hogy a játék ne válassza ki.",
|
||||
"horizon.pages.settings.tooltip": "A Horizon saját beállításai: grafika, vezérlők, extrák és modok.",
|
||||
"horizon.pages.graphics.tooltip": "Grafika, egyszerűen: minden beállítás megadja, hogy mennyibe kerül.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Minden grafikus beállítás, beleértve a Horizont is, minden költséggel együtt.",
|
||||
"horizon.pages.controller.tooltip": "Vezérlők: gombjaik, botjaik, rezgés és menü navigáció.",
|
||||
"horizon.pages.extras.tooltip": "Opcionális kiegészítések, alapértelmezés szerint mind ki: a játék addig marad, amíg be nem kapcsol egyet.",
|
||||
"horizon.pages.agent.tooltip": "Engedje, hogy saját AI-ügynöke játsszon: horizon-ctl és MCP kliensek ezen a számítógépen.",
|
||||
"horizon.pages.split_profiles.tooltip": "Ki játszik: ebben a játékmappában tárolt profil.",
|
||||
"horizon.pages.split_setup.tooltip": "Az ablakok és a monitorok, és ki hol játszik.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader csomagok a shaderpacks mappából: a Horizon saját és a shader/ formátumúak. Egyszerre egy fut.",
|
||||
"horizon.pages.mods.tooltip": "A Horizonhoz készült modok a horizon-mods mappából. Mindegyik homokozóban fut, és kikapcsolva marad, amíg be nem kapcsolja.",
|
||||
"horizon.pages.open.graphics": "Grafika...",
|
||||
"horizon.pages.open.graphics_advanced": "Speciális grafika...",
|
||||
"horizon.pages.open.shader_packs": "Shader csomagok...",
|
||||
"horizon.pages.open.controller": "Vezérlő beállításai...",
|
||||
"horizon.pages.open.split_setup": "Osztott képernyő...",
|
||||
"horizon.pages.open.extras": "Extrák...",
|
||||
"horizon.pages.open.mods": "Modok...",
|
||||
"horizon.pages.open.agent": "AI ügynök...",
|
||||
"horizon.pages.open.controller_binds": "A vezérlő köt...",
|
||||
"horizon.pages.open.horizon_settings": "Horizont beállítások...",
|
||||
"horizon.pages.open.music": "Zene...",
|
||||
"horizon.mods.empty": "Nincs mod: tegye a .hmod fájlokat a horizon-mods mappába",
|
||||
"horizon.mods.stopped": " (Leállítva)",
|
||||
"horizon.mods.not_built": " (nem épült)",
|
||||
"horizon.mods.dev": " (Fejlesztő)",
|
||||
"horizon.mods.settings": "Beállítások elemre",
|
||||
"horizon.mods.in_world": " (hagyd a világot, hogy megváltozzon)",
|
||||
"horizon.mods.open_folder": "Nyissa meg a Mod mappát",
|
||||
"horizon.shader_packs.empty": "Nincs árnyékoló csomag: helyezze őket a shaderpacks mappába",
|
||||
"horizon.shader_packs.kind_horizon": " (Horizont)",
|
||||
"horizon.shader_options.open": "Árnyékoló opciók...",
|
||||
"horizon.shader_options.profile": "Profil",
|
||||
"horizon.shader_options.loading": "A shader csomag opcióinak elolvasása...",
|
||||
"horizon.agent.copy_details": "Másolja a kapcsolat részleteit",
|
||||
"horizon.multiplayer.join": "Csatlakozz",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Az újracsatlakozás megszakítása",
|
||||
"horizon.multiplayer.join_friend": "Csatlakozz egy baráthoz",
|
||||
"horizon.multiplayer.invite_code": "Meghívó kód",
|
||||
"horizon.multiplayer.relay": "Relé (opcionális)",
|
||||
"Preset": "Előre beállított",
|
||||
"LOD Distance": "LOD távolság",
|
||||
"Far Detail": "Távoli részlet",
|
||||
"Entity Culling": "Entitás selejtezés",
|
||||
"Chunk Updates": "Csonkos frissítések",
|
||||
"Upload Budget": "Költségkeret feltöltése",
|
||||
"Sky": "Égbolt",
|
||||
"Fog": "Köd",
|
||||
"Weather": "Időjárás",
|
||||
"Animated Textures": "Animált textúrák",
|
||||
"Present Mode": "Jelen mód",
|
||||
"Worker Threads": "Munkásszálak",
|
||||
"Resolution Scale": "Felbontási skála",
|
||||
"Anti-aliasing": "Élsimítás",
|
||||
"Upscaling": "Felskálázás",
|
||||
"Paper White": "Fehér papír",
|
||||
"Peak Brightness": "Csúcsfényerő",
|
||||
"Black Level": "Fekete szint",
|
||||
"UI Brightness": "UI fényerő",
|
||||
"Battery Mode": "Akkumulátor üzemmód",
|
||||
"Graphics API": "Grafikus API",
|
||||
"Translucent Batching": "Átlátszó adagolás",
|
||||
"Performance Overlay": "Teljesítményfedvény",
|
||||
"Full Detail Distance": "Teljes részlet Távolság",
|
||||
"On": "Be",
|
||||
"Off": "Ki",
|
||||
"Unlimited": "Korlátlan",
|
||||
"Custom": "Egyedi",
|
||||
"Performance": "Teljesítmény",
|
||||
"Balanced": "Kiegyensúlyozott",
|
||||
"Quality": "Minőség",
|
||||
"Ultra Performance": "Ultra teljesítmény",
|
||||
"Native": "Natív",
|
||||
"Full": "Tele",
|
||||
"Reduced": "Csökkentett",
|
||||
"Efficient": "Hatékony",
|
||||
"Precise": "Pontos",
|
||||
"Mailbox": "Postafiók",
|
||||
"Immediate": "Azonnali",
|
||||
"FPS and Graph": "FPS és grafikon",
|
||||
"No cost": "Nincs költség",
|
||||
"Low cost": "Alacsony költség",
|
||||
"Medium cost": "Közepes költség",
|
||||
"High cost": "Magas költség",
|
||||
"Saves time": "Időt takarít meg",
|
||||
"Graphics": "Grafika",
|
||||
"Done": "Kész",
|
||||
"This feature is not yet available in Horizon.": "Ez a funkció még nem érhető el a Horizonban.",
|
||||
"Your display or system doesn't offer HDR output.": "Kijelzője vagy rendszere nem kínál HDR-kimenetet.",
|
||||
"Turn HDR on to use it.": "A használatához kapcsolja be a HDR-t.",
|
||||
"Your GPU or graphics API doesn't support it.": "A GPU vagy a grafikus API nem támogatja.",
|
||||
"%s chunks": "%s darab",
|
||||
"%s per frame": "%s képkockánként",
|
||||
"%s MiB/frame": "%s MiB/kocka",
|
||||
"%s KiB/frame": "%s KiB/kocka",
|
||||
"Auto (%s)": "Automatikus (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Részletes tereptartomány. A LOD Distance messzebbre terjesztheti a helyi világokat.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Durva távoli terep a helyi világokban. Az integrált GPU-k 128 darabnál állnak meg.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "A csökkentett rajz sokkal durvább: gyorsabb ezen az integrált GPU-n.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "A redukált sokkal durvábban rajzolja meg a terepet, a GPU-knál lassul.",
|
||||
"Field of view. Wider shows more terrain at once.": "Látómező. A szélesebb egyszerre több terepet mutat.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Képkocka sebessége másodpercenként; a sapka energiát és hőt takarít meg.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Várja a kijelző frissítését: nincs szakadás, egy kis késleltetés.",
|
||||
"Size of menus and the HUD.": "A menük mérete és a HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Az alábbi opciók együttes beállítása. Az egyik megváltoztatása egyedivé teszi.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "A láva, a tűz és a napfény fényesebben ragyog, mint a fehér. HDR kijelző kell hozzá.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "A kijelződ a legsötétebb fekete. 0 OLED esetén; Az LCD-k kicsit világítanak.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Milyen fényesek a csúcsfények. Automatikus: a rendszer által jelentett csúcs.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Milyen fényes fehér papír és menük néznek ki. Automatikus: az asztal fehér.",
|
||||
"How bright the HUD and menus are, against paper white.": "Milyen fényesek a HUD és a menük papírfehérrel szemben.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Meddig dolgoznak a csőcselék, a termények és a gépek. CPU idő.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Milyen messzire húzódnak a mobok és tárgyak, a normál arányaként.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Kihagyja a zárt szakaszok mögé rejtett terepet. Az off sokba kerül.",
|
||||
"Hides entities behind opaque blocks.": "Az entitásokat átlátszatlan blokkok mögé rejti.",
|
||||
"No entity occlusion culling.": "Nincs entitáselzáródási selejtezés.",
|
||||
"CPU occlusion test runs this frame.": "CPU elzáródási teszt futtatja ezt a keretet.",
|
||||
"Occlusion checks run on a worker thread.": "Az elzáródás-ellenőrzések egy dolgozó szálon futnak.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Képkockánként feltöltött hálós szakaszok. Az alsó simább töltés közben.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Lágy árnyékok a sarkokban. Csak az építési idő töredékébe kerül.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Blokktextúrák kisebb példányai távoli terepre: kevesebb csillogás.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "A darabok építése során összekeveri a füvet és a víz színét a biomokon.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Felhőréteg: kikapcsolt, lapos (gyors) vagy 3D (divatos).",
|
||||
"How far clouds reach, in chunks.": "Milyen messzire érnek a felhők, darabokban.",
|
||||
"Draws the sky, sun, moon and stars.": "Rajzolja az eget, a napot, a holdat és a csillagokat.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Távolság köd. Az Off a betöltött világ szélét mutatja.",
|
||||
"Draws rain and snow.": "Esőt és havat rajzol.",
|
||||
"How far rain and snow fall around you.": "Milyen messze esik körülötted az eső és a hó.",
|
||||
"How many particles effects spawn.": "Hány részecske hatások spawn.",
|
||||
"Round shadows under mobs and items.": "Kerek árnyékok a mobok és tárgyak alatt.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Levelek átlátszó hézagokkal. Az Off megerősíti őket.",
|
||||
"Sorts water and glass more exactly behind each other.": "Pontosabban egymás mögé rendezi a vizet és az üveget.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Melyik darab újjáépítése várja a keretet: nincs, a közelben vagy a tiéd.",
|
||||
"Animates water, lava, fire and other moving textures.": "Vizet, lávát, tüzet és más mozgó textúrákat animál.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Világképpontok képernyőpixelenként. 100% alatt gyorsabb a GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Kisimítja a szaggatott széleket. FXAA: lágyabb. TAA: a legsimább, mozgó dolgok nyomába eredhetnek.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Kisebbé teszi a világot, és TAA-val újjáépíti. Gyorsabb, lágyabb.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR kimenet HDR kijelzőn: a láva, a tűz és a napfény erősebben ragyog.",
|
||||
"How frames reach the screen while VSync is off.": "Hogyan érik el a keretek a képernyőt, amikor a VSync ki van kapcsolva.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Hatékony alvás a következő képkockáig; Pontos pörgetések, mint a vanília.",
|
||||
"Fills the screen (borderless).": "Kitölti a képernyőt (szegély nélkül).",
|
||||
"Brightens dark places.": "Világosítja a sötét helyeket.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Csökkenti a képkockasebességet minimálisra csökkentve vagy üresjáratban is.",
|
||||
"Sways the view while walking.": "Séta közben megingatja a kilátást.",
|
||||
"Fades newly loaded chunks in.": "Elhalványítja az újonnan betöltött darabokat.",
|
||||
"Darkens the screen's edges.": "Sötétíti a képernyő széleit.",
|
||||
"Shows when the world saves.": "Megmutatja, mikor ment meg a világ.",
|
||||
"Shows attack cooldown.": "A támadás lehűlését mutatja.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS sapka (30 a menükben vagy nem fókuszálva), a dolgozók fele: kevesebb energia.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Szálak, amelyek darabokat hoznak létre, könnyítenek és hálóznak össze. Következő kezdés.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, majd DirectX 12 (Windows) vagy OpenGL (Linux), ahol meghiúsul. Következő kezdés.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Víz és üveg egy pufferben: kevesebb hívás, kicsit több VRAM. Következő kezdés.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "A GPU-nak küldött hálóadatok képkockánként. Az alsó simább töltés közben.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Képkockasebesség és képkockaidő a játék során, grafikonnal.",
|
||||
"horizon.multiplayer.finding_friend": "Megtalálni a barátod világát...",
|
||||
"horizon.multiplayer.not_signed_in": "Ez a szerver online módban van. A csatlakozáshoz jelentkezzen be Microsoft-fiókjával a Horizon Indítóban.",
|
||||
"horizon.multiplayer.invalid_address": "Érvénytelen szervercím: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Egyenesen a barátoddal...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Kapcsolatban áll a barátjával a relén keresztül...",
|
||||
"General": "tábornok",
|
||||
"Advanced": "Haladó",
|
||||
"Undo": "Visszavonás",
|
||||
"Search options": "Keresési lehetőségek",
|
||||
"Shortcut": "Parancsikon",
|
||||
"Distance": "Távolság",
|
||||
"Display": "Kijelző",
|
||||
"Interface": "Interfész",
|
||||
"Sky and weather": "Ég és időjárás",
|
||||
"Lighting and entities": "Világítás és entitások",
|
||||
"Textures": "Textúrák",
|
||||
"Image": "Kép",
|
||||
"Chunk loading": "Darab betöltés",
|
||||
"Culling": "Selejtezés",
|
||||
"Power": "Erő",
|
||||
"Frames": "Keretek",
|
||||
"Overlay": "Fedvény",
|
||||
"Output": "Kimenet",
|
||||
"Brightness": "Fényerő",
|
||||
"How far you see, the window and the HUD.": "Milyen messzire látsz, az ablak és a HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Az ég és az időjárás, a világítás, a textúrák és a kép.",
|
||||
"Chunk loading, culling and power use.": "Darabtöltés, selejtezés és energiafelhasználás.",
|
||||
"How frames reach the screen, and the performance overlay.": "Hogyan érik el a képkockák a képernyőt, és a teljesítményfedvény.",
|
||||
"Brighter-than-white output on an HDR display.": "A fehérnél világosabb kimenet HDR kijelzőn.",
|
||||
"Results for \"%s\"": "Találatok a következőre: \"%s\"",
|
||||
"No settings match.": "Egyetlen beállítás sem egyezik.",
|
||||
"horizon.title.not_affiliated": "Nem áll kapcsolatban a Mojanggal vagy a Microsofttal",
|
||||
"horizon.extras.header.lighting": "Világítás",
|
||||
"horizon.extras.header.third_person": "Harmadik személy",
|
||||
"horizon.extras.header.particles": "Részecskék",
|
||||
"horizon.extras.header.inventory": "Leltár",
|
||||
"horizon.extras.header.multiplayer": "Többjátékos",
|
||||
"horizon.extras.header.sound": "Hang",
|
||||
"horizon.extras.header.menus": "Menük",
|
||||
"horizon.extras.dynamic_lights": "Dinamikus fények",
|
||||
"horizon.extras.own_name_tag": "Saját névtábla",
|
||||
"horizon.extras.hide_block_particles": "A blokktörés részecskéinek elrejtése",
|
||||
"horizon.extras.auto_reconnect": "Automatikus újracsatlakozás",
|
||||
"horizon.extras.ui_sound_reserve": "Tartsa meg a csatornákat a felhasználói felület hangjaihoz",
|
||||
"horizon.extras.music_page": "Zenei oldal",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks vezérlők",
|
||||
"horizon.extras.mouse_tweaks_wheel": "A görgő mozgatja az elemeket",
|
||||
"horizon.extras.sort": "Rendező gombok",
|
||||
"horizon.extras.sort_key": "Rendezési kulcs",
|
||||
"horizon.extras.no_ai": "Nincs AI",
|
||||
"horizon.extras.agent_control": "Ügynökvezérlés",
|
||||
"horizon.extras.discord_presence": "Discord gazdag jelenlét",
|
||||
"horizon.extras.splitscreen": "Osztott képernyő",
|
||||
"horizon.extras.chat_animation": "Sima chat",
|
||||
"horizon.extras.chat_indicator": "Üzenetjelző",
|
||||
"horizon.extras.wheel_cycles_settings": "A görgetőkerék módosítja a beállításokat",
|
||||
"horizon.extras.pack_browser": "Erőforráscsomag böngésző",
|
||||
"horizon.extras.fullbright.tooltip": "A blokkok és entitások teljes megvilágításban jelennek meg, még sötét területeken is.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "A fáklyák és más fényforrások megtartották, leejtették vagy lángra lobbantották a körülöttük lévő blokkokat.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Megjeleníti a nevét a fejed felett harmadik személyben, ahogy a többi játékos látja. Elrejtve, ha a szerver elrejti a csapatod nevét, vagy ha láthatatlan vagy.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Elrejti a törmelékrészecskéket, amelyek egy törött tömbből kitörtek, és lemorzsolódnak az általad bányászottról. A többi részecskék megmaradnak.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Megszakadt kapcsolat vagy újraindítás után csatlakozik újra a szerverhez, soha nem rúgás vagy kitiltás után.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "A világ hangjai a 247 hangcsatorna közül 16-ot szabadon hagynak, így ezek özöne (nagy robbanás) nem tudja elhallgattatni a menükattintásokat.",
|
||||
"horizon.extras.music_page.tooltip": "A Zene és hangok Zene... oldalt kap: játsszon le bármelyik beépített számot most, vagy kapcsolja ki az egyiket, hogy a játék ne válassza ki azt. Semmi sem változik, amíg ki nem kapcsol egy műsorszámot.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "A jobb oldali húzás résenként egyet helyez el, a bal húzás a megtartott elemet gyűjti össze, a Shift + balra húzás pedig minden helyet mozgat.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Le a nyílás egyik elemét a másik oldalra küldi, a felfelé pedig egyet vissza.",
|
||||
"horizon.extras.sort.tooltip": "Rendezi a leltárt, ládákat, hordókat, ender ládákat és shulker dobozokat kreatív lapok sorrendjében, egyesíti a halmokat.",
|
||||
"horizon.extras.sort_key.tooltip": "A kurzor alatti részt rendezi. Kattintson, majd nyomjon meg egy billentyűt; A menekülés feloldja.",
|
||||
"horizon.extras.no_ai.tooltip": "Elrejti és kikapcsolja az összes mesterséges intelligencia funkciót: az ügynökvezérlést, a horizon-ctl-t és az MCP-kiszolgálókat. Az indító kapcsolója is.",
|
||||
"horizon.extras.agent_control.tooltip": "Saját AI-ügynöke csak ezen a számítógépen tudja játszani ezt a játékot a horizon-ctl-n vagy egy MCP-kliensen keresztül.",
|
||||
"horizon.extras.discord_presence.tooltip": "Megjeleníti a Playing Horizont a Discord-profilon, miközben a Discord fut: menük, egyjátékos vagy többjátékos, a dimenzió és a lejátszási idő. Soha nem egy világnév vagy egy szerver címe.",
|
||||
"Calibrate...": "Kalibrálás...",
|
||||
"Calibrate Anyway...": "Mindenesetre kalibráld...",
|
||||
"Use Automatic Setup": "Használja az Automatikus beállítást",
|
||||
"Shortcut: %s": "Parancsikon: %s",
|
||||
"Next": "Következő",
|
||||
"HDR Calibration %s/%s: %s": "HDR kalibrálás %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "A három alatti teljes képernyőt állítja be, egyszerre egy tesztképet.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Kulcs, amely megnyitja ezt a képernyőt a játékból. Először nem kötve.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "A kijelzője nem jelezte, hogy mennyire lesz fényes: kalibrálja a beállításhoz.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Már be van állítva a kijelzőjéhez. Csak akkor kalibráljon, ha a HDR rossznak tűnik.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Visszaállítja a csúcsfényerőt és a papírfehéret a kijelző beállításához.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Beállítás a kijelzőhöz. Hagyja az Automatikus beállítást, hacsak nem tűnik rosszul.",
|
||||
"Lower it until the logo just disappears into the black.": "Engedje le, amíg a logó el nem tűnik a feketében.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Emelje fel, amíg a logó el nem tűnik a világos négyzetben.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Tedd a kártyát olyan fehér színűvé, amelyre kényelmesen nézheted sokáig, a játék fényében.",
|
||||
"horizon.splitscreen.profiles.forget": "Felejtsd el",
|
||||
"horizon.splitscreen.profiles.create": "Létrehozása",
|
||||
"horizon.splitscreen.profiles.you": " - te",
|
||||
"horizon.splitscreen.profiles.forget_named": "Felejtsd el %s",
|
||||
"horizon.splitscreen.profiles.previous": "Előző",
|
||||
"horizon.splitscreen.profiles.next": "Következő",
|
||||
"horizon.splitscreen.profiles.new_button": "Új profil...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Elfelejti a profilt?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Kihagyja ennek a példánynak a listáját.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "A világ fejlődése és beállításai megmaradnak.",
|
||||
"horizon.splitscreen.profiles.new_title": "Új profil",
|
||||
"horizon.splitscreen.profiles.name_hint": "Név (3-16 betű, számjegy, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Profil váltása",
|
||||
"horizon.splitscreen.profiles.offline_note": "Az offline profilok ehhez a példányhoz tartoznak",
|
||||
"horizon.splitscreen.profiles.move": "Menj oda, ahol játszol",
|
||||
"horizon.splitscreen.profiles.move_place": "Menjen oda, ahol játszik: %s",
|
||||
"horizon.friends.title": "Játssz a barátokkal",
|
||||
"horizon.friends.hint": "Az ismerősök beillesztik a kódot a címképernyő Barátok gombja mögé, a Közvetlen kapcsolatba vagy az indító Play oldalára.",
|
||||
"horizon.friends.copied": "Másolva",
|
||||
"horizon.friends.copy_code": "Kód másolása",
|
||||
"horizon.friends.stop_sharing": "Megosztás leállítása",
|
||||
"horizon.friends.none_yet": "Még nincsenek itt barátok.",
|
||||
"horizon.friends.here_now": "Itt most: %s",
|
||||
"horizon.friends.share_intro": "Oszd meg ezt a világot egy meghívókód mögött: megnyílik a LAN-hoz, és az interneten lévő ismerősök is csatlakoznak a kódhoz. A mi szerverünk nem érintett.",
|
||||
"horizon.friends.start_sharing": "Indítsa el a Megosztást",
|
||||
"horizon.friends.troubleshooting": "Hibaelhárítás...",
|
||||
"horizon.friends.stopped": "A meghívókód megosztása leállt.",
|
||||
"horizon.friends.short_code_changed": "Valaki folyamatosan rossz rövid kóddal próbálkozott, ezért megváltozott.",
|
||||
"horizon.friends.wrong_code": "Valaki rossz kóddal próbálkozott.",
|
||||
"horizon.friends.code_ready": "A meghívókód készen áll: Játékmenü, Játssz a barátokkal. Másold ki és küldd el ismerőseidnek.",
|
||||
"horizon.friends.share_failed": "Nem sikerült megosztani a világot: %s",
|
||||
"horizon.friends.finding_route": "Megtudja, hogyan érhetnek el barátaid…",
|
||||
"horizon.osk.shift": "Váltás",
|
||||
"horizon.osk.space": "Tér",
|
||||
"horizon.osk.delete": "Törölje",
|
||||
"horizon.osk.type": "Írja be",
|
||||
"horizon.osk.symbols": "Szimbólumok",
|
||||
"horizon.osk.close": "Bezárás",
|
||||
"horizon.extras.chat_animation.tooltip": "Egy új csevegősor balról csúszik be, miközben elhalványul, ahelyett, hogy egészben jelenne meg.",
|
||||
"horizon.extras.chat_indicator.tooltip": "A színes sáv a csevegősor bal oldalán.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "A beállításokban a gomb feletti görgő megváltoztatja annak értékét, mint a vanília esetében. Kikapcsolva a kerék csak a listát görgeti.",
|
||||
"horizon.extras.pack_browser.tooltip": "Az Erőforrás-csomagok kiválasztásakor megjelenik egy Hozzáadás gomb, amely megkeresi a Modrinth-t, és telepít egy csomagot a csomagok mappájába. Ki, a játék soha nem lép kapcsolatba a Modrinth-szal.",
|
||||
"horizon.extras.splitscreen.tooltip": "Egy világban egy másik kontroller is csatlakozhat új játékosként: nyomja meg az A gombot, amikor kéri, majd válasszon profilt és hol játszani.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "egy monitort",
|
||||
"horizon.splitscreen.setup.window": "Ablak %s",
|
||||
"horizon.splitscreen.setup.new_window": "Új ablak",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Töltse ki %s",
|
||||
"horizon.splitscreen.setup.done": "Kész",
|
||||
"horizon.splitscreen.setup.on": "Be",
|
||||
"horizon.splitscreen.setup.off": "Ki",
|
||||
"horizon.splitscreen.setup.player": "Játékos %s",
|
||||
"horizon.splitscreen.setup.revert": "Vissza",
|
||||
"horizon.splitscreen.setup.apply": "Alkalmazni",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Senki sem játszik az 1. ablakban: elbújik",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Senki sem játszik a %s ablakban: bezárul az Alkalmaz gombra",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Senki sem játszik a %s ablakban: nem nyílik meg",
|
||||
"horizon.splitscreen.setup.warning.on_main": "A %s ablak kitölti a Window 1 monitorát: a játékosok az 1-es ablakban játszanak",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "A %s ablak monitora nincs csatlakoztatva: lejátszói az 1-es ablakban játszanak",
|
||||
"horizon.splitscreen.setup.status.off": "Az osztott képernyő ki van kapcsolva.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Kapcsolja be, majd nyomja meg a gombot egy másik vezérlőn a világon.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Nyomja meg az A gombot a csatlakozáshoz, és válasszon egy profilt.",
|
||||
"horizon.splitscreen.setup.status.idle": "Osztott képernyő tétlen",
|
||||
"horizon.splitscreen.setup.status.alone": "Nincs más helyi játékos.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Az egyéni vezérlések és a vezérlő visszajelzései változatlanok.",
|
||||
"horizon.splitscreen.setup.window_empty": "Ablak %s: üres",
|
||||
"horizon.splitscreen.setup.note.select": "Kattintson egy lejátszóra, vagy húzza át egy ablakra vagy egy monitorra.",
|
||||
"horizon.splitscreen.setup.note.apply": "Alkalmazza ezt a játékot, a visszavonáshoz pedig vissza.",
|
||||
"horizon.splitscreen.setup.note.place": "Hol játsszon %s?",
|
||||
"horizon.splitscreen.setup.this_player": "ezt a játékost",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Az osztott képernyő kikapcsol, ha a többiek kilépnek (a szünet menüjük).",
|
||||
"horizon.splitscreen.setup.window_place": "%s ablak (%s/%s)",
|
||||
"horizon.splitscreen.setup.names": "Nevek",
|
||||
"horizon.splitscreen.setup.identify": "Azonosítsd",
|
||||
"horizon.splitscreen.setup.layout.two": "2 megosztás: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 megosztás: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Egymás mellett",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Felső és alsó",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Egy széles a tetején",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Egy magas a bal oldalon",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Egymás mellett",
|
||||
"horizon.splitscreen.setup.help.player": "Kattintson vagy húzza át őket a %s elemre.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (egymás mellett, ha az ablak széles). Nyomja meg a módosításhoz.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Nyomja meg a módosításhoz.",
|
||||
"horizon.splitscreen.setup.help.switch": "Osztott képernyő ehhez a számítógéphez, be- vagy kikapcsolva.",
|
||||
"horizon.splitscreen.setup.help.identify": "Minden játékos nézete mutatja, hogy melyik ablakban van.",
|
||||
"horizon.splitscreen.setup.help.names": "Minden játékos neve a nézetük sarkában.",
|
||||
"horizon.splitscreen.setup.help.revert": "A még nem alkalmazott módosítások visszavonása.",
|
||||
"horizon.splitscreen.setup.help.apply": "Játssz úgy, ahogy a térkép mutatja.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Még nem változott semmi.",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Vezérlő csatlakoztatva",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "A vezérlő leválasztva",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Csatlakoztassa újra, vagy nyomja meg a gombot egy másikon",
|
||||
"horizon.splitscreen.prompt.join.one": "%s szeretne csatlakozni",
|
||||
"horizon.splitscreen.prompt.join.many": "%s vezérlő szeretne csatlakozni",
|
||||
"horizon.splitscreen.prompt.join.yes": "Csatlakozz",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Játssz P1-ként",
|
||||
"horizon.splitscreen.prompt.join.no": "Nem",
|
||||
"horizon.splitscreen.prompt.lost": "%s: a vezérlő leválasztva",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Használj másikat",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Hagyja",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "A vezérlő abbahagyja a vezetést, és felajánlják, hogy csatlakozzon (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s - %s: A idedobja, B visszateszi.",
|
||||
"horizon.splitscreen.setup.note.held": "%s felvette: bal/jobb kiválasztja, hol, A leejti, B visszateszi.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "A modelleket átlátszatlan blokkok mögé rejti. Async ellenőrzi a szálon kívüli; entitások ketyegnek.",
|
||||
"Block Entities": "Entitások blokkolása",
|
||||
"Shulker Boxes": "Shulker dobozok",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Újrafelhasználja az ülepített dobozokat; a fedél mozgása animált marad.",
|
||||
"Signs": "Jelek",
|
||||
"Reuses sign text until text, light, or font changes.": "Újra használja a jelszöveget, amíg a szöveg, a fény vagy a betűtípus nem változik.",
|
||||
"Decorated Pots": "Díszített edények",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Újrafelhasználja a csendes edényeket; törje meg az ingadozást, és a sherd változások újrarajzolják.",
|
||||
"Bells": "Harangok",
|
||||
"Reuses still bells; ringing stays animated.": "Újrafelhasználja a állóharangokat; a csengetés animált marad.",
|
||||
"Chests": "Ládák",
|
||||
"Reuses closed chests; moving lids stay animated.": "A zárt ládákat újrafelhasználja; mozgó fedelek animáltak maradnak.",
|
||||
"Banners": "Bannerek",
|
||||
"Reuses poles; flags and pattern layers still update.": "Az oszlopok újrafelhasználása; zászlók és mintarétegek továbbra is frissülnek.",
|
||||
"Copper Golem Statues": "Réz gólem szobrok",
|
||||
"Reuses statue geometry until its block state changes.": "Újra felhasználja a szobor geometriáját, amíg a blokk állapota meg nem változik.",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Minden levél arca. Vanília díszes levelei.",
|
||||
"Distant Plant Dither": "Távoli Plant Dither",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "A nagy felbontású csomagokban lévő finom növényeket távol tartja a fakulástól.",
|
||||
"Screenshot Quality": "Képernyőkép minősége",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2 képernyőképek a játék felbontásának sokszorosával. A magasabb tovább tart.",
|
||||
"Frame Generation": "Keretgenerálás",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Egy-egy kitalált keretet mutat a két valódi között: simább, kicsit több késés.",
|
||||
"Shadow Cache": "Árnyék gyorsítótár",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Újra felhasználja a csomag árnyéktérképét, amikor a nap, a kilátás és a világ megegyezik. Alapértelmezés szerint ki.",
|
||||
"Find BlueMap": "Keresse meg a BlueMap-et",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "A szervereken megkeresi a BlueMap webtérképüket a távoli terepre. Következő csatlakozás.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Milyen távolról töltik le a BlueMap csempéket. Következő csatlakozás.",
|
||||
"Synchronous": "Szinkron",
|
||||
"Asynchronous": "Aszinkron",
|
||||
"Solid": "Szilárd",
|
||||
"Solid Aggressive": "Szilárd agresszív",
|
||||
"Hollow": "Üreges",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "A lombkorona belsejében lévő levelek szilárdan rajzolódnak ki: kevesebb átlátszó réteg.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Az egyik nyitott oldalu levelek is tömörek: gyorsabbak, vaskosabb élek.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Leejti az arcokat a levelek közé: gyorsabb, de a rések átlátszanak.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Előző nyerőgép",
|
||||
"Hotbar: Next Slot": "Hotbar: Következő Slot",
|
||||
"horizon.agent.connected.one": "Csatlakozva: 1 ügynöki kapcsolat",
|
||||
"horizon.agent.connected.many": "Csatlakozva: %s ügynökkapcsolatok",
|
||||
"horizon.agent.connected.one_ago": "Csatlakozva: 1 ügynökkapcsolat, az utolsó parancs %s másodperce",
|
||||
"horizon.agent.connected.many_ago": "Csatlakozva: %s ügynökkapcsolat, utolsó parancs %s másodperce",
|
||||
"horizon.agent.waiting": "Ügynökre vár.",
|
||||
"horizon.agent.off": "Ki: az ügynökök nem érik el ezt a játékot.",
|
||||
"horizon.agent.session_opened": "Erre a munkamenetre a játékot elindító program nyitotta meg.",
|
||||
"horizon.agent.socket": "Socket: %s (másolja a részleteket a teljes elérési úthoz)",
|
||||
"horizon.agent.local_only": "Csak helyi: ügynökök ezen a számítógépen, soha a hálózaton.",
|
||||
"Auto (%s nits)": "Automatikus (%s nits)",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "A modelleket átlátszatlan blokkok mögé rejti. Az entitások frissítése. Egyedi csomagokkal.",
|
||||
"horizon.extras.repro_keys": "Felvételi parancsikonok",
|
||||
"horizon.extras.repro_keys.tooltip": "Az F3+R elindítja vagy leállítja a repro felvételt, és az F3+E írja a kötegét. Alapértelmezés szerint kikapcsolva: a felvétel a világot másolja.",
|
||||
"horizon.agent.record_session": "Indítsa el a felvételi munkamenetet",
|
||||
"horizon.agent.record_stop": "Állítsa le a rögzítési munkamenetet",
|
||||
"horizon.agent.export_repro": "Repro csomag exportálása",
|
||||
"horizon.repro.started": "A felvétel elkezdődött; Az F3+R leállítja és kiírja a köteget.",
|
||||
"horizon.repro.stopped": "Felvétel leállítva: %s",
|
||||
"horizon.repro.bundle": "Repro csomag: %s",
|
||||
"horizon.repro.no_recording": "Még nincs exportálható felvétel.",
|
||||
"horizon.repro.replay_started": "Újrajátszás elkezdődött: %s",
|
||||
"horizon.pack.addInfo": "Adjon hozzá egy erőforráscsomagot a Modrinthtól",
|
||||
"horizon.pack.addWorking": "Kérdezem Modrinthot...",
|
||||
"horizon.pack.addNone": "Nem található erőforráscsomag",
|
||||
"horizon.pack.addTitle": "Adjon hozzá egy erőforráscsomagot",
|
||||
"horizon.pack.install": "Telepítés",
|
||||
"horizon.pack.installing": "Telepítés",
|
||||
"horizon.pack.installed": "Telepítve",
|
||||
"horizon.pack.search": "Keresés",
|
||||
"horizon.pack.searchHint": "Modrinth keresése...",
|
||||
"horizon.pack.filters": "Szűrők",
|
||||
"horizon.pack.filter.realistic": "Reális",
|
||||
"horizon.pack.filter.simplistic": "Leegyszerűsítő",
|
||||
"horizon.pack.filter.vanillaLike": "Vaníliaszerű",
|
||||
"horizon.pack.addFailed": "A csomagot nem sikerült hozzáadni: %s",
|
||||
"horizon.extras.header.cosmetics": "Kozmetikumok",
|
||||
"horizon.extras.cosmetics_mine": "A kozmetikumom",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Rejtett: minden Horizon játékos lát téged a kozmetikumai nélkül, beleértve Önt is. Ugyanaz a kapcsoló, mint az indító gardróbjában.",
|
||||
"horizon.extras.cosmetics_others": "Más játékosok kozmetikumai",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Amit a többi Horizon játékos visel: mindent, csak a névcímkék stílusát, vagy semmit. Rejtett, a játék soha nem kérdezi, mit viselnek.",
|
||||
"horizon.extras.cosmetics_auras": "Mások aurái és nyomai",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Más játékosok által viselt aurák és nyomvonalak. Alapértelmezés szerint ki.",
|
||||
"horizon.extras.cosmetics.shown": "Megjelenítve",
|
||||
"horizon.extras.cosmetics.hidden": "Rejtett",
|
||||
"horizon.extras.cosmetics.name_tags": "Csak névcímkék"
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
{
|
||||
"horizon.options.controller": "Controllore",
|
||||
"horizon.options.vibration": "Vibrazione",
|
||||
"horizon.options.adaptive_triggers": "Trigger adattivi",
|
||||
"horizon.options.lightbar": "Barra luminosa",
|
||||
"horizon.options.look_sensitivity": "Sensibilità visuale",
|
||||
"horizon.options.invert_x": "Inverti visuale X",
|
||||
"horizon.options.invert_y": "Inverti visuale Y",
|
||||
"horizon.options.sneak_button": "Pulsante per accovacciarsi",
|
||||
"horizon.options.move_deadzone": "Zona morta del movimento",
|
||||
"horizon.options.look_deadzone": "Zona morta della visuale",
|
||||
"horizon.options.button_glyphs": "Glifi dei pulsanti",
|
||||
"horizon.options.menu_navigation": "Navigazione nel menu",
|
||||
"horizon.options.on_screen_keyboard": "Tastiera su schermo",
|
||||
"horizon.options.chat_delay": "Ritardo nella chat",
|
||||
"horizon.options.lightbar.health": "Salute",
|
||||
"horizon.options.lightbar.dimension": "Dimensione",
|
||||
"horizon.options.glyphs.auto": "Automatico",
|
||||
"horizon.options.menu_focus": "Concentrarsi",
|
||||
"horizon.options.menu_pointer": "Puntatore",
|
||||
"horizon.options.keyboard.auto": "Automatico",
|
||||
"horizon.options.keyboard.steam": "Vapore",
|
||||
"horizon.options.unavailable": "Questa funzionalità non è ancora disponibile in Horizon.",
|
||||
"horizon.options.search": "Cerca impostazioni...",
|
||||
"horizon.pages.mods": "Mod",
|
||||
"horizon.pages.shader_packs": "Pacchetti shader",
|
||||
"horizon.pages.shader_options": "Opzioni dello shader",
|
||||
"horizon.pages.controller": "Impostazioni del controller",
|
||||
"horizon.pages.controller_binds": "Il controller si lega",
|
||||
"horizon.pages.extras": "Extra di Horizon",
|
||||
"horizon.pages.settings": "Impostazioni di Horizon",
|
||||
"horizon.pages.agent": "Agente dell'IA",
|
||||
"horizon.split.leave": "Lascia",
|
||||
"horizon.pages.split_settings": "Impostazioni schermo diviso",
|
||||
"horizon.pages.split_profiles": "Chi sta giocando?",
|
||||
"horizon.pages.split_setup": "Configurazione dello schermo diviso",
|
||||
"horizon.pages.music": "Musica",
|
||||
"horizon.pages.music.menu": "Menù",
|
||||
"horizon.pages.music.overworld": "Sopramondo",
|
||||
"horizon.pages.music.creative": "Creativo",
|
||||
"horizon.pages.music.underwater": "Sott'acqua",
|
||||
"horizon.pages.music.end": "La fine",
|
||||
"horizon.pages.music.boss": "Capo",
|
||||
"horizon.pages.music.credits": "Crediti",
|
||||
"horizon.pages.music.overworld_biomes": "Biomi del mondo esterno",
|
||||
"horizon.pages.music.nether_biomes": "Biomi inferiori",
|
||||
"horizon.pages.music.end_biomes": "Fine dei biomi",
|
||||
"horizon.pages.music.other": "Altra musica",
|
||||
"horizon.pages.music.empty": "Nessuna traccia musicale: i suoni del gioco non vengono caricati.",
|
||||
"horizon.pages.music.tooltip": "Le tracce integrate, in base alla situazione: riproducine una adesso o disattivane qualcuna in modo che il gioco smetta di selezionarla.",
|
||||
"horizon.pages.settings.tooltip": "Impostazioni proprie di Horizon: grafica, controller, extra e mod.",
|
||||
"horizon.pages.graphics.tooltip": "Grafica, semplicemente: ogni impostazione dice quanto costa.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Ogni impostazione grafica, inclusa quella di Horizon, con il relativo costo.",
|
||||
"horizon.pages.controller.tooltip": "Controller: pulsanti, levette, vibrazione e navigazione nei menu.",
|
||||
"horizon.pages.extras.tooltip": "Aggiunte opzionali, tutte disattivate per impostazione predefinita: il gioco rimane vanilla finché non ne accendi uno.",
|
||||
"horizon.pages.agent.tooltip": "Lascia che il tuo agente AI giochi: client Horizon-ctl e MCP su questo computer.",
|
||||
"horizon.pages.split_profiles.tooltip": "Chi gioca: un profilo conservato in questa cartella del gioco.",
|
||||
"horizon.pages.split_setup.tooltip": "Le finestre, i monitor e chi gioca dove.",
|
||||
"horizon.pages.shader_packs.tooltip": "Pacchetti shader dalla cartella shaderpacks: quelli di Horizon e quelli nel formato shaders/. Si corre uno alla volta.",
|
||||
"horizon.pages.mods.tooltip": "Mod realizzati per Horizon, dalla cartella Horizon-Mods. Ciascuno viene eseguito in una sandbox e rimane spento finché non lo accendi.",
|
||||
"horizon.pages.open.graphics": "Grafica...",
|
||||
"horizon.pages.open.graphics_advanced": "Grafica avanzata...",
|
||||
"horizon.pages.open.shader_packs": "Pacchetti Shader...",
|
||||
"horizon.pages.open.controller": "Impostazioni del controller...",
|
||||
"horizon.pages.open.split_setup": "Schermo diviso...",
|
||||
"horizon.pages.open.extras": "Extra...",
|
||||
"horizon.pages.open.mods": "Mod...",
|
||||
"horizon.pages.open.agent": "Agente dell'IA...",
|
||||
"horizon.pages.open.horizon_graphics": "Grafica di Horizon...",
|
||||
"horizon.pages.open.controller_binds": "Il controller si lega...",
|
||||
"horizon.pages.open.horizon_settings": "Impostazioni di Horizon...",
|
||||
"horizon.pages.open.music": "Musica...",
|
||||
"horizon.mods.empty": "Nessuna mod: inserisci i file .hmod nella cartella Horizon-Mods",
|
||||
"horizon.mods.stopped": " (Fermato)",
|
||||
"horizon.mods.not_built": " (Non costruito)",
|
||||
"horizon.mods.dev": " (Sviluppo)",
|
||||
"horizon.mods.settings": "Impostazioni",
|
||||
"horizon.mods.in_world": " (lasciare che il mondo cambi)",
|
||||
"horizon.mods.open_folder": "Apri la cartella Mod",
|
||||
"horizon.shader_packs.empty": "Nessun pacchetto shader: inseriscili nella cartella shaderpacks",
|
||||
"horizon.shader_options.open": "Opzioni shader...",
|
||||
"horizon.shader_options.profile": "Profilo",
|
||||
"horizon.shader_options.loading": "Leggendo le opzioni del pacchetto shader...",
|
||||
"horizon.agent.copy_details": "Copia i dettagli della connessione",
|
||||
"horizon.multiplayer.join": "Partecipa",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Annulla riconnessione",
|
||||
"horizon.multiplayer.join_friend": "Unisciti a un amico",
|
||||
"horizon.multiplayer.invite_code": "Codice invito",
|
||||
"horizon.multiplayer.relay": "Relè (opzionale)",
|
||||
"Preset": "Preimpostato",
|
||||
"LOD Distance": "Distanza LOD",
|
||||
"Far Detail": "Dettaglio lontano",
|
||||
"Cave Culling": "Abbattimento delle caverne",
|
||||
"Entity Culling": "Eccitazione di entità",
|
||||
"Chunk Updates": "Aggiornamenti a blocchi",
|
||||
"Upload Budget": "Carica budget",
|
||||
"Sky": "Cielo",
|
||||
"Fog": "Nebbia",
|
||||
"Weather": "Meteo",
|
||||
"Animated Textures": "Texture animate",
|
||||
"Present Mode": "Modalità presente",
|
||||
"Frame Pacing": "Ritmo dei fotogrammi",
|
||||
"Worker Threads": "Discussioni sui lavoratori",
|
||||
"Resolution Scale": "Scala di risoluzione",
|
||||
"Anti-aliasing": "Anti-alias",
|
||||
"Paper White": "Carta Bianca",
|
||||
"Peak Brightness": "Luminosità massima",
|
||||
"Black Level": "Livello nero",
|
||||
"UI Brightness": "Luminosità dell'interfaccia utente",
|
||||
"Battery Mode": "Modalità batteria",
|
||||
"Graphics API": "API grafica",
|
||||
"Translucent Batching": "Dosaggio traslucido",
|
||||
"Performance Overlay": "Sovrapposizione delle prestazioni",
|
||||
"Full Detail Distance": "Distanza dettagliata completa",
|
||||
"On": "Su",
|
||||
"Off": "Spento",
|
||||
"Unlimited": "Illimitato",
|
||||
"Auto": "Automatico",
|
||||
"Custom": "Personalizzato",
|
||||
"Performance": "Prestazioni",
|
||||
"Balanced": "Equilibrato",
|
||||
"Quality": "Qualità",
|
||||
"Ultra Performance": "Prestazioni ultra",
|
||||
"Native": "Nativo",
|
||||
"Full": "Pieno",
|
||||
"Reduced": "Ridotto",
|
||||
"Efficient": "Efficiente",
|
||||
"Precise": "Preciso",
|
||||
"Auto (VSync)": "Automatico (sincronizzazione verticale)",
|
||||
"Mailbox": "Cassetta postale",
|
||||
"Immediate": "Immediato",
|
||||
"Vulkan": "Vulcano",
|
||||
"DirectX 12": "DirectX12",
|
||||
"FPS and Graph": "FPS e grafico",
|
||||
"TAA": "TAAA",
|
||||
"No cost": "Nessun costo",
|
||||
"Low cost": "Basso costo",
|
||||
"Medium cost": "Costo medio",
|
||||
"High cost": "Costo elevato",
|
||||
"Saves time": "Risparmia tempo",
|
||||
"Graphics": "Grafica",
|
||||
"Done": "Fatto",
|
||||
"This feature is not yet available in Horizon.": "Questa funzionalità non è ancora disponibile in Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Il tuo display o sistema non offre l'uscita HDR.",
|
||||
"Turn HDR on to use it.": "Attiva l'HDR per usarlo.",
|
||||
"Your GPU or graphics API doesn't support it.": "La tua GPU o API grafica non lo supporta.",
|
||||
"%s chunks": "%s pezzi",
|
||||
"%s per frame": "%s per fotogramma",
|
||||
"%s MiB/frame": "%s MiB/fotogramma",
|
||||
"%s KiB/frame": "%s KiB/telaio",
|
||||
"Auto (%s)": "Automatico (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Gamma di terreno dettagliata. LOD La distanza può estendere ulteriormente i mondi locali.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Terreni distanti e grossolani nei mondi locali. Le GPU integrate si fermano a 128 blocchi.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Ridotti disegna terreni lontani più ruvidi: più veloce su questa GPU integrata.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Il ridotto disegna terreni lontani più grossolani, per le GPU rallenta.",
|
||||
"Field of view. Wider shows more terrain at once.": "Campo visivo. Più ampio mostra più terreno contemporaneamente.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Limita i fotogrammi al secondo; un cappuccio consente di risparmiare energia e calore.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Attende il refresh del display: niente strappi, poca latenza.",
|
||||
"Size of menus and the HUD.": "Dimensioni dei menu e dell'HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Imposta insieme le opzioni seguenti. Cambiarne uno lo rende personalizzato.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "La lava, il fuoco e la luce del sole brillano più del bianco. Necessita di un display HDR.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Il tuo display è nero più scuro. 0 per OLED; Gli LCD si illuminano leggermente.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Come diventano brillanti le luci. Auto: il picco segnalato dal sistema.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Come appaiono la carta bianca e i menu. Auto: il tuo desktop è bianco.",
|
||||
"How bright the HUD and menus are, against paper white.": "Quanto sono luminosi l'HUD e i menu, rispetto al bianco della carta.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Fino a che punto mob, raccolti e macchine continuano a funzionare. Tempo della CPU.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Quanto lontano vengono disegnati mob e oggetti, come percentuale del normale.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Salta il terreno nascosto dietro le sezioni chiuse. Spendere costa molto.",
|
||||
"Hides entities behind opaque blocks.": "Nasconde le entità dietro blocchi opachi.",
|
||||
"No entity occlusion culling.": "Nessuna selezione per l'occlusione delle entità.",
|
||||
"CPU occlusion test runs this frame.": "Il test di occlusione della CPU esegue questo frame.",
|
||||
"Occlusion checks run on a worker thread.": "I controlli di occlusione vengono eseguiti su un thread di lavoro.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Sezioni mesh caricate per fotogramma. La parte inferiore è più fluida durante il caricamento.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Ombre morbide negli angoli. Costa solo tempo per la costruzione del blocco.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Copie più piccole di texture a blocchi per terreni distanti: meno luccichio.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Unisce i colori dell'erba e dell'acqua nei biomi durante la costruzione dei blocchi.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Livello nuvola: disattivato, piatto (veloce) o 3D (fantasioso).",
|
||||
"How far clouds reach, in chunks.": "Quanto lontano arrivano le nuvole, in blocchi.",
|
||||
"Draws the sky, sun, moon and stars.": "Disegna il cielo, il sole, la luna e le stelle.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Nebbia a distanza. Off mostra il limite del mondo caricato.",
|
||||
"Draws rain and snow.": "Attira pioggia e neve.",
|
||||
"How far rain and snow fall around you.": "Quanta pioggia e neve cadono intorno a te.",
|
||||
"How many particles effects spawn.": "Quanti effetti particellari vengono generati.",
|
||||
"Round shadows under mobs and items.": "Ombre rotonde sotto mob e oggetti.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Foglie con spazi trasparenti. Off li attira solidi.",
|
||||
"Sorts water and glass more exactly behind each other.": "Ordina l'acqua e il bicchiere in modo più preciso uno dietro l'altro.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Quale pezzo ricostruito attende il frame: nessuno, nelle vicinanze o il tuo.",
|
||||
"Animates water, lava, fire and other moving textures.": "Anima acqua, lava, fuoco e altre texture in movimento.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Pixel mondiali per pixel dello schermo. Sotto il 100% la GPU è più veloce.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Leviga i bordi frastagliati. FXAA: più morbido. TAA: le cose più fluide e in movimento possono trascinarsi.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Rende il mondo più piccolo e lo ricostruisce con TAA. Più veloce, più morbido.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "Uscita HDR su un display HDR: lava, fuoco e luce solare brillano più luminosi.",
|
||||
"How frames reach the screen while VSync is off.": "In che modo i frame raggiungono lo schermo mentre VSync è disattivato.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Sonni efficienti fino al fotogramma successivo; Rotazioni precise, come la vaniglia.",
|
||||
"Fills the screen (borderless).": "Riempie lo schermo (senza bordi).",
|
||||
"Brightens dark places.": "Illumina i luoghi bui.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Riduce il frame rate quando è ridotto al minimo o anche inattivo.",
|
||||
"Sways the view while walking.": "Oscilla la vista mentre si cammina.",
|
||||
"Fades newly loaded chunks in.": "Dissolve i blocchi appena caricati.",
|
||||
"Darkens the screen's edges.": "Scurisce i bordi dello schermo.",
|
||||
"Shows when the world saves.": "Mostra quando il mondo salva.",
|
||||
"Shows attack cooldown.": "Mostra il tempo di recupero dell'attacco.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "Limite di 60 FPS (30 nei menu o senza messa a fuoco), metà dei lavoratori: meno energia.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Fili che generano, accendono e intrecciano spezzoni. Prossimo inizio.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, quindi DirectX 12 (Windows) o OpenGL (Linux) dove fallisce. Prossimo inizio.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Acqua e vetro in un unico buffer: meno chiamate, un po' più di VRAM. Prossimo inizio.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Dati mesh inviati alla GPU per frame. La parte inferiore è più fluida durante il caricamento.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Frequenza fotogrammi e durata dei fotogrammi durante il gioco, con un grafico.",
|
||||
"horizon.multiplayer.finding_friend": "Trovare il mondo del tuo amico...",
|
||||
"horizon.multiplayer.not_signed_in": "Questo server è in modalità online. Accedi con il tuo account Microsoft nel launcher Horizon per iscriverti.",
|
||||
"horizon.multiplayer.invalid_address": "Indirizzo del server non valido: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Collegato direttamente al tuo amico...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Connesso al tuo amico tramite il relè...",
|
||||
"General": "Generale",
|
||||
"Advanced": "Avanzato",
|
||||
"Undo": "Annulla",
|
||||
"Search options": "Opzioni di ricerca",
|
||||
"Shortcut": "Scorciatoia",
|
||||
"Distance": "Distanza",
|
||||
"Display": "Visualizzazione",
|
||||
"Interface": "Interfaccia",
|
||||
"Sky and weather": "Cielo e tempo",
|
||||
"Lighting and entities": "Illuminazione ed entità",
|
||||
"Textures": "Trame",
|
||||
"Image": "Immagine",
|
||||
"Chunk loading": "Caricamento di blocchi",
|
||||
"Culling": "Abbattimento",
|
||||
"Power": "Potenza",
|
||||
"Frames": "Cornici",
|
||||
"Overlay": "Sovrapposizione",
|
||||
"Output": "Uscita",
|
||||
"Brightness": "Luminosità",
|
||||
"How far you see, the window and the HUD.": "Quanto lontano vedi, la finestra e l'HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Il cielo e il tempo, l'illuminazione, le trame e l'immagine.",
|
||||
"Chunk loading, culling and power use.": "Caricamento di blocchi, selezione e consumo di energia.",
|
||||
"How frames reach the screen, and the performance overlay.": "Come i fotogrammi raggiungono lo schermo e la sovrapposizione delle prestazioni.",
|
||||
"Brighter-than-white output on an HDR display.": "Uscita più luminosa del bianco su un display HDR.",
|
||||
"Results for \"%s\"": "Risultati per \"%s\"",
|
||||
"No settings match.": "Nessuna impostazione corrisponde.",
|
||||
"horizon.title.not_affiliated": "Non affiliato con Mojang o Microsoft",
|
||||
"horizon.extras.header.lighting": "Illuminazione",
|
||||
"horizon.extras.header.third_person": "Terza persona",
|
||||
"horizon.extras.header.particles": "Particelle",
|
||||
"horizon.extras.header.inventory": "Inventario",
|
||||
"horizon.extras.header.chat": "Chatta",
|
||||
"horizon.extras.header.multiplayer": "Multigiocatore",
|
||||
"horizon.extras.header.sound": "Suono",
|
||||
"horizon.extras.header.menus": "Menù",
|
||||
"horizon.extras.fullbright": "Piena luminosità",
|
||||
"horizon.extras.dynamic_lights": "Luci dinamiche",
|
||||
"horizon.extras.own_name_tag": "Proprio cartellino con il nome",
|
||||
"horizon.extras.hide_block_particles": "Nascondi le particelle di rottura del blocco",
|
||||
"horizon.extras.auto_reconnect": "Riconnessione automatica",
|
||||
"horizon.extras.ui_sound_reserve": "Mantieni i canali per i suoni dell'interfaccia utente",
|
||||
"horizon.extras.music_page": "Pagina Musica",
|
||||
"horizon.extras.mouse_tweaks": "Controlli per le modifiche del mouse",
|
||||
"horizon.extras.mouse_tweaks_wheel": "La rotella di scorrimento sposta gli elementi",
|
||||
"horizon.extras.sort": "Ordina i pulsanti",
|
||||
"horizon.extras.sort_key": "Chiave di ordinamento",
|
||||
"horizon.extras.no_ai": "Nessuna intelligenza artificiale",
|
||||
"horizon.extras.agent_control": "Controllo degli agenti",
|
||||
"horizon.extras.discord_presence": "Presenza ricca di discordia",
|
||||
"horizon.extras.splitscreen": "Schermo diviso",
|
||||
"horizon.extras.chat_animation": "Chiacchierata fluida",
|
||||
"horizon.extras.chat_indicator": "Indicatore di messaggio",
|
||||
"horizon.extras.wheel_cycles_settings": "La rotella di scorrimento modifica le impostazioni",
|
||||
"horizon.extras.pack_browser": "Browser del pacchetto di risorse",
|
||||
"horizon.extras.fullbright.tooltip": "Mostra blocchi ed entità in piena luce, anche nelle aree buie.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Torce e altre fonti di luce tenute, lasciate cadere o in fiamme illuminano i blocchi attorno a loro.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Mostra il tuo nome sopra la tua testa in terza persona, così come lo vedono gli altri giocatori. Nascosto quando il server nasconde i nomi della tua squadra o sei invisibile.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Nasconde le particelle di detriti che escono da un blocco rotto e si sbriciolano da quello che hai estratto. Altre particelle rimangono.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Rientra in un server dopo una perdita di connessione o un riavvio, mai dopo un kick o un ban.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "I suoni nel mondo lasciano liberi 16 dei 247 canali audio, quindi un'ondata di essi (una grande esplosione) non può silenziare i clic dei menu.",
|
||||
"horizon.extras.music_page.tooltip": "Musica e suoni hanno una pagina Musica...: riproduci subito qualsiasi traccia integrata o disattivane una in modo che il gioco smetta di selezionarla. Non cambia nulla finché non disattivi una traccia.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Il trascinamento con il tasto destro ne posiziona uno per slot, il trascinamento con il tasto sinistro raccoglie l'oggetto trattenuto, Maiusc + il trascinamento con il tasto sinistro sposta ogni slot.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Giù manda un oggetto dello slot dall'altra parte, su ne riporta uno indietro.",
|
||||
"horizon.extras.sort.tooltip": "Ordina l'inventario, i forzieri, i barili, i forzieri finali e le scatole degli shulker in un ordine di schede creativo, unendo le pile.",
|
||||
"horizon.extras.sort_key.tooltip": "Ordina la parte sotto il cursore. Fare clic, quindi premere un tasto; La fuga lo scioglie.",
|
||||
"horizon.extras.no_ai.tooltip": "Nasconde e disattiva ogni funzionalità AI: controllo agente, Horizon-ctl e server MCP. Anche l'interruttore del launcher.",
|
||||
"horizon.extras.agent_control.tooltip": "Il tuo agente AI può giocare a questo gioco tramite Horizon-ctl o un client MCP, solo su questo computer.",
|
||||
"horizon.extras.discord_presence.tooltip": "Mostra Playing Horizon sul tuo profilo Discord mentre Discord è in esecuzione: menu, giocatore singolo o multiplayer, la dimensione e il tempo giocato. Mai il nome di un mondo o l'indirizzo di un server.",
|
||||
"Calibrate...": "Calibra...",
|
||||
"Calibrate Anyway...": "Calibra comunque...",
|
||||
"Use Automatic Setup": "Utilizza l'impostazione automatica",
|
||||
"Shortcut: %s": "Scorciatoia: %s",
|
||||
"Next": "Avanti",
|
||||
"HDR Calibration %s/%s: %s": "Calibrazione HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Imposta le tre immagini sottostanti a schermo intero, un'immagine di prova alla volta.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Una chiave che apre questa schermata del gioco. All'inizio non legato.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Il display non ha indicato la luminosità: calibralo per configurarlo.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Già predisposto per il tuo display. Calibrare solo se l'HDR sembra sbagliato.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Riporta la luminosità di picco e il bianco della carta alla configurazione del display.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Configura per il tuo display. Lasciare su Auto a meno che non sembri sbagliato.",
|
||||
"Lower it until the logo just disappears into the black.": "Abbassalo finché il logo non scompare nel nero.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Sollevalo finché il logo non scompare nel quadrato luminoso.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Rendi la carta bianca, comoda da guardare a lungo, nella luce in cui giochi.",
|
||||
"horizon.splitscreen.profiles.forget": "Dimentica",
|
||||
"horizon.splitscreen.profiles.create": "Crea",
|
||||
"horizon.splitscreen.profiles.you": " - tu",
|
||||
"horizon.splitscreen.profiles.forget_named": "Dimentica %s",
|
||||
"horizon.splitscreen.profiles.previous": "Precedente",
|
||||
"horizon.splitscreen.profiles.next": "Avanti",
|
||||
"horizon.splitscreen.profiles.new_button": "Nuovo profilo...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Dimentica il profilo?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Esce dall'elenco di questa istanza.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "I progressi e le impostazioni del mondo vengono mantenuti.",
|
||||
"horizon.splitscreen.profiles.new_title": "Nuovo profilo",
|
||||
"horizon.splitscreen.profiles.name_hint": "Nome (da 3 a 16 lettere, cifre, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Cambia profilo",
|
||||
"horizon.splitscreen.profiles.offline_note": "I profili offline appartengono a questa istanza",
|
||||
"horizon.splitscreen.profiles.move": "Muoviti dove giochi",
|
||||
"horizon.splitscreen.profiles.move_place": "Spostati dove giochi: %s",
|
||||
"horizon.friends.title": "Gioca con gli amici",
|
||||
"horizon.friends.hint": "Gli amici incollano il codice dopo il pulsante Amici della schermata del titolo, in Connessione diretta o nella pagina di riproduzione del launcher.",
|
||||
"horizon.friends.copied": "Copiato",
|
||||
"horizon.friends.copy_code": "Copia codice",
|
||||
"horizon.friends.stop_sharing": "Interrompi la condivisione",
|
||||
"horizon.friends.none_yet": "Nessun amico qui ancora.",
|
||||
"horizon.friends.here_now": "Ecco adesso: %s",
|
||||
"horizon.friends.share_intro": "Condividi questo mondo dietro un codice di invito: si apre alla LAN e gli amici su Internet si uniscono con il codice. Nessun nostro server è coinvolto.",
|
||||
"horizon.friends.start_sharing": "Inizia a condividere",
|
||||
"horizon.friends.troubleshooting": "Risoluzione dei problemi...",
|
||||
"horizon.friends.stopped": "Interrotta la condivisione del codice di invito.",
|
||||
"horizon.friends.short_code_changed": "Qualcuno continuava a provare un codice funzione sbagliato, quindi è cambiato.",
|
||||
"horizon.friends.wrong_code": "Qualcuno ha provato un codice sbagliato.",
|
||||
"horizon.friends.code_ready": "Il tuo codice di invito è pronto: Menu di gioco, Gioca con gli amici. Copialo e invialo ai tuoi amici.",
|
||||
"horizon.friends.share_failed": "Impossibile condividere il mondo: %s",
|
||||
"horizon.friends.finding_route": "Scoprire come gli amici possono contattarti...",
|
||||
"horizon.osk.shift": "Cambio",
|
||||
"horizon.osk.space": "Spazio",
|
||||
"horizon.osk.delete": "Elimina",
|
||||
"horizon.osk.type": "Digitare",
|
||||
"horizon.osk.symbols": "Simboli",
|
||||
"horizon.osk.close": "Chiudi",
|
||||
"horizon.extras.chat_animation.tooltip": "Una nuova linea di chat scorre da sinistra mentre si dissolve, invece di apparire intera.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barra colorata a sinistra di una chat line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La rotella di scorrimento sopra un pulsante nelle impostazioni ne modifica il valore, come in Vanilla. Spento, la rotella scorre solo l'elenco.",
|
||||
"horizon.extras.pack_browser.tooltip": "Seleziona pacchetti di risorse ottiene un pulsante Aggiungi che cerca Modrinth e installa un pacchetto nella cartella dei pacchetti. Spento, il gioco non contatta mai Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In un mondo, un altro controller può unirsi come nuovo giocatore: premi A quando richiesto, quindi scegli un profilo e dove giocare.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorare %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un monitor",
|
||||
"horizon.splitscreen.setup.window": "Finestra %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nuova finestra",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Riempi %s",
|
||||
"horizon.splitscreen.setup.done": "Fatto",
|
||||
"horizon.splitscreen.setup.on": "Su",
|
||||
"horizon.splitscreen.setup.off": "Spento",
|
||||
"horizon.splitscreen.setup.player": "Giocatore %s",
|
||||
"horizon.splitscreen.setup.revert": "Ripristina",
|
||||
"horizon.splitscreen.setup.apply": "Applicare",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Nessuno gioca nella Finestra 1: si nasconde",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Nessuno gioca nella finestra %s: si chiude su Applica",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Nessuno gioca nella Finestra %s: non si apre",
|
||||
"horizon.splitscreen.setup.warning.on_main": "La finestra %s riempie il monitor della Finestra 1: i suoi giocatori giocano nella Finestra 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Il monitor della Finestra %s non è collegato: i suoi giocatori giocano nella Finestra 1",
|
||||
"horizon.splitscreen.setup.status.off": "Lo schermo diviso è disattivato.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Accendilo, quindi premi un pulsante su un altro controller in un mondo.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Premi A per iscriverti e scegliere un profilo.",
|
||||
"horizon.splitscreen.setup.status.idle": "Schermo diviso inattivo",
|
||||
"horizon.splitscreen.setup.status.alone": "Nessun altro giocatore locale.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "I controlli Solo e il feedback del controller rimangono invariati.",
|
||||
"horizon.splitscreen.setup.window_empty": "Finestra %s: vuota",
|
||||
"horizon.splitscreen.setup.note.select": "Fai clic su un giocatore o trascinalo su una finestra o su un monitor.",
|
||||
"horizon.splitscreen.setup.note.apply": "Applica per giocare in questo modo, Ripristina per annullare.",
|
||||
"horizon.splitscreen.setup.note.place": "Dove dovrebbe giocare %s?",
|
||||
"horizon.splitscreen.setup.this_player": "questo giocatore",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Lo schermo diviso si spegne una volta che gli altri se ne sono andati (il loro menu di pausa).",
|
||||
"horizon.splitscreen.setup.window_place": "Finestra %s (%s di %s)",
|
||||
"horizon.splitscreen.setup.names": "Nomi",
|
||||
"horizon.splitscreen.setup.identify": "Identificare",
|
||||
"horizon.splitscreen.setup.layout.two": "2 condivisione: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 condivisione: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "Automatico",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Fianco a fianco",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Sopra e sotto",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Uno largo in alto",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Uno alto a sinistra",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Fianco a fianco",
|
||||
"horizon.splitscreen.setup.help.player": "Fai clic o trascina %s per spostarli.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (affiancati quando la finestra è ampia). Premere per modificare.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Premere per modificare.",
|
||||
"horizon.splitscreen.setup.help.switch": "Schermo diviso per questo computer, attivato o disattivato.",
|
||||
"horizon.splitscreen.setup.help.identify": "La vista di ogni giocatore mostra in quale finestra si trova.",
|
||||
"horizon.splitscreen.setup.help.names": "Il nome di ogni giocatore nell'angolo della vista.",
|
||||
"horizon.splitscreen.setup.help.revert": "Annulla le modifiche non ancora applicate.",
|
||||
"horizon.splitscreen.setup.help.apply": "Gioca come mostra la mappa.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Non è cambiato ancora nulla.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Rinunciare Pad",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Controller collegato",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Controller disconnesso",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Ricollegalo o premi un pulsante su un altro",
|
||||
"horizon.splitscreen.prompt.join.one": "%s vuole unirsi",
|
||||
"horizon.splitscreen.prompt.join.many": "%s controllori vogliono unirsi",
|
||||
"horizon.splitscreen.prompt.join.yes": "Partecipa",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Gioca come P1",
|
||||
"horizon.splitscreen.prompt.lost": "%s: controller disconnesso",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Usane un altro",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Lascia",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Il tuo controller smette di guidarti e gli viene offerto di unirsi (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "Da %s a %s: A la lascia qui, B la rimette a posto.",
|
||||
"horizon.splitscreen.setup.note.held": "%s raccolto: sinistra/destra sceglie dove, A lascia cadere, B lo rimette a posto.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Nasconde i modelli dietro blocchi opachi. Async controlla fuori thread; le entità spuntano.",
|
||||
"Block Entities": "Blocca entità",
|
||||
"Shulker Boxes": "Scatole Shulker",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Riutilizza le scatole depositate; il movimento del coperchio rimane animato.",
|
||||
"Signs": "Segni",
|
||||
"Reuses sign text until text, light, or font changes.": "Riutilizza il testo del segno finché il testo, la luce o il carattere non cambiano.",
|
||||
"Decorated Pots": "Vasi decorati",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Riutilizza i vasi fermi; rompi l'oscillazione e le modifiche del frammento vengono ridisegnate.",
|
||||
"Bells": "Campane",
|
||||
"Reuses still bells; ringing stays animated.": "Riutilizza ancora le campane; lo squillo rimane animato.",
|
||||
"Chests": "Forzieri",
|
||||
"Reuses closed chests; moving lids stay animated.": "Riutilizza i bauli chiusi; le palpebre mobili rimangono animate.",
|
||||
"Banners": "Banner",
|
||||
"Reuses poles; flags and pattern layers still update.": "Riutilizza i pali; le bandiere e i livelli di pattern continuano ad essere aggiornati.",
|
||||
"Copper Golem Statues": "Statue Golem di rame",
|
||||
"Reuses statue geometry until its block state changes.": "Riutilizza la geometria della statua finché il suo stato di blocco non cambia.",
|
||||
"Leaf Culling": "Abbattimento delle foglie",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Ogni faccia della foglia. Le foglie fantasia della vaniglia.",
|
||||
"Distant Plant Dither": "Il tremore delle piante lontane",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Impedisce alle piante pregiate in confezioni ad alta risoluzione di sbiadire in lontananza.",
|
||||
"Screenshot Quality": "Qualità dello schermo",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "Screenshot F2 con una risoluzione pari a molte volte quella del gioco. Più alto richiede più tempo.",
|
||||
"Frame Generation": "Generazione di fotogrammi",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Mostra un fotogramma inventato tra ciascuno dei due fotogrammi reali: più fluido, con un po' più di ritardo.",
|
||||
"Shadow Cache": "Cassa dell'Ombra",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Riutilizza la mappa delle ombre del pacchetto quando sole, vista e mondo corrispondono. Disattivato per impostazione predefinita.",
|
||||
"Find BlueMap": "Trova BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Sui server, trova la mappa web BlueMap per terreni lontani. Prossima partecipazione.",
|
||||
"BlueMap Radius": "Raggio BlueMap",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "La distanza intorno a te in cui vengono scaricate le tessere BlueMap. Prossima partecipazione.",
|
||||
"Synchronous": "Sincrono",
|
||||
"Asynchronous": "Asincrono",
|
||||
"Solid": "Solido",
|
||||
"Solid Aggressive": "Solido aggressivo",
|
||||
"Hollow": "Vuoto",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Le foglie completamente all'interno della chioma diventano solide: meno strati trasparenti.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Anche le foglie con un lato aperto disegnano solidi: bordi più veloci e più grossi.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Gocce facce tra le foglie: più veloci, ma gli spazi traspare.",
|
||||
"Hotbar: Previous Slot": "Barra di accesso rapido: slot precedente",
|
||||
"Hotbar: Next Slot": "Barra di accesso rapido: slot successivo",
|
||||
"horizon.agent.connected.one": "Connesso: 1 connessione agente",
|
||||
"horizon.agent.connected.many": "Connesso: %s connessioni agente",
|
||||
"horizon.agent.connected.one_ago": "Connesso: 1 connessione agente, ultimo comando %s s fa",
|
||||
"horizon.agent.connected.many_ago": "Connesso: %s connessioni agente, ultimo comando %s s fa",
|
||||
"horizon.agent.waiting": "In attesa di un agente.",
|
||||
"horizon.agent.off": "Disattivato: gli agenti non possono raggiungere questo gioco.",
|
||||
"horizon.agent.session_opened": "Aperto per questa sessione dal programma che ha avviato il gioco.",
|
||||
"horizon.agent.socket": "Socket: %s (copia i dettagli per il percorso completo)",
|
||||
"horizon.agent.local_only": "Solo locale: agenti su questo computer, mai sulla rete.",
|
||||
"%s nits": "%s lendini",
|
||||
"Auto (%s nits)": "Auto (%s nit)",
|
||||
"%.2f nits": "%.2f lendini",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Nasconde i modelli dietro blocchi opachi. Aggiornamento delle entità. Via con i pacchetti personalizzati.",
|
||||
"horizon.extras.repro_keys": "Scorciatoie di registrazione",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R avvia o interrompe una registrazione di riproduzione e F3+E scrive il suo bundle. Disattivato per impostazione predefinita: una registrazione copia il mondo.",
|
||||
"horizon.agent.record_session": "Avvia la sessione di registrazione",
|
||||
"horizon.agent.record_stop": "Interrompi la sessione di registrazione",
|
||||
"horizon.agent.export_repro": "Esporta pacchetto di riproduzione",
|
||||
"horizon.repro.started": "La registrazione è iniziata; F3+R lo ferma e scrive il bundle.",
|
||||
"horizon.repro.stopped": "Registrazione interrotta: %s",
|
||||
"horizon.repro.bundle": "Pacchetto riproduzione: %s",
|
||||
"horizon.repro.error": "Riproduzione: %s",
|
||||
"horizon.repro.no_recording": "Non c'è ancora alcuna registrazione da esportare.",
|
||||
"horizon.repro.replay_started": "Replay iniziato: %s",
|
||||
"horizon.pack.addInfo": "Aggiungi un pacchetto di risorse da Modrinth",
|
||||
"horizon.pack.addWorking": "Chiedendo a Modrinth...",
|
||||
"horizon.pack.addNone": "Nessun pacchetto di risorse trovato",
|
||||
"horizon.pack.addTitle": "Aggiungi un pacchetto di risorse",
|
||||
"horizon.pack.install": "Installa",
|
||||
"horizon.pack.installing": "Installazione",
|
||||
"horizon.pack.installed": "Installato",
|
||||
"horizon.pack.search": "Cerca",
|
||||
"horizon.pack.searchHint": "Cerca Modrinth...",
|
||||
"horizon.pack.filters": "Filtri",
|
||||
"horizon.pack.filter.realistic": "Realistico",
|
||||
"horizon.pack.filter.simplistic": "Semplicistico",
|
||||
"horizon.pack.filter.vanillaLike": "Simile alla vaniglia",
|
||||
"horizon.pack.addFailed": "Impossibile aggiungere il pacchetto: %s",
|
||||
"horizon.extras.header.cosmetics": "Cosmetici",
|
||||
"horizon.extras.cosmetics_mine": "I miei cosmetici",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Nascosto: ogni giocatore di Horizon ti vede senza i tuoi cosmetici, incluso te. Lo stesso interruttore dell'armadio del lanciatore.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetici di altri giocatori",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Cosa indossano gli altri giocatori di Horizon: tutto, solo lo stile delle etichette con il nome o niente. Nascosto, il gioco non chiede mai cosa indossano.",
|
||||
"horizon.extras.cosmetics_auras": "Aure e tracce degli altri",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aure e tracce indossate da altri giocatori. Disattivato per impostazione predefinita.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrato",
|
||||
"horizon.extras.cosmetics.hidden": "Nascosto",
|
||||
"horizon.extras.cosmetics.name_tags": "Solo targhette con nome"
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
{
|
||||
"horizon.options.controller": "コントローラー",
|
||||
"horizon.options.vibration": "振動",
|
||||
"horizon.options.adaptive_triggers": "アダプティブトリガー",
|
||||
"horizon.options.lightbar": "ライトバー",
|
||||
"horizon.options.look_sensitivity": "視点感度",
|
||||
"horizon.options.invert_x": "視点X反転",
|
||||
"horizon.options.invert_y": "視点Y反転",
|
||||
"horizon.options.sneak_button": "スニークボタン",
|
||||
"horizon.options.move_deadzone": "移動のデッドゾーン",
|
||||
"horizon.options.look_deadzone": "視点のデッドゾーン",
|
||||
"horizon.options.button_glyphs": "ボタンのグリフ",
|
||||
"horizon.options.menu_navigation": "メニューナビゲーション",
|
||||
"horizon.options.on_screen_keyboard": "オンスクリーンキーボード",
|
||||
"horizon.options.chat_delay": "チャットの遅延",
|
||||
"horizon.options.lightbar.health": "健康",
|
||||
"horizon.options.lightbar.dimension": "次元",
|
||||
"horizon.options.glyphs.auto": "自動",
|
||||
"horizon.options.glyphs.playstation": "プレイステーション",
|
||||
"horizon.options.glyphs.nintendo": "任天堂",
|
||||
"horizon.options.menu_focus": "フォーカス",
|
||||
"horizon.options.menu_pointer": "ポインタ",
|
||||
"horizon.options.keyboard.auto": "自動",
|
||||
"horizon.options.keyboard.steam": "蒸気",
|
||||
"horizon.options.unavailable": "この機能は Horizon ではまだ利用できません。",
|
||||
"horizon.options.search": "検索設定...",
|
||||
"horizon.pages.mods": "改造",
|
||||
"horizon.pages.shader_packs": "シェーダーパック",
|
||||
"horizon.pages.shader_options": "シェーダオプション",
|
||||
"horizon.pages.controller": "コントローラーの設定",
|
||||
"horizon.pages.controller_binds": "コントローラーのバインド",
|
||||
"horizon.pages.extras": "Horizon エクストラ",
|
||||
"horizon.pages.settings": "Horizon 設定",
|
||||
"horizon.pages.agent": "AIエージェント",
|
||||
"horizon.split.leave": "休暇を取る",
|
||||
"horizon.pages.split_settings": "分割画面設定",
|
||||
"horizon.pages.split_profiles": "誰が遊んでるの?",
|
||||
"horizon.pages.split_setup": "分割画面のセットアップ",
|
||||
"horizon.pages.music": "音楽",
|
||||
"horizon.pages.music.menu": "メニュー",
|
||||
"horizon.pages.music.overworld": "オーバーワールド",
|
||||
"horizon.pages.music.creative": "クリエイティブ",
|
||||
"horizon.pages.music.underwater": "水中",
|
||||
"horizon.pages.music.end": "終わり",
|
||||
"horizon.pages.music.boss": "ボス",
|
||||
"horizon.pages.music.credits": "クレジット",
|
||||
"horizon.pages.music.overworld_biomes": "オーバーワールドのバイオーム",
|
||||
"horizon.pages.music.nether_biomes": "ネザーバイオーム",
|
||||
"horizon.pages.music.end_biomes": "エンドバイオーム",
|
||||
"horizon.pages.music.other": "その他の音楽",
|
||||
"horizon.pages.music.empty": "音楽トラックがありません: ゲームのサウンドが読み込まれていません。",
|
||||
"horizon.pages.music.tooltip": "状況別の内蔵トラック: 今すぐ 1 つを再生するか、ゲームが選択を停止するようにいずれかをオフにします。",
|
||||
"horizon.pages.settings.tooltip": "Horizon 独自の設定: グラフィックス、コントローラー、エクストラ、MOD。",
|
||||
"horizon.pages.graphics.tooltip": "グラフィックス、簡単に言うと、各設定にどれだけのコストがかかるかを示します。",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Horizon を含むすべてのグラフィック設定とそれぞれの費用。",
|
||||
"horizon.pages.controller.tooltip": "コントローラー: ボタン、スティック、振動、メニュー ナビゲーション。",
|
||||
"horizon.pages.extras.tooltip": "オプションの追加機能。デフォルトではすべてオフになっています。いずれかをオンにするまで、ゲームはバニラのままです。",
|
||||
"horizon.pages.agent.tooltip": "独自の AI エージェントを実行します: このコンピューター上の Horizon-ctl クライアントと MCP クライアント。",
|
||||
"horizon.pages.split_profiles.tooltip": "プレイする人: このゲーム フォルダーに保存されているプロフィール。",
|
||||
"horizon.pages.split_setup.tooltip": "窓やモニター、そして誰がどこでプレイするか。",
|
||||
"horizon.pages.shader_packs.tooltip": "shaderpacks フォルダーのシェーダー パック: Horizon 独自のものと、shaders/ 形式のもの。一度に 1 つずつ実行されます。",
|
||||
"horizon.pages.mods.tooltip": "Horizon 用に作成された Mod (horizon-mods フォルダーから)。それぞれサンドボックス内で実行され、オンになるまでオフのままになります。",
|
||||
"horizon.pages.open.graphics": "グラフィックス...",
|
||||
"horizon.pages.open.graphics_advanced": "高度なグラフィックス...",
|
||||
"horizon.pages.open.shader_packs": "シェーダーパック...",
|
||||
"horizon.pages.open.controller": "コントローラー設定...",
|
||||
"horizon.pages.open.split_setup": "画面分割...",
|
||||
"horizon.pages.open.extras": "エクストラ...",
|
||||
"horizon.pages.open.mods": "改造...",
|
||||
"horizon.pages.open.agent": "AIエージェント...",
|
||||
"horizon.pages.open.horizon_graphics": "Horizon グラフィックス...",
|
||||
"horizon.pages.open.controller_binds": "コントローラーのバインド...",
|
||||
"horizon.pages.open.horizon_settings": "Horizon 設定...",
|
||||
"horizon.pages.open.music": "音楽...",
|
||||
"horizon.mods.empty": "MODなし: .hmodファイルをhorizon-modsフォルダーに置きます",
|
||||
"horizon.mods.stopped": " (停止中)",
|
||||
"horizon.mods.not_built": " (構築されていません)",
|
||||
"horizon.mods.dev": " (開発)",
|
||||
"horizon.mods.settings": "設定",
|
||||
"horizon.mods.in_world": " (世界の変化に任せて)",
|
||||
"horizon.mods.open_folder": "Modフォルダーを開く",
|
||||
"horizon.shader_packs.empty": "シェーダー パックはありません。シェーダー パック フォルダーに入れてください。",
|
||||
"horizon.shader_options.open": "シェーダオプション...",
|
||||
"horizon.shader_options.profile": "プロフィール",
|
||||
"horizon.shader_options.loading": "シェーダー パックのオプションを読んでいます...",
|
||||
"horizon.agent.copy_details": "接続詳細のコピー",
|
||||
"horizon.multiplayer.join": "参加する",
|
||||
"horizon.multiplayer.cancel_reconnecting": "再接続のキャンセル",
|
||||
"horizon.multiplayer.join_friend": "友達に加わる",
|
||||
"horizon.multiplayer.invite_code": "招待コード",
|
||||
"horizon.multiplayer.relay": "リレー(オプション)",
|
||||
"Preset": "プリセット",
|
||||
"LOD Distance": "LOD距離",
|
||||
"Far Detail": "遠くの詳細",
|
||||
"Cave Culling": "洞窟の淘汰",
|
||||
"Entity Culling": "エンティティのカリング",
|
||||
"Chunk Updates": "チャンクの更新",
|
||||
"Upload Budget": "予算のアップロード",
|
||||
"Sky": "空",
|
||||
"Fog": "霧",
|
||||
"Weather": "天気",
|
||||
"Animated Textures": "アニメーションテクスチャ",
|
||||
"Present Mode": "プレゼントモード",
|
||||
"Frame Pacing": "フレームペーシング",
|
||||
"Worker Threads": "ワーカースレッド",
|
||||
"Resolution Scale": "解像度スケール",
|
||||
"Anti-aliasing": "アンチエイリアシング",
|
||||
"Upscaling": "アップスケーリング",
|
||||
"Paper White": "ペーパーホワイト",
|
||||
"Peak Brightness": "ピーク輝度",
|
||||
"Black Level": "黒レベル",
|
||||
"UI Brightness": "UIの明るさ",
|
||||
"Battery Mode": "バッテリーモード",
|
||||
"Graphics API": "グラフィックスAPI",
|
||||
"Translucent Batching": "半透明のバッチ処理",
|
||||
"Performance Overlay": "パフォーマンスオーバーレイ",
|
||||
"Full Detail Distance": "全詳細距離",
|
||||
"On": "オン",
|
||||
"Off": "オフ",
|
||||
"Unlimited": "無制限",
|
||||
"Auto": "自動",
|
||||
"Custom": "カスタム",
|
||||
"Performance": "パフォーマンス",
|
||||
"Balanced": "バランスの取れた",
|
||||
"Quality": "品質",
|
||||
"Ultra": "ウルトラ",
|
||||
"Ultra Performance": "ウルトラパフォーマンス",
|
||||
"Native": "ネイティブ",
|
||||
"Full": "フル",
|
||||
"Reduced": "減少",
|
||||
"Efficient": "効率的",
|
||||
"Precise": "正確な",
|
||||
"Auto (VSync)": "自動 (垂直同期)",
|
||||
"Mailbox": "メールボックス",
|
||||
"Immediate": "即時",
|
||||
"Vulkan": "バルカン",
|
||||
"DirectX 12": "ダイレクトX12",
|
||||
"FPS and Graph": "FPSとグラフ",
|
||||
"No cost": "費用はかかりません",
|
||||
"Low cost": "低コスト",
|
||||
"Medium cost": "中コスト",
|
||||
"High cost": "高コスト",
|
||||
"Saves time": "時間を節約します",
|
||||
"Graphics": "グラフィックス",
|
||||
"Done": "完了",
|
||||
"This feature is not yet available in Horizon.": "この機能は Horizon ではまだ利用できません。",
|
||||
"Your display or system doesn't offer HDR output.": "お使いのディスプレイまたはシステムは HDR 出力を提供しません。",
|
||||
"Turn HDR on to use it.": "HDR をオンにして使用します。",
|
||||
"Your GPU or graphics API doesn't support it.": "GPU またはグラフィックス API はそれをサポートしていません。",
|
||||
"%s chunks": "%s チャンク",
|
||||
"%s per frame": "フレームあたり%s",
|
||||
"%s MiB/frame": "%s MiB/フレーム",
|
||||
"%s KiB/frame": "%s KiB/フレーム",
|
||||
"Auto (%s)": "自動 (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "フルディテールの地形範囲。 LOD ディスタンスを使用すると、ローカル ワールドをさらに遠くに拡張できます。",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "ローカルワールドの粗い遠くの地形。統合 GPU は 128 チャンクで停止します。",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "遠くの地形の描画を減らすと、より粗く、この統合された GPU ではより速く描画されます。",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "縮小すると、遠くの地形の描画が粗くなり、GPU の場合は速度が低下します。",
|
||||
"Field of view. Wider shows more terrain at once.": "視野。幅を広くすると、より多くの地形が一度に表示されます。",
|
||||
"Caps frames per second; a cap saves power and heat.": "1 秒あたりのフレーム数を制限します。キャップは電力と熱を節約します。",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "ディスプレイの更新を待ちます。ティアリングはありませんが、待ち時間は少しあります。",
|
||||
"Size of menus and the HUD.": "メニューと HUD のサイズ。",
|
||||
"Sets the options below together. Changing one makes it Custom.": "以下のオプションをまとめて設定します。どれかを変更するとカスタムになります。",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "溶岩、火、太陽光は白よりも明るく輝きます。 HDRディスプレイが必要です。",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "ディスプレイの中で最も暗い黒。 OLEDの場合は0。液晶が少し光ります。",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "ハイライトがどれだけ明るくなるか。自動: システムが報告するピーク。",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "白い紙とメニューがいかに明るく見えるか。自動: デスクトップが白になります。",
|
||||
"How bright the HUD and menus are, against paper white.": "紙の白に対する HUD とメニューの明るさ。",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "暴徒、作物、機械がどこまで機能し続けるか。 CPU時間。",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "通常の割合として、Mob とアイテムが描画される距離。",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "閉じたセクションの後ろに隠れている地形をスキップします。オフには多額の費用がかかります。",
|
||||
"Hides entities behind opaque blocks.": "エンティティを不透明なブロックの後ろに隠します。",
|
||||
"No entity occlusion culling.": "エンティティ オクルージョン カリングはありません。",
|
||||
"CPU occlusion test runs this frame.": "CPU オクルージョン テストはこのフレームを実行します。",
|
||||
"Occlusion checks run on a worker thread.": "オクルージョン チェックはワーカー スレッドで実行されます。",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "メッシュ化されたセクションはフレームごとにアップロードされます。ローディング時は下のほうがスムーズです。",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "コーナーの柔らかい影。チャンクの構築時間のみがかかります。",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "遠くの地形のブロック テクスチャの小さいコピー: きらめきが少なくなります。",
|
||||
"Blends grass and water colours across biomes when building chunks.": "チャンクを構築するときに、バイオーム全体で草と水の色をブレンドします。",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "クラウドレイヤー: オフ、フラット (高速) または 3D (ファンシー)。",
|
||||
"How far clouds reach, in chunks.": "雲が塊ごとにどこまで届くか。",
|
||||
"Draws the sky, sun, moon and stars.": "空、太陽、月、星を描きます。",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "距離霧。オフは、読み込まれたワールドの端を示します。",
|
||||
"Draws rain and snow.": "雨と雪を描きます。",
|
||||
"How far rain and snow fall around you.": "あなたの周囲に雨や雪がどれだけ降るのか。",
|
||||
"How many particles effects spawn.": "パーティクル エフェクトのスポーン数。",
|
||||
"Round shadows under mobs and items.": "Mob とアイテムの下の丸い影。",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "隙間が透けて見える葉。オフではしっかりと描画されます。",
|
||||
"Sorts water and glass more exactly behind each other.": "水とガラスをより正確に前後に並べます。",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "どのチャンクの再構築がフレームを待機するか: なし、近くのチャンク、または自分のチャンク。",
|
||||
"Animates water, lava, fire and other moving textures.": "水、溶岩、火、その他の動くテクスチャをアニメーション化します。",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "画面ピクセルごとのワールド ピクセル。 100% 未満の場合、GPU での速度が速くなります。",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "ギザギザのエッジを滑らかにします。 FXAA: 柔らかめ。 TAA: 最もスムーズですが、動いているものは尾を引く可能性があります。",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "世界を小さくレンダリングし、TAA で再構築します。より速く、よりソフトに。",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR ディスプレイ上の HDR 出力: 溶岩、火、太陽光がより明るく輝きます。",
|
||||
"How frames reach the screen while VSync is off.": "VSync がオフのときにフレームが画面に到達する方法。",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "次のフレームまで効率的にスリープします。バニラのような正確なスピン。",
|
||||
"Fills the screen (borderless).": "画面いっぱい(フチなし)に表示します。",
|
||||
"Brightens dark places.": "暗い場所を明るくします。",
|
||||
"Lowers the frame rate when minimised, or idle too.": "最小化またはアイドル時にもフレーム レートを下げます。",
|
||||
"Sways the view while walking.": "歩いていると景色が揺れます。",
|
||||
"Fades newly loaded chunks in.": "新しくロードされたチャンクをフェードインします。",
|
||||
"Darkens the screen's edges.": "画面の端を暗くします。",
|
||||
"Shows when the world saves.": "世界が救われるときを示します。",
|
||||
"Shows attack cooldown.": "攻撃のクールダウンを表示します。",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS の上限 (メニューまたはフォーカスされていない場合は 30)、ワーカーの半分: 電力が減少します。",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "チャンクを生成、ライト、メッシュするスレッド。次のスタート。",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan、次に DirectX 12 (Windows) または OpenGL (Linux) で失敗します。次のスタート。",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "1 つのバッファーに水とガラス: ドローコールが減り、VRAM が少し増えます。次のスタート。",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "フレームごとに GPU に送信されるメッシュ データ。ローディング時は下のほうがスムーズです。",
|
||||
"Frame rate and frame times over the game, with a graph.": "ゲーム全体のフレーム レートとフレーム時間をグラフで表示します。",
|
||||
"horizon.multiplayer.finding_friend": "友達の世界を探しています...",
|
||||
"horizon.multiplayer.not_signed_in": "このサーバーはオンライン モードです。 Horizon ランチャーに Microsoft アカウントを使用してサインインして、それに参加します。",
|
||||
"horizon.multiplayer.invalid_address": "無効なサーバーアドレス: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "友達と直接つながります...",
|
||||
"horizon.multiplayer.connected_friend_relay": "リレーを通じて友達とつながっています...",
|
||||
"General": "一般",
|
||||
"Advanced": "上級者向け",
|
||||
"Undo": "元に戻す",
|
||||
"Search options": "検索オプション",
|
||||
"Shortcut": "ショートカット",
|
||||
"Distance": "距離",
|
||||
"Display": "ディスプレイ",
|
||||
"Interface": "インターフェース",
|
||||
"Sky and weather": "空と天気",
|
||||
"Lighting and entities": "照明とエンティティ",
|
||||
"Textures": "テクスチャ",
|
||||
"Image": "画像",
|
||||
"Chunk loading": "チャンクロード",
|
||||
"Culling": "淘汰",
|
||||
"Power": "パワー",
|
||||
"Frames": "フレーム",
|
||||
"Overlay": "オーバーレイ",
|
||||
"Output": "出力",
|
||||
"Brightness": "明るさ",
|
||||
"How far you see, the window and the HUD.": "窓とHUDがどこまで見えるか。",
|
||||
"The sky and weather, lighting, textures and the image.": "空と天気、照明、テクスチャ、画像。",
|
||||
"Chunk loading, culling and power use.": "チャンクのロード、カリング、および電力の使用。",
|
||||
"How frames reach the screen, and the performance overlay.": "フレームが画面に到達する方法とパフォーマンス オーバーレイ。",
|
||||
"Brighter-than-white output on an HDR display.": "HDR ディスプレイ上の白よりも明るい出力。",
|
||||
"Results for \"%s\"": "「%s」の結果",
|
||||
"No settings match.": "一致する設定がありません。",
|
||||
"horizon.title.not_affiliated": "Mojang や Microsoft とは関係ありません",
|
||||
"horizon.extras.header.lighting": "照明",
|
||||
"horizon.extras.header.third_person": "第三者",
|
||||
"horizon.extras.header.particles": "粒子",
|
||||
"horizon.extras.header.inventory": "在庫",
|
||||
"horizon.extras.header.chat": "チャット",
|
||||
"horizon.extras.header.multiplayer": "マルチプレイヤー",
|
||||
"horizon.extras.header.sound": "サウンド",
|
||||
"horizon.extras.header.menus": "メニュー",
|
||||
"horizon.extras.fullbright": "フルブライト",
|
||||
"horizon.extras.dynamic_lights": "ダイナミックライト",
|
||||
"horizon.extras.own_name_tag": "自分だけのネームタグ",
|
||||
"horizon.extras.hide_block_particles": "ブロックブレイクパーティクルを非表示にする",
|
||||
"horizon.extras.auto_reconnect": "自動再接続",
|
||||
"horizon.extras.ui_sound_reserve": "UI サウンドのチャンネルを維持する",
|
||||
"horizon.extras.music_page": "音楽ページ",
|
||||
"horizon.extras.mouse_tweaks": "マウス調整コントロール",
|
||||
"horizon.extras.mouse_tweaks_wheel": "スクロール ホイールで項目を移動します",
|
||||
"horizon.extras.sort": "並べ替えボタン",
|
||||
"horizon.extras.sort_key": "ソートキー",
|
||||
"horizon.extras.no_ai": "AIなし",
|
||||
"horizon.extras.agent_control": "エージェント制御",
|
||||
"horizon.extras.discord_presence": "Discord のリッチなプレゼンス",
|
||||
"horizon.extras.splitscreen": "分割画面",
|
||||
"horizon.extras.chat_animation": "スムーズなチャット",
|
||||
"horizon.extras.chat_indicator": "メッセージインジケーター",
|
||||
"horizon.extras.wheel_cycles_settings": "スクロール ホイールで設定を変更します",
|
||||
"horizon.extras.pack_browser": "リソースパックブラウザ",
|
||||
"horizon.extras.fullbright.tooltip": "暗い場所でもブロックやエンティティを完全な光で表示します。",
|
||||
"horizon.extras.dynamic_lights.tooltip": "トーチやその他の光源を持ったり、落としたり、火をつけたりすると、周囲のブロックが点灯します。",
|
||||
"horizon.extras.own_name_tag.tooltip": "他のプレイヤーと同じように、三人称で自分の名前が頭上に表示されます。サーバーがチームの名前を隠している場合、またはあなたが表示されていない場合は、非表示になります。",
|
||||
"horizon.extras.hide_block_particles.tooltip": "壊れたブロックから飛び出し、採掘したブロックから崩れ落ちる破片の粒子を隠します。他の粒子はそのまま残ります。",
|
||||
"horizon.extras.auto_reconnect.tooltip": "キックや禁止の後ではなく、接続の喪失または再起動後にサーバーに再参加します。",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "世界中のサウンドは 247 サウンド チャンネルのうち 16 チャンネルを解放しているため、それらの洪水 (大爆発) がメニュー クリックを沈黙させることはできません。",
|
||||
"horizon.extras.music_page.tooltip": "Music & Sounds に Music... ページが表示されます。内蔵トラックを今すぐ再生するか、ゲームがトラックを選択しないように 1 つをオフにします。トラックをオフにするまでは何も変わりません。",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "右ドラッグでスロットごとに 1 つずつ配置し、左ドラッグで保持しているアイテムを収集し、Shift + 左ドラッグでスロットごとに移動します。",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Down はスロットの 1 つの項目を反対側に送り、Up は 1 つの項目を戻します。",
|
||||
"horizon.extras.sort.tooltip": "インベントリ、チェスト、バレル、エンダーチェスト、シュルカーボックスをクリエイティブなタブオーダーで並べ替え、スタックを結合します。",
|
||||
"horizon.extras.sort_key.tooltip": "カーソル下の部分を並べ替えます。クリックしてキーを押します。エスケープするとバインドが解除されます。",
|
||||
"horizon.extras.no_ai.tooltip": "すべての AI 機能 (エージェント コントロール、Horizon-ctl、MCP サーバー) を非表示にしてオフにします。ランチャーのスイッチも。",
|
||||
"horizon.extras.agent_control.tooltip": "独自の AI エージェントは、このコンピューター上でのみ、horizon-ctl または MCP クライアントを通じてこのゲームをプレイできます。",
|
||||
"horizon.extras.discord_presence.tooltip": "Discord の実行中に、Discord プロフィールに Playing Horizon が表示されます: メニュー、シングル プレーヤーまたはマルチプレーヤー、ディメンション、および再生時間が表示されます。決して世界の名前やサーバーのアドレスではありません。",
|
||||
"Calibrate...": "校正...",
|
||||
"Calibrate Anyway...": "とにかく校正してください...",
|
||||
"Use Automatic Setup": "自動セットアップを使用する",
|
||||
"Shortcut: %s": "ショートカット: %s",
|
||||
"Next": "次へ",
|
||||
"HDR Calibration %s/%s: %s": "HDR キャリブレーション %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "フルスクリーンの下の 3 つを、一度に 1 つのテスト画像を設定します。",
|
||||
"A key that opens this screen from the game. Not bound at first.": "ゲームからこの画面を開くキー。最初は縛られてない。",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "ディスプレイにはどのくらい明るくなるかは記載されていません。調整して設定してください。",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "ディスプレイ用にすでにセットアップされています。 HDR が間違っていると思われる場合にのみ調整してください。",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "ピークの明るさとペーパー ホワイトをディスプレイの設定に戻します。",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "ディスプレイに合わせて設定します。問題がなければ、[自動] のままにしておきます。",
|
||||
"Lower it until the logo just disappears into the black.": "ロゴが真っ黒に消えるまで下げます。",
|
||||
"Raise it until the logo just disappears into the bright square.": "ロゴが明るい四角形に消えるまで上げます。",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "プレイする光の下で、カードを長時間見ても快適な白にします。",
|
||||
"horizon.splitscreen.profiles.forget": "忘れる",
|
||||
"horizon.splitscreen.profiles.create": "作成",
|
||||
"horizon.splitscreen.profiles.you": " - あなた",
|
||||
"horizon.splitscreen.profiles.forget_named": "%sは忘れてください",
|
||||
"horizon.splitscreen.profiles.previous": "前へ",
|
||||
"horizon.splitscreen.profiles.next": "次へ",
|
||||
"horizon.splitscreen.profiles.new_button": "新しいプロフィール...",
|
||||
"horizon.splitscreen.profiles.forget_title": "プロフィールを忘れましたか?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "このインスタンスのリストを残します。",
|
||||
"horizon.splitscreen.profiles.forget_kept": "ワールドの進行状況と設定は保持されます。",
|
||||
"horizon.splitscreen.profiles.new_title": "新しいプロフィール",
|
||||
"horizon.splitscreen.profiles.name_hint": "お名前(3~16文字の英数字、_)",
|
||||
"horizon.splitscreen.profiles.switch_title": "プロファイルの切り替え",
|
||||
"horizon.splitscreen.profiles.offline_note": "オフライン プロファイルはこのインスタンスに属します",
|
||||
"horizon.splitscreen.profiles.move": "遊ぶ場所を移動する",
|
||||
"horizon.splitscreen.profiles.move_place": "プレイする場所を移動: %s",
|
||||
"horizon.friends.title": "友達と遊ぶ",
|
||||
"horizon.friends.hint": "フレンドは、タイトル画面のフレンドボタンの後、ダイレクト接続、またはランチャーのプレイページにコードを貼り付けます。",
|
||||
"horizon.friends.copied": "コピーされました",
|
||||
"horizon.friends.copy_code": "コードをコピーする",
|
||||
"horizon.friends.stop_sharing": "共有を停止する",
|
||||
"horizon.friends.none_yet": "ここにはまだ友達がいません。",
|
||||
"horizon.friends.here_now": "今ここ: %s",
|
||||
"horizon.friends.share_intro": "招待コードを使用してこの世界を共有します。コードは LAN に開かれ、インターネット上の友人はコードを使用して参加します。弊社のサーバーは関与しておりません。",
|
||||
"horizon.friends.start_sharing": "共有を開始する",
|
||||
"horizon.friends.troubleshooting": "トラブルシューティング中...",
|
||||
"horizon.friends.stopped": "招待コードの共有を停止しました。",
|
||||
"horizon.friends.short_code_changed": "誰かが間違ったショートコードを試し続けたため、コードが変更されました。",
|
||||
"horizon.friends.wrong_code": "誰かが間違ったコードを試みました。",
|
||||
"horizon.friends.code_ready": "招待コードの準備が整いました: ゲーム メニュー、友達とプレイ。コピーして友達に送信してください。",
|
||||
"horizon.friends.share_failed": "世界を共有できませんでした: %s",
|
||||
"horizon.friends.finding_route": "友達があなたに連絡する方法を調べています…",
|
||||
"horizon.osk.shift": "シフト",
|
||||
"horizon.osk.space": "宇宙",
|
||||
"horizon.osk.delete": "削除",
|
||||
"horizon.osk.type": "種類",
|
||||
"horizon.osk.symbols": "記号",
|
||||
"horizon.osk.close": "閉じる",
|
||||
"horizon.extras.chat_animation.tooltip": "新しいチャット ラインは全体として表示されるのではなく、フェードインしながら左側からスライドして表示されます。",
|
||||
"horizon.extras.chat_indicator.tooltip": "チャットラインの左側にある色付きのバー。",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "バニラと同様に、設定内のボタン上でホイールをスクロールすると、その値が変更されます。オフの場合、ホイールはリストのみをスクロールします。",
|
||||
"horizon.extras.pack_browser.tooltip": "[リソース パック] を選択すると、Modrinth を検索してパック フォルダーにパックをインストールする [追加] ボタンが表示されます。オフ、ゲームは決してモドリンスに接触しません。",
|
||||
"horizon.extras.splitscreen.tooltip": "ワールドでは、別のコントローラーが新しいプレーヤーとして参加できます。メッセージが表示されたら A を押し、プロフィールとプレイする場所を選択します。",
|
||||
"horizon.splitscreen.setup.monitor": "モニター%s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "モニター",
|
||||
"horizon.splitscreen.setup.window": "ウィンドウ%s",
|
||||
"horizon.splitscreen.setup.new_window": "新しいウィンドウ",
|
||||
"horizon.splitscreen.setup.fill_monitor": "%s を入力してください",
|
||||
"horizon.splitscreen.setup.done": "完了",
|
||||
"horizon.splitscreen.setup.on": "オン",
|
||||
"horizon.splitscreen.setup.off": "オフ",
|
||||
"horizon.splitscreen.setup.player": "プレーヤー%s",
|
||||
"horizon.splitscreen.setup.revert": "元に戻す",
|
||||
"horizon.splitscreen.setup.apply": "申し込む",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "ウィンドウ 1 では誰もプレイしません。ウィンドウ 1 は非表示になります。",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "ウィンドウ %s では誰もプレイしません: 適用時に閉じます",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Window %s では誰もプレイしていません: 開きません",
|
||||
"horizon.splitscreen.setup.warning.on_main": "ウィンドウ %s がウィンドウ 1 のモニターに表示されます。プレイヤーはウィンドウ 1 でプレイします。",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Window %s のモニターが接続されていません。そのプレイヤーは Window 1 でプレイします。",
|
||||
"horizon.splitscreen.setup.status.off": "画面分割がオフになっています。",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "それをオンにして、ワールド内の別のコントローラーのボタンを押します。",
|
||||
"horizon.splitscreen.setup.status.join_hint": "A を押して参加し、プロフィールを選択します。",
|
||||
"horizon.splitscreen.setup.status.idle": "分割画面のアイドル状態",
|
||||
"horizon.splitscreen.setup.status.alone": "他に地元選手はいない。",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "ソロコントロールとコントローラーフィードバックは変更されません。",
|
||||
"horizon.splitscreen.setup.window_empty": "ウィンドウ %s: 空",
|
||||
"horizon.splitscreen.setup.note.select": "プレーヤーをクリックするか、ウィンドウまたはモニターにドラッグします。",
|
||||
"horizon.splitscreen.setup.note.apply": "この方法で再生するには適用し、元に戻すには戻るを選択します。",
|
||||
"horizon.splitscreen.setup.note.place": "%s はどこでプレイすべきですか?",
|
||||
"horizon.splitscreen.setup.this_player": "この選手",
|
||||
"horizon.splitscreen.setup.switch_disabled": "他の人が退席すると (一時停止メニュー)、分割画面はオフになります。",
|
||||
"horizon.splitscreen.setup.window_place": "ウィンドウ %s (%s の %s)",
|
||||
"horizon.splitscreen.setup.names": "名前",
|
||||
"horizon.splitscreen.setup.identify": "特定する",
|
||||
"horizon.splitscreen.setup.layout.two": "2 共有: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 共有: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "自動",
|
||||
"horizon.splitscreen.setup.layout.two.1": "並んで",
|
||||
"horizon.splitscreen.setup.layout.two.2": "上と下",
|
||||
"horizon.splitscreen.setup.layout.three.0": "上にワイドが1つ",
|
||||
"horizon.splitscreen.setup.layout.three.1": "左側の背の高い人",
|
||||
"horizon.splitscreen.setup.layout.three.2": "並んで",
|
||||
"horizon.splitscreen.setup.help.player": "%s をクリックまたはドラッグして移動します。",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (ウィンドウが広い場合は横に並べて表示)。を押して変更します。",
|
||||
"horizon.splitscreen.setup.help.layout": "%s。を押して変更します。",
|
||||
"horizon.splitscreen.setup.help.switch": "このコンピュータの画面分割 (オンまたはオフ)。",
|
||||
"horizon.splitscreen.setup.help.identify": "各プレイヤーのビューには、どのウィンドウにいるかが表示されます。",
|
||||
"horizon.splitscreen.setup.help.names": "各プレイヤーの名前が視界の隅に表示されます。",
|
||||
"horizon.splitscreen.setup.help.revert": "まだ適用されていない変更を元に戻します。",
|
||||
"horizon.splitscreen.setup.help.apply": "マップに表示されている方法でプレイしてください。",
|
||||
"horizon.splitscreen.setup.help.unchanged": "まだ何も変わっていません。",
|
||||
"horizon.splitscreen.setup.give_up_pad": "ギブアップパッド",
|
||||
"horizon.splitscreen.prompt.controller_connected": "コントローラーが接続されています",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "コントローラーが切断されました",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "再接続するか、別のボタンを押してください",
|
||||
"horizon.splitscreen.prompt.join.one": "%s さんは参加を希望しています",
|
||||
"horizon.splitscreen.prompt.join.many": "%s コントローラーが参加を希望しています",
|
||||
"horizon.splitscreen.prompt.join.yes": "参加する",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "P1としてプレイする",
|
||||
"horizon.splitscreen.prompt.join.no": "いいえ",
|
||||
"horizon.splitscreen.prompt.lost": "%s: コントローラーが切断されました",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "別のものを使用する",
|
||||
"horizon.splitscreen.prompt.lost.leave": "休暇を取る",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "コントローラーは運転を停止し、(A) に参加するよう勧められます。",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s から %s: A がここにドロップし、B が元に戻します。",
|
||||
"horizon.splitscreen.setup.note.held": "%s を拾った: 左/右がどこにピックし、A がドロップし、B が元に戻します。",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "モデルを不透明なブロックの後ろに隠します。非同期チェックはオフスレッドで行われます。エンティティがティックします。",
|
||||
"Block Entities": "ブロックエンティティ",
|
||||
"Shulker Boxes": "シュルカーボックス",
|
||||
"Reuses settled boxes; lid movement stays animated.": "固定されたボックスを再利用します。蓋の動きはアニメーションのままです。",
|
||||
"Signs": "標識",
|
||||
"Reuses sign text until text, light, or font changes.": "テキスト、ライト、またはフォントが変更されるまで、署名テキストを再利用します。",
|
||||
"Decorated Pots": "装飾鉢",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "蒸留器を再利用します。ぐらつきを解消し、シャードの変更を再描画します。",
|
||||
"Bells": "ベル",
|
||||
"Reuses still bells; ringing stays animated.": "静止ベルを再利用します。呼び出し音はアニメーションのままになります。",
|
||||
"Chests": "チェスト",
|
||||
"Reuses closed chests; moving lids stay animated.": "閉じたチェストを再利用します。動くまぶたはアニメーションを維持します。",
|
||||
"Banners": "バナー",
|
||||
"Reuses poles; flags and pattern layers still update.": "ポールを再利用します。フラグとパターンレイヤーは引き続き更新されます。",
|
||||
"Copper Golem Statues": "銅のゴーレム像",
|
||||
"Reuses statue geometry until its block state changes.": "ブロックの状態が変わるまで、彫像ジオメトリを再利用します。",
|
||||
"Leaf Culling": "葉の間引き",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "すべての葉の顔。バニラのファンシーな葉っぱ。",
|
||||
"Distant Plant Dither": "遠くの植物のディザー",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "高解像度パック内の立派な植物を遠く離れた場所で色褪せるのを防ぎます。",
|
||||
"Screenshot Quality": "スクリーンショットの品質",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2 キーを押すと、ゲームの解像度の何倍もの解像度でスクリーンショットが撮影されます。高いほど時間がかかります。",
|
||||
"Frame Generation": "フレーム生成",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "2 つの実際のフレームの間に構成されたフレームを表示します。よりスムーズで、少し遅れが生じます。",
|
||||
"Shadow Cache": "シャドウキャッシュ",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "太陽、ビュー、ワールドが一致する場合、パック シャドウ マップを再利用します。デフォルトではオフです。",
|
||||
"Find BlueMap": "ブルーマップを探す",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "サーバー上で、遠方の地形用の BlueMap Web マップを検索します。次に参加します。",
|
||||
"BlueMap Radius": "ブルーマップの半径",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "BlueMap タイルがダウンロードされる周囲の距離。次に参加します。",
|
||||
"Synchronous": "同期",
|
||||
"Asynchronous": "非同期",
|
||||
"Solid": "固体",
|
||||
"Solid Aggressive": "ソリッドアグレッシブ",
|
||||
"Hollow": "中空",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "樹冠の内側に葉が完全に描画されるため、透けて見えるレイヤーが少なくなります。",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "片側が開いた葉もしっかりと描画され、エッジがより速く、より分厚くなります。",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "葉の間に面をドロップします: 速度は速くなりますが、隙間が透けて見えます。",
|
||||
"Hotbar: Previous Slot": "ホットバー: 前のスロット",
|
||||
"Hotbar: Next Slot": "ホットバー: 次のスロット",
|
||||
"horizon.agent.connected.one": "接続済み: 1 つのエージェント接続",
|
||||
"horizon.agent.connected.many": "接続済み: %s エージェント接続",
|
||||
"horizon.agent.connected.one_ago": "接続済み: 1 エージェント接続、最後のコマンドは %s 秒前",
|
||||
"horizon.agent.connected.many_ago": "接続済み: %s エージェント接続、最後のコマンド %s 秒前",
|
||||
"horizon.agent.waiting": "エージェントを待っています。",
|
||||
"horizon.agent.off": "オフ: エージェントはこのゲームにアクセスできません。",
|
||||
"horizon.agent.session_opened": "ゲームを開始したプログラムによってこのセッション用に開かれました。",
|
||||
"horizon.agent.socket": "ソケット: %s (フルパスの詳細をコピーします)",
|
||||
"horizon.agent.local_only": "ローカルのみ: このコンピュータ上にエージェントがあり、ネットワーク上には存在しません。",
|
||||
"%s nits": "%sニット",
|
||||
"Auto (%s nits)": "自動 (%s ニット)",
|
||||
"%.2f nits": "%.2fニット",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "モデルを不透明なブロックの後ろに隠します。エンティティが更新されます。カスタムパックでオフ。",
|
||||
"horizon.extras.repro_keys": "録音のショートカット",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R は再現録画を開始または停止し、F3+E はそのバンドルを書き込みます。デフォルトではオフ: 録音は世界をコピーします。",
|
||||
"horizon.agent.record_session": "録音セッションの開始",
|
||||
"horizon.agent.record_stop": "セッションの記録を停止",
|
||||
"horizon.agent.export_repro": "レプロバンドルのエクスポート",
|
||||
"horizon.repro.started": "録音が開始されました。 F3+R で停止し、バンドルを書き込みます。",
|
||||
"horizon.repro.stopped": "記録が停止されました: %s",
|
||||
"horizon.repro.bundle": "再現バンドル: %s",
|
||||
"horizon.repro.error": "再現: %s",
|
||||
"horizon.repro.no_recording": "エクスポートする録音がまだありません。",
|
||||
"horizon.repro.replay_started": "リプレイ開始: %s",
|
||||
"horizon.pack.addInfo": "Modrinth からリソース パックを追加する",
|
||||
"horizon.pack.addWorking": "モドリンスに聞いてみると…",
|
||||
"horizon.pack.addNone": "リソース パックが見つかりません",
|
||||
"horizon.pack.addTitle": "リソースパックを追加する",
|
||||
"horizon.pack.install": "インストール",
|
||||
"horizon.pack.installing": "インストール中",
|
||||
"horizon.pack.installed": "インストール済み",
|
||||
"horizon.pack.search": "検索",
|
||||
"horizon.pack.searchHint": "モドリンスを検索...",
|
||||
"horizon.pack.filters": "フィルター",
|
||||
"horizon.pack.filter.realistic": "現実的な",
|
||||
"horizon.pack.filter.simplistic": "単純化した",
|
||||
"horizon.pack.filter.vanillaLike": "バニラっぽい",
|
||||
"horizon.pack.filter.x16": "16倍",
|
||||
"horizon.pack.filter.x32": "32倍",
|
||||
"horizon.pack.filter.x64": "64倍",
|
||||
"horizon.pack.addFailed": "パックを追加できませんでした: %s",
|
||||
"horizon.extras.header.cosmetics": "化粧品",
|
||||
"horizon.extras.cosmetics_mine": "私の化粧品",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "非表示: すべての Horizon プレイヤーは、あなたも含め、化粧品を着用していないあなたを見ます。ランチャーのワードローブと同じスイッチ。",
|
||||
"horizon.extras.cosmetics_others": "他のプレイヤーの化粧品",
|
||||
"horizon.extras.cosmetics_others.tooltip": "他の Horizon プレイヤーが着ているもの: すべて、名札のスタイルのみ、または何も着ていません。隠されていますが、ゲームでは彼らが何を着ているかは尋ねられません。",
|
||||
"horizon.extras.cosmetics_auras": "他人のオーラと軌跡",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "他のプレイヤーが身に着けているオーラと軌跡。デフォルトではオフです。",
|
||||
"horizon.extras.cosmetics.shown": "示されている",
|
||||
"horizon.extras.cosmetics.hidden": "隠された",
|
||||
"horizon.extras.cosmetics.name_tags": "ネームタグのみ"
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
{
|
||||
"horizon.options.controller": "Valdiklis",
|
||||
"horizon.options.vibration": "Vibracija",
|
||||
"horizon.options.adaptive_triggers": "Adaptyvūs trigeriai",
|
||||
"horizon.options.lightbar": "Šviesos juosta",
|
||||
"horizon.options.look_sensitivity": "Žvilgsnio jautrumas",
|
||||
"horizon.options.invert_x": "Apversti žvilgsnio X ašį",
|
||||
"horizon.options.invert_y": "Apversti žvilgsnio Y ašį",
|
||||
"horizon.options.sneak_button": "Sėlinimo mygtukas",
|
||||
"horizon.options.move_deadzone": "Judėjimo negyvoji zona",
|
||||
"horizon.options.look_deadzone": "Žvilgsnio negyvoji zona",
|
||||
"horizon.options.button_glyphs": "Mygtukų Glifai",
|
||||
"horizon.options.menu_navigation": "Meniu naršymas",
|
||||
"horizon.options.on_screen_keyboard": "Ekrano klaviatūra",
|
||||
"horizon.options.chat_delay": "Pokalbio delsimas",
|
||||
"horizon.options.lightbar.health": "Sveikata",
|
||||
"horizon.options.lightbar.dimension": "Matmenys",
|
||||
"horizon.options.menu_focus": "Fokusas",
|
||||
"horizon.options.menu_pointer": "Rodyklė",
|
||||
"horizon.options.keyboard.horizon": "Horizontas",
|
||||
"horizon.options.keyboard.steam": "Garas",
|
||||
"horizon.options.unavailable": "Ši funkcija Horizonte dar nepasiekiama.",
|
||||
"horizon.options.search": "Paieškos nustatymai...",
|
||||
"horizon.pages.mods": "Modifikacijos",
|
||||
"horizon.pages.shader_packs": "Shader paketai",
|
||||
"horizon.pages.shader_options": "Shader parinktys",
|
||||
"horizon.pages.controller": "Valdiklio nustatymai",
|
||||
"horizon.pages.controller_binds": "Valdiklis susieja",
|
||||
"horizon.pages.extras": "„Horizon“ priedai",
|
||||
"horizon.pages.settings": "Horizonto nustatymai",
|
||||
"horizon.pages.agent": "AI agentas",
|
||||
"horizon.split.leave": "Palikti",
|
||||
"horizon.pages.split_settings": "Padalinto ekrano nustatymai",
|
||||
"horizon.pages.split_profiles": "Kas žaidžia?",
|
||||
"horizon.pages.split_setup": "Padalinto ekrano sąranka",
|
||||
"horizon.pages.music": "Muzika",
|
||||
"horizon.pages.music.menu": "Meniu",
|
||||
"horizon.pages.music.overworld": "Viršutinis pasaulis",
|
||||
"horizon.pages.music.creative": "Kūrybingas",
|
||||
"horizon.pages.music.underwater": "Povandeninis",
|
||||
"horizon.pages.music.end": "Pabaiga",
|
||||
"horizon.pages.music.boss": "Bosas",
|
||||
"horizon.pages.music.credits": "Kreditai",
|
||||
"horizon.pages.music.overworld_biomes": "Viršutinio pasaulio biomai",
|
||||
"horizon.pages.music.nether_biomes": "Nei biomai",
|
||||
"horizon.pages.music.end_biomes": "Pabaikite biomus",
|
||||
"horizon.pages.music.other": "Kita muzika",
|
||||
"horizon.pages.music.empty": "Nėra muzikos takelių: žaidimo garsai neįkeliami.",
|
||||
"horizon.pages.music.tooltip": "Įtaisytieji takeliai pagal situaciją: žaiskite vieną dabar arba išjunkite bet kurį, kad žaidimas nustotų jį pasirinkti.",
|
||||
"horizon.pages.settings.tooltip": "Patys Horizon nustatymai: grafika, valdikliai, priedai ir modifikacijos.",
|
||||
"horizon.pages.graphics.tooltip": "Grafika, paprasčiausiai: kiekvienas nustatymas nurodo, kiek jis kainuoja.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Kiekvienas grafikos nustatymas, įtrauktas ir Horizontas, kiek kainuoja.",
|
||||
"horizon.pages.controller.tooltip": "Valdikliai: jų mygtukai, lazdos, vibracija ir meniu navigacija.",
|
||||
"horizon.pages.extras.tooltip": "Pasirenkami papildymai, visi pagal numatytuosius nustatymus: žaidimas išlieka vanilinis, kol jį įjungiate.",
|
||||
"horizon.pages.agent.tooltip": "Leiskite savo AI agentui žaisti: horizon-ctl ir MCP klientus šiame kompiuteryje.",
|
||||
"horizon.pages.split_profiles.tooltip": "Kas žaidžia: profilis, saugomas šiame žaidimo aplanke.",
|
||||
"horizon.pages.split_setup.tooltip": "Langai ir monitoriai ir kas kur žaidžia.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader paketai iš „shaderpacks“ aplanko: paties „Horizon“ ir „shader“ / formato. Vienu metu bėga vienas.",
|
||||
"horizon.pages.mods.tooltip": "Modifikacijos, sukurtos „Horizon“, iš aplanko „horizon-mods“. Kiekvienas veikia smėlio dėžėje ir lieka išjungtas, kol jį įjungiate.",
|
||||
"horizon.pages.open.graphics": "Grafika...",
|
||||
"horizon.pages.open.graphics_advanced": "Išplėstinė grafika...",
|
||||
"horizon.pages.open.controller": "Valdiklio nustatymai...",
|
||||
"horizon.pages.open.split_setup": "Padalintas ekranas...",
|
||||
"horizon.pages.open.extras": "Priedai...",
|
||||
"horizon.pages.open.mods": "Modifikacijos...",
|
||||
"horizon.pages.open.agent": "AI agentas...",
|
||||
"horizon.pages.open.horizon_graphics": "Horizonto grafika...",
|
||||
"horizon.pages.open.controller_binds": "Valdiklis susieja...",
|
||||
"horizon.pages.open.horizon_settings": "Horizonto nustatymai...",
|
||||
"horizon.pages.open.music": "Muzika...",
|
||||
"horizon.mods.empty": "Jokių modifikacijų: įdėkite .hmod failus į aplanką „horizon-mods“.",
|
||||
"horizon.mods.stopped": " (Sustabdytas)",
|
||||
"horizon.mods.not_built": " (Nepastatytas)",
|
||||
"horizon.mods.dev": " (Kūrėjas)",
|
||||
"horizon.mods.settings": "Nustatymai",
|
||||
"horizon.mods.in_world": " (palik pasauliui pasikeisti)",
|
||||
"horizon.mods.open_folder": "Atidarykite Mod aplanką",
|
||||
"horizon.shader_packs.empty": "Nėra šešėlių paketų: įdėkite juos į šešėlių paketų aplanką",
|
||||
"horizon.shader_packs.kind_horizon": " (Horizontas)",
|
||||
"horizon.shader_options.open": "Shader parinktys...",
|
||||
"horizon.shader_options.profile": "Profilis",
|
||||
"horizon.shader_options.loading": "Skaitant šešėlių paketo parinktis...",
|
||||
"horizon.agent.copy_details": "Nukopijuokite ryšio informaciją",
|
||||
"horizon.multiplayer.join": "Prisijunk",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Atšaukti prisijungimą iš naujo",
|
||||
"horizon.multiplayer.join_friend": "Prisijunk prie draugo",
|
||||
"horizon.multiplayer.invite_code": "Kvietimo kodas",
|
||||
"horizon.multiplayer.relay": "Relė (neprivaloma)",
|
||||
"Preset": "Iš anksto nustatytas",
|
||||
"LOD Distance": "LOD atstumas",
|
||||
"Far Detail": "Tolima detalė",
|
||||
"Entity Culling": "Esybės naikinimas",
|
||||
"Chunk Updates": "Dalies atnaujinimai",
|
||||
"Upload Budget": "Įkelti biudžetą",
|
||||
"Sky": "Dangus",
|
||||
"Fog": "Rūkas",
|
||||
"Weather": "Orai",
|
||||
"Animated Textures": "Animuotos tekstūros",
|
||||
"Present Mode": "Dabartinis režimas",
|
||||
"Frame Pacing": "Kadro tempas",
|
||||
"Worker Threads": "Darbuotojų siūlai",
|
||||
"Resolution Scale": "Rezoliucijos skalė",
|
||||
"Upscaling": "Didinimas",
|
||||
"Paper White": "Popierius Baltas",
|
||||
"Peak Brightness": "Didžiausias ryškumas",
|
||||
"Black Level": "Juodasis lygis",
|
||||
"UI Brightness": "UI ryškumas",
|
||||
"Battery Mode": "Baterijos režimas",
|
||||
"Graphics API": "Grafikos API",
|
||||
"Translucent Batching": "Permatomos partijos",
|
||||
"Performance Overlay": "Našumo perdanga",
|
||||
"Full Detail Distance": "Visas detalus atstumas",
|
||||
"On": "Įjungta",
|
||||
"Off": "Išjungta",
|
||||
"Unlimited": "Neribota",
|
||||
"Custom": "Pasirinktinis",
|
||||
"Performance": "Spektaklis",
|
||||
"Balanced": "Subalansuota",
|
||||
"Quality": "Kokybė",
|
||||
"Native": "Gimtoji",
|
||||
"Full": "Pilnas",
|
||||
"Reduced": "Sumažintas",
|
||||
"Efficient": "Efektyvus",
|
||||
"Precise": "Tikslus",
|
||||
"Auto (VSync)": "Automatinis (VSync)",
|
||||
"Mailbox": "Pašto dėžutė",
|
||||
"Immediate": "Nedelsiant",
|
||||
"Vulkan": "Vulkanas",
|
||||
"DirectX 12": "„DirectX 12“.",
|
||||
"FPS and Graph": "FPS ir grafikas",
|
||||
"No cost": "Jokių išlaidų",
|
||||
"Low cost": "Maža kaina",
|
||||
"Medium cost": "Vidutinė kaina",
|
||||
"High cost": "Didelė kaina",
|
||||
"Saves time": "Taupo laiką",
|
||||
"Graphics": "Grafika",
|
||||
"Done": "Atlikta",
|
||||
"This feature is not yet available in Horizon.": "Ši funkcija Horizonte dar nepasiekiama.",
|
||||
"Your display or system doesn't offer HDR output.": "Jūsų ekranas arba sistema nesiūlo HDR išvesties.",
|
||||
"Turn HDR on to use it.": "Norėdami jį naudoti, įjunkite HDR.",
|
||||
"Your GPU or graphics API doesn't support it.": "Jūsų GPU arba grafikos API to nepalaiko.",
|
||||
"%s chunks": "%s gabaliukai",
|
||||
"%s per frame": "%s vienam kadrui",
|
||||
"%s MiB/frame": "%s MiB/kadras",
|
||||
"%s KiB/frame": "%s KiB/kadras",
|
||||
"Auto (%s)": "Automatinis (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Išsamus reljefo diapazonas. LOD atstumas gali išplėsti vietinius pasaulius toliau.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Grubus tolimas reljefas vietiniuose pasauliuose. Integruoti GPU sustoja ties 128 gabalėliais.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Sumažintas piešimas kur kas grubesnis: greičiau naudojant šį integruotą GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Sumažintas reljefas traukia stambesnį, o GPU – lėtėja.",
|
||||
"Field of view. Wider shows more terrain at once.": "Matymo laukas. Plačiau vienu metu rodo daugiau reljefo.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Caps kadrų per sekundę; dangtelis taupo energiją ir šilumą.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Laukiama ekrano atnaujinimo: be plyšimų, šiek tiek delsos.",
|
||||
"Size of menus and the HUD.": "Meniu dydis ir HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Kartu nustatomos toliau pateiktos parinktys. Pakeitus vieną, jis tampa individualus.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, ugnis ir saulės šviesa šviečia ryškiau nei balta. Reikia HDR ekrano.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Jūsų ekranas tamsiausias juodas. 0 OLED; LCD ekranai šiek tiek šviečia.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Kaip ryškūs akcentai. Automatinis: didžiausias jūsų sistemos pranešimas.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Kaip atrodo ryškiai baltas popierius ir meniu. Automatinis: jūsų darbalaukis yra baltas.",
|
||||
"How bright the HUD and menus are, against paper white.": "Koks ryškus yra HUD ir meniu, palyginti su popieriaus baltumu.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Kiek toliau dirba minios, javai ir mašinos. CPU laikas.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Kaip toli minios ir daiktai nupiešti, kaip įprasta dalis.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Praleidžia reljefą, paslėptą už uždarų atkarpų. Išjungti kainuoja daug.",
|
||||
"Hides entities behind opaque blocks.": "Slepia esybes už nepermatomų blokų.",
|
||||
"No entity occlusion culling.": "Jokio subjekto okliuzijos skerdimo.",
|
||||
"CPU occlusion test runs this frame.": "CPU okliuzijos testas paleidžia šį kadrą.",
|
||||
"Occlusion checks run on a worker thread.": "Užsikimšimo patikros vykdomos darbuotojo gijoje.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Viename kadre įkeltos tinklinės sekcijos. Apatinė yra sklandesnė kraunant.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Švelnūs šešėliai kampuose. Kainuoja tik statybos laiką.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Mažesnės blokelių tekstūros kopijos tolimoje vietovėje: mažiau mirgėjimo.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Sujungia žolės ir vandens spalvas per biomus, kai stato gabalus.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Debesų sluoksnis: išjungtas, plokščias (greitas) arba 3D (išgalvotas).",
|
||||
"How far clouds reach, in chunks.": "Kiek debesys siekia, gabalais.",
|
||||
"Draws the sky, sun, moon and stars.": "Piešia dangų, saulę, mėnulį ir žvaigždes.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Atstumas rūkas. Išjungta rodo įkrauto pasaulio kraštą.",
|
||||
"Draws rain and snow.": "Piešia lietų ir sniegą.",
|
||||
"How far rain and snow fall around you.": "Kaip toli aplink jus krinta lietus ir sniegas.",
|
||||
"How many particles effects spawn.": "Kiek dalelių poveikio atsiranda.",
|
||||
"Round shadows under mobs and items.": "Apvalūs šešėliai po miniomis ir daiktais.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Lapai su permatomais tarpais. Off atkreipia juos kietas.",
|
||||
"Sorts water and glass more exactly behind each other.": "Tiksliau vienas už kitą rūšiuoja vandenį ir stiklą.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Kuris gabalas atstato laukia kadro: nėra, šalia ar jūsų.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animuoja vandenį, lavą, ugnį ir kitas judančias tekstūras.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Pasaulio pikselių vienam ekrano taškui. Žemiau 100 % yra greitesnis GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Išlygina dantytus kraštus. FXAA: minkštesnis. TAA: sklandžiausi, judantys daiktai gali sekti.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Padaro pasaulį mažesnį ir atkuria jį su TAA. Greičiau, švelniau.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR išvestis HDR ekrane: lava, ugnis ir saulės šviesa šviečia ryškiau.",
|
||||
"How frames reach the screen while VSync is off.": "Kaip kadrai pasiekia ekraną, kai VSync išjungtas.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Efektyvus miegas iki kito kadro; Tikslūs sukimai, kaip vanilė.",
|
||||
"Fills the screen (borderless).": "Užpildo ekraną (be rėmelių).",
|
||||
"Brightens dark places.": "Šviesina tamsias vietas.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Sumažina kadrų dažnį, kai jis sumažinamas arba veikia tuščiąja eiga.",
|
||||
"Sways the view while walking.": "Vaikštant vingiuoja vaizdas.",
|
||||
"Fades newly loaded chunks in.": "Išblukina naujai įkeltus gabalus.",
|
||||
"Darkens the screen's edges.": "Tamsina ekrano kraštus.",
|
||||
"Shows when the world saves.": "Rodo, kada pasaulis gelbsti.",
|
||||
"Shows attack cooldown.": "Rodo atakos atšalimą.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS dangtelis (30 meniu arba nefokusuotas), pusė darbuotojų: mažiau energijos.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Siūlai, kurie sukuria, apšviečia ir tinklelio gabaliukus. Kitas startas.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "„Vulkan“, tada „DirectX 12“ („Windows“) arba „OpenGL“ („Linux“), kur nepavyksta. Kitas startas.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Vanduo ir stiklas viename buferyje: mažiau skambučių, šiek tiek daugiau VRAM. Kitas startas.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Vieno kadro tinklelio duomenys siunčiami į GPU. Apatinė yra sklandesnė kraunant.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Kadrų dažnis ir kadrų laikas žaidime su diagrama.",
|
||||
"horizon.multiplayer.finding_friend": "Atrask savo draugo pasaulį...",
|
||||
"horizon.multiplayer.not_signed_in": "Šis serveris veikia prisijungimo režimu. Prisijunkite naudodami „Microsoft“ paskyrą „Horizon“ paleidimo priemonėje, kad prisijungtumėte prie jos.",
|
||||
"horizon.multiplayer.invalid_address": "Neteisingas serverio adresas: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Prisijungę tiesiai su draugu...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Prisijungta prie savo draugo per relę...",
|
||||
"General": "Generolas",
|
||||
"Advanced": "Išplėstinė",
|
||||
"Undo": "Anuliuoti",
|
||||
"Search options": "Paieškos parinktys",
|
||||
"Shortcut": "Spartusis klavišas",
|
||||
"Distance": "Atstumas",
|
||||
"Display": "Ekranas",
|
||||
"Interface": "Sąsaja",
|
||||
"Sky and weather": "Dangus ir oras",
|
||||
"Lighting and entities": "Apšvietimas ir objektai",
|
||||
"Textures": "Tekstūros",
|
||||
"Image": "Vaizdas",
|
||||
"Chunk loading": "Gabalų pakrovimas",
|
||||
"Culling": "Skerdimas",
|
||||
"Power": "Galia",
|
||||
"Frames": "Rėmeliai",
|
||||
"Overlay": "Perdanga",
|
||||
"Output": "Išvestis",
|
||||
"Brightness": "Ryškumas",
|
||||
"How far you see, the window and the HUD.": "Kaip toli matote, langas ir HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Dangus ir oras, apšvietimas, faktūros ir vaizdas.",
|
||||
"Chunk loading, culling and power use.": "Gabalų pakrovimas, skerdimas ir energijos naudojimas.",
|
||||
"How frames reach the screen, and the performance overlay.": "Kaip kadrai pasiekia ekraną ir našumo perdanga.",
|
||||
"Brighter-than-white output on an HDR display.": "Ryškesnė nei balta išvestis HDR ekrane.",
|
||||
"Results for \"%s\"": "Rezultatai pagal „%s“",
|
||||
"No settings match.": "Jokie nustatymai neatitinka.",
|
||||
"horizon.title.not_affiliated": "Nesusijęs su „Mojang“ ar „Microsoft“.",
|
||||
"horizon.extras.header.lighting": "Apšvietimas",
|
||||
"horizon.extras.header.third_person": "Trečiasis asmuo",
|
||||
"horizon.extras.header.particles": "Dalelės",
|
||||
"horizon.extras.header.inventory": "Inventorius",
|
||||
"horizon.extras.header.chat": "Pokalbis",
|
||||
"horizon.extras.header.multiplayer": "Kelių žaidėjų",
|
||||
"horizon.extras.header.sound": "Garsas",
|
||||
"horizon.extras.header.menus": "Meniu",
|
||||
"horizon.extras.dynamic_lights": "Dinaminiai žibintai",
|
||||
"horizon.extras.own_name_tag": "Nuosavo vardo žyma",
|
||||
"horizon.extras.hide_block_particles": "Slėpti blokų lūžimo daleles",
|
||||
"horizon.extras.auto_reconnect": "Automatinis pakartotinis prisijungimas",
|
||||
"horizon.extras.ui_sound_reserve": "Išsaugokite vartotojo sąsajos garsų kanalus",
|
||||
"horizon.extras.music_page": "Muzikos puslapis",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks valdikliai",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Slinkties ratukas perkelia elementus",
|
||||
"horizon.extras.sort": "Rūšiavimo mygtukai",
|
||||
"horizon.extras.sort_key": "Rūšiavimo raktas",
|
||||
"horizon.extras.no_ai": "Nėra AI",
|
||||
"horizon.extras.agent_control": "Agento kontrolė",
|
||||
"horizon.extras.splitscreen": "Padalintas ekranas",
|
||||
"horizon.extras.chat_animation": "Sklandus pokalbis",
|
||||
"horizon.extras.chat_indicator": "Pranešimo indikatorius",
|
||||
"horizon.extras.wheel_cycles_settings": "Slinkties ratukas keičia nustatymus",
|
||||
"horizon.extras.pack_browser": "Išteklių paketo naršyklė",
|
||||
"horizon.extras.fullbright.tooltip": "Rodyti blokus ir objektus esant visiškam apšvietimui, net tamsiose srityse.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Fakelai ir kiti šviesos šaltiniai laikė, numetė ar degė, apšviečia aplink juos esančius blokus.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Rodo jūsų vardą virš galvos trečiuoju asmeniu, kaip mato kiti žaidėjai. Paslėpta, kai serveris slepia jūsų komandos pavadinimus arba esate nematomas.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Slepia nuolaužų daleles, kurios sprogo iš sulaužyto bloko ir nutrupėjo nuo to, kurį užminate. Kitos dalelės lieka.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Vėl prisijungia prie serverio nutrūkus ryšiui arba paleidus iš naujo, niekada po smūgio ar uždraudimo.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Garsai pasaulyje palieka laisvus 16 iš 247 garso kanalų, todėl jų potvynis (didelis sprogimas) negali nutildyti meniu paspaudimų.",
|
||||
"horizon.extras.music_page.tooltip": "Muzika ir garsai gauna puslapį Muzika...: dabar paleiskite bet kurį integruotą takelį arba išjunkite jį, kad žaidimas nustotų jį pasirinkti. Niekas nepasikeičia, kol neišjungiate takelio.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Dešiniuoju pelės mygtuku perkeliamas vienas į lizdą, kairėn – surenkamas sulaikytas elementas, o klavišas „Shift“ + vilkite kairėn – perkelia kiekvieną lizdą.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Žemyn vieną lizdo elementą siunčia į kitą pusę, o aukštyn – atgal.",
|
||||
"horizon.extras.sort.tooltip": "Rūšiuoja inventorių, skrynias, statines, enderų skrynias ir šulerių dėžutes kūrybine skirtukų tvarka, sujungia krūvas.",
|
||||
"horizon.extras.sort_key.tooltip": "Rūšiuoja dalį po žymekliu. Spustelėkite, tada paspauskite klavišą; Pabėgimas jį atriša.",
|
||||
"horizon.extras.no_ai.tooltip": "Paslepia ir išjungia visas AI funkcijas: agento valdymą, horizon-ctl ir MCP serverius. Taip pat paleidimo jungiklis.",
|
||||
"horizon.extras.agent_control.tooltip": "Jūsų dirbtinio intelekto agentas gali žaisti šį žaidimą per „horizon-ctl“ arba MCP klientą tik šiame kompiuteryje.",
|
||||
"horizon.extras.discord_presence.tooltip": "Rodo „Playing Horizon“ jūsų „Discord“ profilyje, kol veikia „Discord“: meniu, vieno žaidėjo ar kelių žaidėjų režimas, matmenys ir žaidimo laikas. Niekada pasaulio pavadinimo ar serverio adreso.",
|
||||
"Calibrate...": "Kalibruoti...",
|
||||
"Calibrate Anyway...": "Vis tiek kalibruoti...",
|
||||
"Use Automatic Setup": "Naudokite automatinę sąranką",
|
||||
"Shortcut: %s": "Spartusis klavišas: %s",
|
||||
"Next": "Kitas",
|
||||
"HDR Calibration %s/%s: %s": "HDR kalibravimas %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Vienu metu nustatomi trys žemiau esantys viso ekrano vaizdai, po vieną bandomąją nuotrauką.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Raktas, kuris atidaro šį žaidimo ekraną. Iš pradžių neįrištas.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Ekrane nebuvo nurodyta, koks jis ryškus: sukalibruokite, kad jį nustatytumėte.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Jau nustatyta jūsų ekranui. Kalibruokite tik tada, jei HDR atrodo neteisingai.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Sugrąžina didžiausią ryškumą ir popieriaus baltumą į ekrano sąranką.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Nustatykite savo ekraną. Palikite įjungtą „Auto“, nebent tai atrodo neteisinga.",
|
||||
"Lower it until the logo just disappears into the black.": "Nuleiskite jį, kol logotipas tiesiog išnyks juodai.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Pakelkite jį, kol logotipas tiesiog išnyks šviesioje aikštėje.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Padarykite kortelę baltą, į kurią būtų patogu žiūrėti ilgą laiką, žaidžiant šviesoje.",
|
||||
"horizon.splitscreen.profiles.forget": "Pamiršk",
|
||||
"horizon.splitscreen.profiles.create": "Sukurti",
|
||||
"horizon.splitscreen.profiles.you": " - tu",
|
||||
"horizon.splitscreen.profiles.forget_named": "Pamiršk %s",
|
||||
"horizon.splitscreen.profiles.previous": "Ankstesnis",
|
||||
"horizon.splitscreen.profiles.next": "Kitas",
|
||||
"horizon.splitscreen.profiles.new_button": "Naujas profilis...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Pamiršai profilį?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Jis palieka šio atvejo sąrašą.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Pasaulio progresas ir nustatymai išlaikomi.",
|
||||
"horizon.splitscreen.profiles.new_title": "Naujas profilis",
|
||||
"horizon.splitscreen.profiles.name_hint": "Vardas (nuo 3 iki 16 raidžių, skaitmenų, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Perjungti profilį",
|
||||
"horizon.splitscreen.profiles.offline_note": "Šiam atvejui priklauso profiliai neprisijungus",
|
||||
"horizon.splitscreen.profiles.move": "Perkelkite ten, kur žaidžiate",
|
||||
"horizon.splitscreen.profiles.move_place": "Perkelkite ten, kur žaidžiate: %s",
|
||||
"horizon.friends.title": "Žaisk su draugais",
|
||||
"horizon.friends.hint": "Draugai įklijuoja kodą po pavadinimo ekrano mygtuko „Draugai“, tiesioginiame prisijungime arba paleidimo priemonės „Play“ puslapyje.",
|
||||
"horizon.friends.copied": "Nukopijuota",
|
||||
"horizon.friends.copy_code": "Kopijuoti kodą",
|
||||
"horizon.friends.stop_sharing": "Sustabdyti bendrinimą",
|
||||
"horizon.friends.none_yet": "Draugų čia dar nėra.",
|
||||
"horizon.friends.here_now": "Čia dabar: %s",
|
||||
"horizon.friends.share_intro": "Bendrinkite šį pasaulį naudodami kvietimo kodą: jis atidaromas LAN, o draugai internete prisijungia naudodami kodą. Joks mūsų serveris nedalyvauja.",
|
||||
"horizon.friends.start_sharing": "Pradėti bendrinti",
|
||||
"horizon.friends.troubleshooting": "Trikčių šalinimas...",
|
||||
"horizon.friends.stopped": "Nustojo bendrinti kvietimo kodą.",
|
||||
"horizon.friends.short_code_changed": "Kažkas vis bandė neteisingą trumpąjį kodą, todėl jis pasikeitė.",
|
||||
"horizon.friends.wrong_code": "Kažkas bandė neteisingą kodą.",
|
||||
"horizon.friends.code_ready": "Jūsų kvietimo kodas paruoštas: žaidimų meniu, žaiskite su draugais. Nukopijuokite ir nusiųskite draugams.",
|
||||
"horizon.friends.share_failed": "Nepavyko pasidalinti pasauliu: %s",
|
||||
"horizon.friends.finding_route": "Sužinokite, kaip draugai gali su jumis susisiekti…",
|
||||
"horizon.osk.space": "Erdvė",
|
||||
"horizon.osk.delete": "Ištrinti",
|
||||
"horizon.osk.type": "Tipas",
|
||||
"horizon.osk.symbols": "Simboliai",
|
||||
"horizon.osk.close": "Uždaryti",
|
||||
"horizon.extras.chat_animation.tooltip": "Nauja pokalbio linija slenka iš kairės, kol ji išnyksta, o ne atrodo visa.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Spalvota juosta pokalbio linijos kairėje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Slinkties ratukas virš mygtuko nustatymuose keičia jo reikšmę, kaip ir vanilės atveju. Išjungtas, ratas tik slenka sąrašu.",
|
||||
"horizon.extras.pack_browser.tooltip": "Pasirinkite Išteklių paketai gauna mygtuką Pridėti, kuris ieško Modrinth ir įdiegia paketą į jūsų paketų aplanką. Išjungtas, žaidimas niekada nesusisiekia su Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Pasaulyje kitas valdiklis gali prisijungti kaip naujas žaidėjas: paspauskite A, kai jis paprašys, tada pasirinkite profilį ir kur žaisti.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorius %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitorius",
|
||||
"horizon.splitscreen.setup.window": "Langas %s",
|
||||
"horizon.splitscreen.setup.new_window": "Naujas langas",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Užpildykite %s",
|
||||
"horizon.splitscreen.setup.done": "Atlikta",
|
||||
"horizon.splitscreen.setup.on": "Įjungta",
|
||||
"horizon.splitscreen.setup.off": "Išjungta",
|
||||
"horizon.splitscreen.setup.player": "Žaidėjas %s",
|
||||
"horizon.splitscreen.setup.revert": "Grįžti",
|
||||
"horizon.splitscreen.setup.apply": "Taikyti",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Niekas nežaidžia 1 lange: jis slepiasi",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Lange %s niekas negroja: jis užsidaro, kai rodoma Taikyti",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Lange %s niekas nežaidžia: jis neatsidarys",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Langas %s užpildo 1 lango monitorių: jo grotuvai žaidžia 1 lange",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Lango %s monitorius neprijungtas: jo grotuvai žaidžia 1 lange",
|
||||
"horizon.splitscreen.setup.status.off": "Splitscreen išjungtas.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Įjunkite jį, tada paspauskite kito valdiklio mygtuką visame pasaulyje.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Paspauskite A, kad prisijungtumėte ir pasirinktumėte profilį.",
|
||||
"horizon.splitscreen.setup.status.idle": "Padalintas ekranas neaktyvus",
|
||||
"horizon.splitscreen.setup.status.alone": "Kitų vietinių žaidėjų nėra.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Vieni valdikliai ir valdiklio atsiliepimai nesikeičia.",
|
||||
"horizon.splitscreen.setup.window_empty": "Langas %s: tuščias",
|
||||
"horizon.splitscreen.setup.note.select": "Spustelėkite grotuvą arba vilkite jį į langą arba monitorių.",
|
||||
"horizon.splitscreen.setup.note.apply": "Pateikite paraišką, jei norite žaisti tokiu būdu, grąžinkite, kad anuliuotumėte.",
|
||||
"horizon.splitscreen.setup.note.place": "Kur %s turėtų žaisti?",
|
||||
"horizon.splitscreen.setup.this_player": "šis žaidėjas",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen išsijungia, kai kiti išeina (jų pristabdymo meniu).",
|
||||
"horizon.splitscreen.setup.window_place": "Langas %s (%s iš %s)",
|
||||
"horizon.splitscreen.setup.names": "Vardai",
|
||||
"horizon.splitscreen.setup.identify": "Identifikuoti",
|
||||
"horizon.splitscreen.setup.layout.two": "2 bendrinimas: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 bendrinimas: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Šalia",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Viršuje ir apačioje",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Vienas platus viršuje",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Vienas aukštas kairėje",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Šalia",
|
||||
"horizon.splitscreen.setup.help.player": "Spustelėkite arba vilkite %s, kad juos perkeltumėte.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (greta, kai langas platus). Paspauskite norėdami pakeisti.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Paspauskite norėdami pakeisti.",
|
||||
"horizon.splitscreen.setup.help.switch": "Padalytas ekranas šiam kompiuteriui, įjungtas arba išjungtas.",
|
||||
"horizon.splitscreen.setup.help.identify": "Kiekvieno žaidėjo vaizdas rodo, kuriame lange jis yra.",
|
||||
"horizon.splitscreen.setup.help.names": "Kiekvieno žaidėjo vardas kampe.",
|
||||
"horizon.splitscreen.setup.help.revert": "Anuliuoti dar nepritaikytus pakeitimus.",
|
||||
"horizon.splitscreen.setup.help.apply": "Žaiskite taip, kaip rodo žemėlapis.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Dar niekas nepasikeitė.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Atsisakykite padėklo",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Valdiklis prijungtas",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Valdiklis atjungtas",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Prijunkite jį iš naujo arba paspauskite kito mygtuką",
|
||||
"horizon.splitscreen.prompt.join.one": "%s nori prisijungti",
|
||||
"horizon.splitscreen.prompt.join.many": "%s valdikliai nori prisijungti",
|
||||
"horizon.splitscreen.prompt.join.yes": "Prisijunk",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Žaisti kaip P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Nr",
|
||||
"horizon.splitscreen.prompt.lost": "%s: valdiklis atjungtas",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Naudokite kitą",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Palikti",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Valdiklis nustoja jus vairuoti ir jam siūloma prisijungti prie (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s į %s: A numeta jį čia, o B padeda atgal.",
|
||||
"horizon.splitscreen.setup.note.held": "%s pakeliama: kairė/dešinė pasirenka kur, A numeta, B grąžina atgal.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Slepia modelius už nepermatomų blokų. Async tikrina ne giją; subjektai pažymi.",
|
||||
"Block Entities": "Blokuoti subjektus",
|
||||
"Shulker Boxes": "Šulkerio dėžės",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Pakartotinai naudoja nusistovėjusias dėžes; dangtelio judėjimas išlieka animuotas.",
|
||||
"Signs": "Ženklai",
|
||||
"Reuses sign text until text, light, or font changes.": "Pakartotinai naudoja ženklo tekstą, kol pasikeičia tekstas, šviesa arba šriftas.",
|
||||
"Decorated Pots": "Dekoruoti puodai",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Pakartotinai naudojami nejudančių puodų; nutraukti klibėjimą ir šerdies pokyčius perbraižyti.",
|
||||
"Bells": "Varpai",
|
||||
"Reuses still bells; ringing stays animated.": "Pakartotinai naudoja nejudančius varpelius; skambėjimas išlieka animuotas.",
|
||||
"Chests": "Skrynias",
|
||||
"Reuses closed chests; moving lids stay animated.": "Pakartotinai naudoja uždaras skrynias; judantys dangteliai išlieka animuoti.",
|
||||
"Banners": "Baneriai",
|
||||
"Reuses poles; flags and pattern layers still update.": "Pakartotinai naudoja stulpus; vėliavėlės ir raštų sluoksniai vis dar atnaujinami.",
|
||||
"Copper Golem Statues": "Varinės Golemo statulos",
|
||||
"Reuses statue geometry until its block state changes.": "Pakartotinai naudoja statulos geometriją, kol nepasikeis jos bloko būsena.",
|
||||
"Leaf Culling": "Lapų skerdimas",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Kiekvieno lapo veidas. Įmantrūs vanilės lapai.",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Išlaiko puikius augalus didelės raiškos pakuotėse nuo išblukimo toli.",
|
||||
"Screenshot Quality": "Ekrano nuotraukos kokybė",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2 ekrano kopijos tokia daug kartų didesnė už žaidimo skiriamąją gebą. Aukštesnis užtrunka ilgiau.",
|
||||
"Frame Generation": "Rėmelių generavimas",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Rodo sugalvotą kadrą tarp dviejų tikrų: sklandžiau, šiek tiek daugiau atsilikimo.",
|
||||
"Shadow Cache": "Šešėlių talpykla",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Pakartotinai naudoja pakuotės šešėlinį žemėlapį, kai sutampa saulė, vaizdas ir pasaulis. Išjungta pagal numatytuosius nustatymus.",
|
||||
"Find BlueMap": "Raskite BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Serveriuose randa jų „BlueMap“ žiniatinklio žemėlapį, skirtą tolimam reljefui. Kitas prisijungti.",
|
||||
"BlueMap Radius": "BlueMap spindulys",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Kaip toli aplink jus atsisiunčiamos „BlueMap“ plytelės. Kitas prisijungti.",
|
||||
"Synchronous": "Sinchroninis",
|
||||
"Asynchronous": "Asinchroninis",
|
||||
"Solid": "Tvirtas",
|
||||
"Solid Aggressive": "Tvirtas Agresyvus",
|
||||
"Hollow": "Tuščiaviduriai",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Lapai, esantys visiškai viduje, traukiasi kietai: mažiau permatomų sluoksnių.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Lapai su viena atvira puse taip pat traukiasi: greitesni, stambesni kraštai.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Numeta veidus tarp lapų: greičiau, bet tarpai matosi kiaurai.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Ankstesnis lizdas",
|
||||
"Hotbar: Next Slot": "Hotbar: Kitas lizdas",
|
||||
"horizon.agent.connected.one": "Prisijungta: 1 agento ryšys",
|
||||
"horizon.agent.connected.many": "Prisijungta: %s agento ryšiai",
|
||||
"horizon.agent.connected.one_ago": "Prisijungta: 1 agento ryšys, paskutinė komanda prieš %s sek",
|
||||
"horizon.agent.connected.many_ago": "Prisijungta: %s agento jungtys, paskutinė komanda prieš %s sek",
|
||||
"horizon.agent.waiting": "Laukia agento.",
|
||||
"horizon.agent.off": "Išjungta: agentai negali pasiekti šio žaidimo.",
|
||||
"horizon.agent.session_opened": "Šiam seansui atidarė programą, kuri pradėjo žaidimą.",
|
||||
"horizon.agent.socket": "Lizdas: %s (nukopijuokite išsamią informaciją apie visą jo kelią)",
|
||||
"horizon.agent.local_only": "Tik vietinis: agentai šiame kompiuteryje, niekada tinkle.",
|
||||
"Auto (%s nits)": "Automatinis (%s nitai)",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Slepia modelius už nepermatomų blokų. Subjektai atnaujinami. Išjungti su pasirinktiniais paketais.",
|
||||
"horizon.extras.repro_keys": "Įrašymo spartieji klavišai",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R pradeda arba sustabdo repro įrašymą, o F3+E įrašo jo paketą. Išjungta pagal numatytuosius nustatymus: įrašas kopijuoja pasaulį.",
|
||||
"horizon.agent.record_session": "Pradėkite įrašymo sesiją",
|
||||
"horizon.agent.record_stop": "Sustabdykite įrašymo sesiją",
|
||||
"horizon.agent.export_repro": "Eksportuokite Repro paketą",
|
||||
"horizon.repro.started": "Pradėtas įrašymas; F3+R jį sustabdo ir įrašo paketą.",
|
||||
"horizon.repro.stopped": "Įrašymas sustabdytas: %s",
|
||||
"horizon.repro.bundle": "Repro rinkinys: %s",
|
||||
"horizon.repro.no_recording": "Dar nėra įrašo, kurį būtų galima eksportuoti.",
|
||||
"horizon.repro.replay_started": "Pakartojimas pradėtas: %s",
|
||||
"horizon.pack.addInfo": "Pridėkite išteklių paketą iš Modrinth",
|
||||
"horizon.pack.addWorking": "Klausia Modrinth...",
|
||||
"horizon.pack.addNone": "Nerasta jokių išteklių paketų",
|
||||
"horizon.pack.addTitle": "Pridėkite išteklių paketą",
|
||||
"horizon.pack.install": "Įdiegti",
|
||||
"horizon.pack.installing": "Diegimas",
|
||||
"horizon.pack.installed": "Įdiegta",
|
||||
"horizon.pack.search": "Ieškoti",
|
||||
"horizon.pack.searchHint": "Ieškoti Modrinth...",
|
||||
"horizon.pack.filters": "Filtrai",
|
||||
"horizon.pack.filter.realistic": "Realistiškas",
|
||||
"horizon.pack.filter.simplistic": "Supaprastinta",
|
||||
"horizon.pack.filter.vanillaLike": "Vanilės tipo",
|
||||
"horizon.pack.addFailed": "Nepavyko pridėti paketo: %s",
|
||||
"horizon.extras.header.cosmetics": "Kosmetika",
|
||||
"horizon.extras.cosmetics_mine": "Mano kosmetika",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Paslėpta: kiekvienas „Horizon“ žaidėjas mato jus be jūsų kosmetikos, įskaitant jus. Tas pats jungiklis kaip ir paleidimo priemonės drabužių spinta.",
|
||||
"horizon.extras.cosmetics_others": "Kitų žaidėjų kosmetika",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ką dėvi kiti „Horizon“ žaidėjai: viskas, tik jų vardų etiketės arba nieko. Paslėptas, žaidimas niekada neklausia, ką jie dėvi.",
|
||||
"horizon.extras.cosmetics_auras": "Kitų auros ir takai",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auros ir takai, kuriuos nešioja kiti žaidėjai. Išjungta pagal numatytuosius nustatymus.",
|
||||
"horizon.extras.cosmetics.shown": "Parodyta",
|
||||
"horizon.extras.cosmetics.hidden": "Paslėpta",
|
||||
"horizon.extras.cosmetics.name_tags": "Tik vardų žymos"
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
{
|
||||
"horizon.options.controller": "Controleur",
|
||||
"horizon.options.vibration": "Trillingen",
|
||||
"horizon.options.adaptive_triggers": "Adaptieve triggers",
|
||||
"horizon.options.lightbar": "Lichtbalk",
|
||||
"horizon.options.look_sensitivity": "Kijkgevoeligheid",
|
||||
"horizon.options.invert_x": "Kijkrichting X omkeren",
|
||||
"horizon.options.invert_y": "Kijkrichting Y omkeren",
|
||||
"horizon.options.sneak_button": "Sluipknop",
|
||||
"horizon.options.move_deadzone": "Dode zone voor bewegen",
|
||||
"horizon.options.look_deadzone": "Dode zone voor kijken",
|
||||
"horizon.options.button_glyphs": "Knop-glyphs",
|
||||
"horizon.options.menu_navigation": "Menunavigatie",
|
||||
"horizon.options.on_screen_keyboard": "Schermtoetsenbord",
|
||||
"horizon.options.chat_delay": "Chatvertraging",
|
||||
"horizon.options.lightbar.health": "Gezondheid",
|
||||
"horizon.options.lightbar.dimension": "Afmeting",
|
||||
"horizon.options.glyphs.auto": "Automatisch",
|
||||
"horizon.options.menu_pointer": "Aanwijzer",
|
||||
"horizon.options.keyboard.auto": "Automatisch",
|
||||
"horizon.options.keyboard.steam": "Stoom",
|
||||
"horizon.options.unavailable": "Deze functie is nog niet beschikbaar in Horizon.",
|
||||
"horizon.options.search": "Zoekinstellingen...",
|
||||
"horizon.pages.shader_packs": "Shader-pakketten",
|
||||
"horizon.pages.shader_options": "Shader-opties",
|
||||
"horizon.pages.controller": "Controllerinstellingen",
|
||||
"horizon.pages.controller_binds": "Controller bindt",
|
||||
"horizon.pages.extras": "Horizon-extra's",
|
||||
"horizon.pages.settings": "Horizon-instellingen",
|
||||
"horizon.pages.agent": "AI-agent",
|
||||
"horizon.split.leave": "Vertrekken",
|
||||
"horizon.pages.split_settings": "Instellingen voor gesplitst scherm",
|
||||
"horizon.pages.split_profiles": "Wie speelt er?",
|
||||
"horizon.pages.split_setup": "Gesplitst scherm instellen",
|
||||
"horizon.pages.music": "Muziek",
|
||||
"horizon.pages.music.menu": "Menukaart",
|
||||
"horizon.pages.music.overworld": "Bovenwereld",
|
||||
"horizon.pages.music.creative": "Creatief",
|
||||
"horizon.pages.music.underwater": "Onder water",
|
||||
"horizon.pages.music.end": "Het einde",
|
||||
"horizon.pages.music.boss": "Baas",
|
||||
"horizon.pages.music.credits": "Kredieten",
|
||||
"horizon.pages.music.overworld_biomes": "Overwereldbiomen",
|
||||
"horizon.pages.music.nether_biomes": "Nether biomen",
|
||||
"horizon.pages.music.end_biomes": "Beëindig biomen",
|
||||
"horizon.pages.music.other": "Andere muziek",
|
||||
"horizon.pages.music.empty": "Geen muzieknummers: de geluiden van het spel worden niet geladen.",
|
||||
"horizon.pages.music.tooltip": "De ingebouwde nummers, per situatie: speel er nu één, of schakel er een uit zodat het spel stopt met het kiezen ervan.",
|
||||
"horizon.pages.settings.tooltip": "Horizon's eigen instellingen: graphics, controllers, extra's en mods.",
|
||||
"horizon.pages.graphics.tooltip": "Grafisch eenvoudig: elke instelling zegt wat het kost.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Elke grafische instelling, inclusief Horizon's, met wat elke kost.",
|
||||
"horizon.pages.controller.tooltip": "Controllers: hun knoppen, sticks, trillingen en menunavigatie.",
|
||||
"horizon.pages.extras.tooltip": "Optionele toevoegingen, standaard allemaal uitgeschakeld: het spel blijft vanille totdat je er een inschakelt.",
|
||||
"horizon.pages.agent.tooltip": "Laat je eigen AI-agent spelen: horizon-ctl en MCP-clients op deze computer.",
|
||||
"horizon.pages.split_profiles.tooltip": "Wie speelt: een profiel dat in deze gamemap wordt bewaard.",
|
||||
"horizon.pages.split_setup.tooltip": "De ramen en monitoren, en wie waar speelt.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader-pakketten uit de map shaderpacks: die van Horizon en die in het shaders/-formaat. Er loopt er één tegelijk.",
|
||||
"horizon.pages.mods.tooltip": "Mods gemaakt voor Horizon, uit de map horizon-mods. Ze worden allemaal in een sandbox uitgevoerd en blijven uitgeschakeld totdat u ze inschakelt.",
|
||||
"horizon.pages.open.graphics": "Grafisch...",
|
||||
"horizon.pages.open.graphics_advanced": "Geavanceerde grafische afbeeldingen...",
|
||||
"horizon.pages.open.shader_packs": "Shader-pakketten...",
|
||||
"horizon.pages.open.controller": "Controllerinstellingen...",
|
||||
"horizon.pages.open.split_setup": "Gesplitst scherm...",
|
||||
"horizon.pages.open.extras": "Extra's...",
|
||||
"horizon.pages.open.agent": "AI-agent...",
|
||||
"horizon.pages.open.horizon_graphics": "Horizon-afbeeldingen...",
|
||||
"horizon.pages.open.controller_binds": "Controller bindt...",
|
||||
"horizon.pages.open.horizon_settings": "Horizon-instellingen...",
|
||||
"horizon.pages.open.music": "Muziek...",
|
||||
"horizon.mods.empty": "Geen mods: plaats .hmod-bestanden in de map horizon-mods",
|
||||
"horizon.mods.stopped": " (gestopt)",
|
||||
"horizon.mods.not_built": " (Niet gebouwd)",
|
||||
"horizon.mods.dev": " (Ontwikkelaar)",
|
||||
"horizon.mods.settings": "Instellingen",
|
||||
"horizon.mods.in_world": " (Laat de wereld veranderen)",
|
||||
"horizon.mods.open_folder": "Open de Mod-map",
|
||||
"horizon.shader_packs.empty": "Geen shader packs: plaats ze in de map shaderpacks",
|
||||
"horizon.shader_options.open": "Shader-opties...",
|
||||
"horizon.shader_options.profile": "Profiel",
|
||||
"horizon.shader_options.loading": "De opties van het shader-pakket lezen...",
|
||||
"horizon.agent.copy_details": "Kopieer verbindingsgegevens",
|
||||
"horizon.multiplayer.join": "Doe mee",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Annuleren Opnieuw verbinden",
|
||||
"horizon.multiplayer.join_friend": "Sluit je aan bij een vriend",
|
||||
"horizon.multiplayer.invite_code": "Uitnodigingscode",
|
||||
"horizon.multiplayer.relay": "relais (optioneel)",
|
||||
"Preset": "Voorinstelling",
|
||||
"LOD Distance": "LOD-afstand",
|
||||
"Far Detail": "Verre details",
|
||||
"Cave Culling": "Grot ruimen",
|
||||
"Entity Culling": "Entiteit ruimen",
|
||||
"Chunk Updates": "Chunk-updates",
|
||||
"Upload Budget": "Budget uploaden",
|
||||
"Sky": "Hemel",
|
||||
"Fog": "Mist",
|
||||
"Weather": "Weer",
|
||||
"Animated Textures": "Geanimeerde texturen",
|
||||
"Present Mode": "Huidige modus",
|
||||
"Frame Pacing": "Frametempo",
|
||||
"Worker Threads": "Werknemersdraden",
|
||||
"Resolution Scale": "Resolutieschaal",
|
||||
"Upscaling": "Opschaling",
|
||||
"Paper White": "Papier Wit",
|
||||
"Peak Brightness": "Piekhelderheid",
|
||||
"Black Level": "Zwart niveau",
|
||||
"UI Brightness": "UI-helderheid",
|
||||
"Battery Mode": "Batterijmodus",
|
||||
"Graphics API": "Grafische API",
|
||||
"Translucent Batching": "Doorschijnend batchen",
|
||||
"Performance Overlay": "Prestatie-overlay",
|
||||
"Full Detail Distance": "Volledige detailafstand",
|
||||
"On": "Aan",
|
||||
"Off": "Uit",
|
||||
"Unlimited": "Onbeperkt",
|
||||
"Auto": "Automatisch",
|
||||
"Custom": "Aangepast",
|
||||
"Performance": "Prestaties",
|
||||
"Balanced": "Evenwichtig",
|
||||
"Quality": "Kwaliteit",
|
||||
"Ultra Performance": "Ultra-prestaties",
|
||||
"Native": "Inheems",
|
||||
"Full": "Vol",
|
||||
"Reduced": "Verlaagd",
|
||||
"Efficient": "Efficiënt",
|
||||
"Precise": "Nauwkeurig",
|
||||
"Auto (VSync)": "Automatisch (VSync)",
|
||||
"Mailbox": "Postbus",
|
||||
"Immediate": "Onmiddellijk",
|
||||
"Vulkan": "Vulkaan",
|
||||
"FPS and Graph": "FPS en grafiek",
|
||||
"No cost": "Geen kosten",
|
||||
"Low cost": "Lage kosten",
|
||||
"Medium cost": "Middelmatige kosten",
|
||||
"High cost": "Hoge kosten",
|
||||
"Saves time": "Bespaart tijd",
|
||||
"Graphics": "Grafisch",
|
||||
"Done": "Klaar",
|
||||
"This feature is not yet available in Horizon.": "Deze functie is nog niet beschikbaar in Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Uw beeldscherm of systeem biedt geen HDR-uitvoer.",
|
||||
"Turn HDR on to use it.": "Schakel HDR in om het te gebruiken.",
|
||||
"Your GPU or graphics API doesn't support it.": "Uw GPU of grafische API ondersteunt dit niet.",
|
||||
"%s chunks": "%s brokken",
|
||||
"%s per frame": "%s per kader",
|
||||
"Auto (%s)": "Automatisch (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Volledig gedetailleerd terreinbereik. LOD Distance kan lokale werelden verder uitbreiden.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Grof afgelegen terrein in lokale werelden. Geïntegreerde GPU's stoppen bij 128 chunks.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Minder trekt veel terrein grover: sneller op deze geïntegreerde GPU.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Verminderd trekt veel terrein grover, voor GPU's vertraagt het.",
|
||||
"Field of view. Wider shows more terrain at once.": "Gezichtsveld. Breder toont meer terrein tegelijk.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Maximaal frames per seconde; een dop bespaart stroom en warmte.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Wacht op vernieuwing van het scherm: geen tearing, een beetje latentie.",
|
||||
"Size of menus and the HUD.": "Grootte van menu's en de HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Stelt de onderstaande opties samen. Door er één te veranderen, wordt het aangepast.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, vuur en zonlicht schijnen helderder dan wit. Heeft een HDR-scherm nodig.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Het donkerste zwart van je scherm. 0 voor OLED; LCD's gloeien een beetje.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Hoe heldere highlights worden. Auto: de piek die uw systeem rapporteert.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Hoe helder wit papier en menu's er uitzien. Automatisch: uw bureaublad is wit.",
|
||||
"How bright the HUD and menus are, against paper white.": "Hoe helder de HUD en menu's zijn, tegen papierwit.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Hoe ver mobs, gewassen en machines blijven werken. CPU-tijd.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Hoe ver weg mobs en items worden getrokken, als percentage van normaal.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Slaat terrein over dat verborgen is achter gesloten secties. Uit kost veel.",
|
||||
"Hides entities behind opaque blocks.": "Verbergt entiteiten achter ondoorzichtige blokken.",
|
||||
"No entity occlusion culling.": "Geen occlusie van entiteiten.",
|
||||
"CPU occlusion test runs this frame.": "De CPU-occlusietest voert dit frame uit.",
|
||||
"Occlusion checks run on a worker thread.": "Occlusiecontroles worden uitgevoerd op een werkthread.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Meshed secties geüpload per frame. Lager is soepeler tijdens het laden.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Zachte schaduwen in hoeken. Kost alleen maar bouwtijd.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Kleinere kopieën van bloktexturen voor afgelegen terrein: minder glans.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Mengt gras- en aquarelkleuren over biomen bij het bouwen van brokken.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Wolkenlaag: uit, plat (snel) of 3D (fancy).",
|
||||
"How far clouds reach, in chunks.": "Hoe ver wolken reiken, in stukjes.",
|
||||
"Draws the sky, sun, moon and stars.": "Tekent de lucht, de zon, de maan en de sterren.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Afstand mist. Off toont de rand van de beladen wereld.",
|
||||
"Draws rain and snow.": "Trekt regen en sneeuw.",
|
||||
"How far rain and snow fall around you.": "Hoe ver regen en sneeuw om je heen valt.",
|
||||
"How many particles effects spawn.": "Hoeveel deeltjeseffecten spawnen.",
|
||||
"Round shadows under mobs and items.": "Ronde schaduwen onder mobs en items.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Bladeren met doorzichtige gaten. Off trekt ze solide.",
|
||||
"Sorts water and glass more exactly behind each other.": "Sorteert water en glas preciezer achter elkaar.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Welk deel opnieuw wordt opgebouwd, wacht op het frame: geen, dichtbij of van jou.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animeert water, lava, vuur en andere bewegende texturen.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Wereldpixels per schermpixel. Onder de 100% is sneller op de GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Maakt gekartelde randen glad. FXAA: zachter. TAA: de soepelste, bewegende dingen kunnen slepen.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Maakt de wereld kleiner en herbouwt deze met TAA. Sneller, zachter.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR-uitvoer op een HDR-scherm: lava, vuur en zonlicht schijnen helderder.",
|
||||
"How frames reach the screen while VSync is off.": "Hoe frames het scherm bereiken terwijl VSync is uitgeschakeld.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Efficiënt slapen tot het volgende frame; Nauwkeurige spins, zoals vanille.",
|
||||
"Fills the screen (borderless).": "Vult het scherm (randloos).",
|
||||
"Brightens dark places.": "Verheldert donkere plekken.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Verlaagt de framesnelheid wanneer deze is geminimaliseerd, of ook inactief.",
|
||||
"Sways the view while walking.": "Verandert het zicht tijdens het lopen.",
|
||||
"Fades newly loaded chunks in.": "Laat nieuw geladen chunks infaden.",
|
||||
"Darkens the screen's edges.": "Maakt de randen van het scherm donkerder.",
|
||||
"Shows when the world saves.": "Laat zien wanneer de wereld redt.",
|
||||
"Shows attack cooldown.": "Toont de cooldown van de aanval.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS-limiet (30 in menu's of ongericht), de helft van de werknemers: minder kracht.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Draden die brokken genereren, verlichten en meshen. Volgende start.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan en vervolgens DirectX 12 (Windows) of OpenGL (Linux) waar het mislukt. Volgende start.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Water en glas in één buffer: minder draw calls, wat meer VRAM. Volgende start.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Mesh-gegevens die per frame naar de GPU worden verzonden. Lager is soepeler tijdens het laden.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Framesnelheid en frametijden gedurende het spel, met een grafiek.",
|
||||
"horizon.multiplayer.finding_friend": "Vind de wereld van je vriend...",
|
||||
"horizon.multiplayer.not_signed_in": "Deze server bevindt zich in de onlinemodus. Meld u aan met uw Microsoft-account in het Horizon-opstartprogramma om hieraan deel te nemen.",
|
||||
"horizon.multiplayer.invalid_address": "Ongeldig serveradres: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Rechtstreeks verbonden met je vriend...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Verbonden met je vriend via het relais...",
|
||||
"General": "Algemeen",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Undo": "Ongedaan maken",
|
||||
"Search options": "Zoekopties",
|
||||
"Shortcut": "Snelkoppeling",
|
||||
"Distance": "Afstand",
|
||||
"Display": "Weergave",
|
||||
"Sky and weather": "Hemel en weer",
|
||||
"Lighting and entities": "Verlichting en entiteiten",
|
||||
"Textures": "Texturen",
|
||||
"Image": "Afbeelding",
|
||||
"Chunk loading": "Stukken laden",
|
||||
"Culling": "Ruimen",
|
||||
"Power": "Macht",
|
||||
"Frames": "Lijsten",
|
||||
"Output": "Uitvoer",
|
||||
"Brightness": "Helderheid",
|
||||
"How far you see, the window and the HUD.": "Hoe ver je kunt kijken, het raam en de HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "De lucht en het weer, de verlichting, de texturen en het beeld.",
|
||||
"Chunk loading, culling and power use.": "Het laden van brokken, het ruimen en het stroomverbruik.",
|
||||
"How frames reach the screen, and the performance overlay.": "Hoe frames het scherm bereiken en de prestatie-overlay.",
|
||||
"Brighter-than-white output on an HDR display.": "Helderder dan witte uitvoer op een HDR-scherm.",
|
||||
"Results for \"%s\"": "Resultaten voor \"%s\"",
|
||||
"No settings match.": "Er komen geen instellingen overeen.",
|
||||
"horizon.title.not_affiliated": "Niet aangesloten bij Mojang of Microsoft",
|
||||
"horizon.extras.header.lighting": "Verlichting",
|
||||
"horizon.extras.header.third_person": "Derde persoon",
|
||||
"horizon.extras.header.particles": "Deeltjes",
|
||||
"horizon.extras.header.inventory": "Inventaris",
|
||||
"horizon.extras.header.chat": "Chatten",
|
||||
"horizon.extras.header.sound": "Geluid",
|
||||
"horizon.extras.header.menus": "Menu's",
|
||||
"horizon.extras.fullbright": "Volledig helder",
|
||||
"horizon.extras.dynamic_lights": "Dynamische lichten",
|
||||
"horizon.extras.own_name_tag": "Eigen naamplaatje",
|
||||
"horizon.extras.hide_block_particles": "Verberg blokbrekende deeltjes",
|
||||
"horizon.extras.auto_reconnect": "Automatisch opnieuw verbinden",
|
||||
"horizon.extras.ui_sound_reserve": "Houd kanalen voor UI-geluiden",
|
||||
"horizon.extras.music_page": "Muziek pagina",
|
||||
"horizon.extras.mouse_tweaks": "Muisaanpassingen",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Scrollwiel verplaatst items",
|
||||
"horizon.extras.sort": "Sorteerknoppen",
|
||||
"horizon.extras.sort_key": "Sorteer sleutel",
|
||||
"horizon.extras.no_ai": "Geen AI",
|
||||
"horizon.extras.agent_control": "Agentcontrole",
|
||||
"horizon.extras.discord_presence": "Onenigheid Rijke aanwezigheid",
|
||||
"horizon.extras.splitscreen": "Gesplitst scherm",
|
||||
"horizon.extras.chat_animation": "Vlotte babbel",
|
||||
"horizon.extras.chat_indicator": "Berichtindicator",
|
||||
"horizon.extras.wheel_cycles_settings": "Scrollwiel wijzigt instellingen",
|
||||
"horizon.extras.pack_browser": "Bronpakketbrowser",
|
||||
"horizon.extras.fullbright.tooltip": "Toon blokken en entiteiten bij vol licht, zelfs in donkere gebieden.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Fakkels en andere lichtbronnen die je vasthoudt, laat vallen of in brand steken, verlichten de blokken eromheen.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Toont je naam boven je hoofd in de derde persoon, zoals andere spelers het zien. Verborgen wanneer de server de namen van uw team verbergt of u onzichtbaar bent.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Verbergt de puindeeltjes die uit een gebroken blok barsten en afbrokkelen van het blok dat je ontgint. Andere deeltjes blijven.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Sluit zich opnieuw aan bij een server na een verbroken verbinding of een herstart, nooit na een kick of ban.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Geluiden in de wereld laten 16 van de 247 geluidskanalen vrij, dus een overstroming ervan (een grote explosie) kan menuklikken niet tot zwijgen brengen.",
|
||||
"horizon.extras.music_page.tooltip": "Muziek en geluiden krijgt een Muziek...-pagina: speel nu een ingebouwd nummer, of schakel er een uit zodat het spel het niet meer kiest. Er verandert niets totdat je een nummer uitschakelt.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Met rechts slepen wordt er één per slot geplaatst, met links slepen wordt het vastgehouden item verzameld, Shift + links slepen verplaatst elk slot.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Omlaag stuurt een item van de gleuf naar de andere kant, omhoog brengt er één terug.",
|
||||
"horizon.extras.sort.tooltip": "Sorteert de inventaris, kisten, vaten, enderkisten en shulkerdozen in creatieve tabvolgorde, waarbij stapels worden samengevoegd.",
|
||||
"horizon.extras.sort_key.tooltip": "Sorteert het gedeelte onder de cursor. Klik en druk vervolgens op een toets; Ontsnappen maakt het los.",
|
||||
"horizon.extras.no_ai.tooltip": "Verbergt en schakelt elke AI-functie uit: agentcontrole, horizon-ctl en de MCP-servers. De schakelaar van de draagraket ook.",
|
||||
"horizon.extras.agent_control.tooltip": "Je eigen AI-agent kan dit spel alleen op deze computer spelen via horizon-ctl of een MCP-client.",
|
||||
"horizon.extras.discord_presence.tooltip": "Toont 'Speelt Horizon' op je Discord-profiel terwijl Discord actief is: menu's, singleplayer of multiplayer, de dimensie en de gespeelde tijd. Nooit een wereldnaam of een serveradres.",
|
||||
"Calibrate...": "Kalibreer...",
|
||||
"Calibrate Anyway...": "Hoe dan ook kalibreren...",
|
||||
"Use Automatic Setup": "Gebruik automatische configuratie",
|
||||
"Shortcut: %s": "Sneltoets: %s",
|
||||
"Next": "Volgende",
|
||||
"HDR Calibration %s/%s: %s": "HDR-kalibratie %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Stelt de onderstaande drie in op volledig scherm, één testbeeld tegelijk.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Een sleutel die dit scherm vanuit het spel opent. In eerste instantie niet gebonden.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Uw display heeft niet aangegeven hoe helder het wordt: kalibreer om het in te stellen.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Al ingesteld voor uw beeldscherm. Kalibreer alleen als HDR er verkeerd uitziet.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Zet piekhelderheid en papierwit terug in de instellingen van uw beeldscherm.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Instellen voor uw beeldscherm. Laat Auto staan, tenzij het er verkeerd uitziet.",
|
||||
"Lower it until the logo just disappears into the black.": "Laat hem zakken totdat het logo net in het zwart verdwijnt.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Breng hem omhoog totdat het logo net in het heldere vierkant verdwijnt.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Maak de kaart wit, zodat je er lang naar kunt kijken, in het licht waarin je speelt.",
|
||||
"horizon.splitscreen.profiles.forget": "Vergeet",
|
||||
"horizon.splitscreen.profiles.create": "Creëer",
|
||||
"horizon.splitscreen.profiles.you": " - jij",
|
||||
"horizon.splitscreen.profiles.forget_named": "Vergeet %s",
|
||||
"horizon.splitscreen.profiles.previous": "Vorige",
|
||||
"horizon.splitscreen.profiles.next": "Volgende",
|
||||
"horizon.splitscreen.profiles.new_button": "Nieuw profiel...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Profiel vergeten?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Het verlaat de lijst van deze instantie.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "De voortgang en instellingen van de wereld blijven behouden.",
|
||||
"horizon.splitscreen.profiles.new_title": "Nieuw profiel",
|
||||
"horizon.splitscreen.profiles.name_hint": "Naam (3 tot 16 letters, cijfers, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Profiel wisselen",
|
||||
"horizon.splitscreen.profiles.offline_note": "Offline profielen behoren tot deze instantie",
|
||||
"horizon.splitscreen.profiles.move": "Verplaats waar je speelt",
|
||||
"horizon.splitscreen.profiles.move_place": "Verplaatsen waar je speelt: %s",
|
||||
"horizon.friends.title": "Speel met vrienden",
|
||||
"horizon.friends.hint": "Vrienden plakken de code na de knop Vrienden in het titelscherm, in Directe verbinding of op de Play-pagina van het opstartprogramma.",
|
||||
"horizon.friends.copied": "Gekopieerd",
|
||||
"horizon.friends.copy_code": "Kopieer code",
|
||||
"horizon.friends.stop_sharing": "Stop met delen",
|
||||
"horizon.friends.none_yet": "Nog geen vrienden hier.",
|
||||
"horizon.friends.here_now": "Hier nu: %s",
|
||||
"horizon.friends.share_intro": "Deel deze wereld achter een uitnodigingscode: deze wordt geopend voor LAN en vrienden op internet doen mee met de code. Er is geen server van ons bij betrokken.",
|
||||
"horizon.friends.start_sharing": "Begin met delen",
|
||||
"horizon.friends.troubleshooting": "Problemen oplossen...",
|
||||
"horizon.friends.stopped": "Het delen van de uitnodigingscode is gestopt.",
|
||||
"horizon.friends.short_code_changed": "Iemand probeerde steeds een verkeerde shortcode, dus het veranderde.",
|
||||
"horizon.friends.wrong_code": "Iemand heeft een verkeerde code geprobeerd.",
|
||||
"horizon.friends.code_ready": "Je uitnodigingscode is klaar: Gamemenu, Spelen met vrienden. Kopieer het en stuur het naar je vrienden.",
|
||||
"horizon.friends.share_failed": "Kon de wereld niet delen: %s",
|
||||
"horizon.friends.finding_route": "Ontdek hoe vrienden u kunnen bereiken...",
|
||||
"horizon.osk.shift": "Verschuiving",
|
||||
"horizon.osk.space": "Ruimte",
|
||||
"horizon.osk.delete": "Verwijderen",
|
||||
"horizon.osk.type": "Typ",
|
||||
"horizon.osk.symbols": "Symbolen",
|
||||
"horizon.osk.close": "Sluiten",
|
||||
"horizon.extras.chat_animation.tooltip": "Een nieuwe chatlijn schuift van links naar binnen terwijl deze invaagt, in plaats van geheel te lijken.",
|
||||
"horizon.extras.chat_indicator.tooltip": "De gekleurde balk links van een chatlijn.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Het scrollwiel boven een knop in de instellingen verandert de waarde, zoals in vanille. Uit: het wiel scrollt alleen door de lijst.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs krijgt een knop Toevoegen die Modrinth doorzoekt en een pakket in uw packs-map installeert. Uit: het spel maakt nooit contact met Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In een wereld kan een andere controller meedoen als nieuwe speler: druk op A wanneer dit wordt gevraagd, kies vervolgens een profiel en waar je wilt spelen.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "een monitor",
|
||||
"horizon.splitscreen.setup.window": "Raam %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nieuw venster",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Vul %s",
|
||||
"horizon.splitscreen.setup.done": "Klaar",
|
||||
"horizon.splitscreen.setup.on": "Aan",
|
||||
"horizon.splitscreen.setup.off": "Uit",
|
||||
"horizon.splitscreen.setup.player": "Speler %s",
|
||||
"horizon.splitscreen.setup.revert": "Terugkeren",
|
||||
"horizon.splitscreen.setup.apply": "Toepassen",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Niemand speelt in Venster 1: het verbergt zich",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Niemand speelt in Venster %s: het wordt gesloten bij Toepassen",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Niemand speelt in Venster %s: het gaat niet open",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Venster %s vult de monitor van Venster 1: de spelers spelen in Venster 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "De monitor van Venster %s is niet aangesloten: de spelers spelen in Venster 1",
|
||||
"horizon.splitscreen.setup.status.off": "Splitscreen staat uit.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Schakel hem in en druk vervolgens op een knop op een andere controller in een wereld.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Druk op A om lid te worden en een profiel te kiezen.",
|
||||
"horizon.splitscreen.setup.status.idle": "Splitscreen inactief",
|
||||
"horizon.splitscreen.setup.status.alone": "Geen andere lokale spelers.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Solobediening en controllerfeedback zijn ongewijzigd.",
|
||||
"horizon.splitscreen.setup.window_empty": "Venster %s: leeg",
|
||||
"horizon.splitscreen.setup.note.select": "Klik op een speler of sleep deze naar een venster of monitor.",
|
||||
"horizon.splitscreen.setup.note.apply": "Toepassen om op deze manier te spelen, Terug naar ongedaan maken.",
|
||||
"horizon.splitscreen.setup.note.place": "Waar moet %s spelen?",
|
||||
"horizon.splitscreen.setup.this_player": "deze speler",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Splitscreen wordt uitgeschakeld zodra de anderen zijn vertrokken (hun pauzemenu).",
|
||||
"horizon.splitscreen.setup.window_place": "Venster %s (%s van %s)",
|
||||
"horizon.splitscreen.setup.names": "Namen",
|
||||
"horizon.splitscreen.setup.identify": "Identificeer",
|
||||
"horizon.splitscreen.setup.layout.two": "2 delen: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 delen: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "Automatisch",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Zij aan zij",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Boven- en onderkant",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Eén breed bovenaan",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Links een lange",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Zij aan zij",
|
||||
"horizon.splitscreen.setup.help.player": "Klik of sleep %s om ze te verplaatsen.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (naast elkaar als het raam breed is). Druk op om te wijzigen.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Druk op om te wijzigen.",
|
||||
"horizon.splitscreen.setup.help.switch": "Splitscreen voor deze computer, aan of uit.",
|
||||
"horizon.splitscreen.setup.help.identify": "In het zicht van elke speler kun je zien in welk venster hij zich bevindt.",
|
||||
"horizon.splitscreen.setup.help.names": "De naam van elke speler in de hoek van zijn weergave.",
|
||||
"horizon.splitscreen.setup.help.revert": "Maak de nog niet toegepaste wijzigingen ongedaan.",
|
||||
"horizon.splitscreen.setup.help.apply": "Speel zoals de kaart laat zien.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Er is nog niets veranderd.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Geef Pad op",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Regelaar aangesloten",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Regelaar losgekoppeld",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Sluit hem opnieuw aan of druk op een knop op een andere",
|
||||
"horizon.splitscreen.prompt.join.one": "%s wil lid worden",
|
||||
"horizon.splitscreen.prompt.join.many": "%s-controllers willen meedoen",
|
||||
"horizon.splitscreen.prompt.join.yes": "Doe mee",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Speel als P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Nee",
|
||||
"horizon.splitscreen.prompt.lost": "%s: regelaar losgekoppeld",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Gebruik een andere",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Vertrekken",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Uw controller stopt met rijden en wordt aangeboden om mee te doen (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s tot %s: A laat het hier vallen, B legt het terug.",
|
||||
"horizon.splitscreen.setup.note.held": "%s opgepakt: links/rechts kiest waar, A laat vallen, B legt het terug.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Verbergt modellen achter ondoorzichtige blokken. Asynchrone controles buiten de thread; entiteiten aanvinken.",
|
||||
"Block Entities": "Blokkeer entiteiten",
|
||||
"Shulker Boxes": "Shulker-dozen",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Hergebruikt bezette dozen; dekselbeweging blijft geanimeerd.",
|
||||
"Signs": "Tekenen",
|
||||
"Reuses sign text until text, light, or font changes.": "Hergebruikt ondertekende tekst totdat de tekst, het licht of het lettertype verandert.",
|
||||
"Decorated Pots": "Versierde potten",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Hergebruikt nog steeds potten; breken wiebelen en scherfveranderingen opnieuw tekenen.",
|
||||
"Bells": "Klokken",
|
||||
"Reuses still bells; ringing stays animated.": "Hergebruikt nog steeds bellen; beltoon blijft geanimeerd.",
|
||||
"Chests": "Kisten",
|
||||
"Reuses closed chests; moving lids stay animated.": "Hergebruik gesloten kisten; bewegende deksels blijven geanimeerd.",
|
||||
"Reuses poles; flags and pattern layers still update.": "Hergebruik stokken; vlaggen en patroonlagen worden nog steeds bijgewerkt.",
|
||||
"Copper Golem Statues": "Koperen Golem-beelden",
|
||||
"Reuses statue geometry until its block state changes.": "Hergebruikt de geometrie van het standbeeld totdat de blokstatus verandert.",
|
||||
"Leaf Culling": "Blad ruimen",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Elk bladvlak. De mooie bladeren van vanille.",
|
||||
"Distant Plant Dither": "Verre plantendithering",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Zorgt ervoor dat mooie planten in pakketten met hoge resolutie niet ver weg vervagen.",
|
||||
"Screenshot Quality": "Screenshotkwaliteit",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2-screenshots met deze resolutie die vele malen hoger is dan die van het spel. Hoger duurt langer.",
|
||||
"Frame Generation": "Framegeneratie",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Toont een verzonnen frame tussen elke twee echte: vloeiender, iets meer vertraging.",
|
||||
"Shadow Cache": "Schaduwcache",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Hergebruikt de roedelschaduwkaart wanneer zon, zicht en wereld overeenkomen. Standaard uitgeschakeld.",
|
||||
"Find BlueMap": "Zoek BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Vindt op servers hun BlueMap-webkaart voor ver terrein. Volgende meedoen.",
|
||||
"BlueMap Radius": "BlueMap-radius",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Hoe ver om je heen BlueMap-tegels worden gedownload. Volgende meedoen.",
|
||||
"Synchronous": "Synchronisch",
|
||||
"Asynchronous": "Asynchroon",
|
||||
"Solid": "Solide",
|
||||
"Solid Aggressive": "Solide agressief",
|
||||
"Hollow": "Hol",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Bladeren die zich volledig in een bladerdak bevinden, trekken stevig aan: minder doorzichtige lagen.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Bladeren met één open zijde zijn ook stevig: snellere, grovere randen.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Laat gezichten tussen bladeren vallen: sneller, maar gaten kijken er doorheen.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Vorig slot",
|
||||
"Hotbar: Next Slot": "Hotbar: volgende slot",
|
||||
"horizon.agent.connected.one": "Verbonden: 1 agentverbinding",
|
||||
"horizon.agent.connected.many": "Verbonden: %s agentverbindingen",
|
||||
"horizon.agent.connected.one_ago": "Verbonden: 1 agentverbinding, laatste opdracht %s s geleden",
|
||||
"horizon.agent.connected.many_ago": "Verbonden: %s agentverbindingen, laatste opdracht %s s geleden",
|
||||
"horizon.agent.waiting": "Wachten op een agent.",
|
||||
"horizon.agent.off": "Uit: agenten kunnen dit spel niet bereiken.",
|
||||
"horizon.agent.session_opened": "Voor deze sessie geopend door het programma waarmee het spel is gestart.",
|
||||
"horizon.agent.socket": "Socket: %s (kopieer de details voor het volledige pad)",
|
||||
"horizon.agent.local_only": "Alleen lokaal: agenten op deze computer, nooit op het netwerk.",
|
||||
"Auto (%s nits)": "Automatisch (%s nits)",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Verbergt modellen achter ondoorzichtige blokken. Entiteiten bijgewerkt. Weg met aangepaste pakketten.",
|
||||
"horizon.extras.repro_keys": "Snelkoppelingen voor opnemen",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R start of stopt een repro-opname en F3+E schrijft zijn bundel. Standaard uitgeschakeld: een opname kopieert de wereld.",
|
||||
"horizon.agent.record_session": "Opnamesessie starten",
|
||||
"horizon.agent.record_stop": "Stop de opnamesessie",
|
||||
"horizon.agent.export_repro": "Reprobundel exporteren",
|
||||
"horizon.repro.started": "Opname gestart; F3+R stopt het en schrijft de bundel.",
|
||||
"horizon.repro.stopped": "Opname gestopt: %s",
|
||||
"horizon.repro.bundle": "Reprobundel: %s",
|
||||
"horizon.repro.error": "Reproductie: %s",
|
||||
"horizon.repro.no_recording": "Er is nog geen opname om te exporteren.",
|
||||
"horizon.repro.replay_started": "Herhaling gestart: %s",
|
||||
"horizon.pack.addInfo": "Voeg een resourcepakket van Modrinth toe",
|
||||
"horizon.pack.addWorking": "Modrinth gevraagd...",
|
||||
"horizon.pack.addNone": "Geen bronpakketten gevonden",
|
||||
"horizon.pack.addTitle": "Voeg een resourcepakket toe",
|
||||
"horizon.pack.install": "Installeren",
|
||||
"horizon.pack.installing": "Installeren",
|
||||
"horizon.pack.installed": "Geïnstalleerd",
|
||||
"horizon.pack.search": "Zoeken",
|
||||
"horizon.pack.searchHint": "Zoek Modrinth...",
|
||||
"horizon.pack.filter.realistic": "Realistisch",
|
||||
"horizon.pack.filter.simplistic": "Simplistisch",
|
||||
"horizon.pack.filter.vanillaLike": "Vanille-achtig",
|
||||
"horizon.pack.addFailed": "Het pakket kon niet worden toegevoegd: %s",
|
||||
"horizon.extras.header.cosmetics": "Cosmetica",
|
||||
"horizon.extras.cosmetics_mine": "Mijn cosmetica",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Verborgen: elke Horizon-speler ziet je zonder je cosmetica, inclusief jij. Dezelfde schakelaar als de kledingkast van de draagraket.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetica van andere spelers",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat andere Horizon-spelers dragen: alles, alleen hun naamplaatjestijlen, of niets. Verborgen vraagt het spel nooit wat ze dragen.",
|
||||
"horizon.extras.cosmetics_auras": "De aura's en sporen van anderen",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aura's en sporen die andere spelers dragen. Standaard uitgeschakeld.",
|
||||
"horizon.extras.cosmetics.shown": "Getoond",
|
||||
"horizon.extras.cosmetics.hidden": "Verborgen",
|
||||
"horizon.extras.cosmetics.name_tags": "Alleen naamplaatjes"
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
{
|
||||
"horizon.options.controller": "Kontroller",
|
||||
"horizon.options.vibration": "Vibrasjon",
|
||||
"horizon.options.adaptive_triggers": "Adaptive triggere",
|
||||
"horizon.options.look_sensitivity": "Blikkfølsomhet",
|
||||
"horizon.options.invert_x": "Inverter blikkets X-akse",
|
||||
"horizon.options.invert_y": "Inverter blikkets Y-akse",
|
||||
"horizon.options.sneak_button": "Snikknapp",
|
||||
"horizon.options.move_deadzone": "Dødsone for bevegelse",
|
||||
"horizon.options.look_deadzone": "Dødsone for blikk",
|
||||
"horizon.options.button_glyphs": "Knappetegn",
|
||||
"horizon.options.menu_navigation": "Menynavigering",
|
||||
"horizon.options.on_screen_keyboard": "Skjermtastatur",
|
||||
"horizon.options.chat_delay": "Chat-forsinkelse",
|
||||
"horizon.options.lightbar.health": "Helse",
|
||||
"horizon.options.lightbar.dimension": "Dimensjon",
|
||||
"horizon.options.menu_focus": "Fokus",
|
||||
"horizon.options.menu_pointer": "Peker",
|
||||
"horizon.options.unavailable": "Denne funksjonen er ennå ikke tilgjengelig i Horizon.",
|
||||
"horizon.options.search": "Søkeinnstillinger...",
|
||||
"horizon.pages.shader_packs": "Shader-pakker",
|
||||
"horizon.pages.shader_options": "Shader-alternativer",
|
||||
"horizon.pages.controller": "Kontrollerinnstillinger",
|
||||
"horizon.pages.controller_binds": "Kontrolleren binder",
|
||||
"horizon.pages.agent": "AI-agent",
|
||||
"horizon.split.leave": "Gå",
|
||||
"horizon.pages.split_settings": "Innstillinger for delt skjerm",
|
||||
"horizon.pages.split_profiles": "Hvem spiller?",
|
||||
"horizon.pages.split_setup": "Oppsett av delt skjerm",
|
||||
"horizon.pages.music": "Musikk",
|
||||
"horizon.pages.music.menu": "Meny",
|
||||
"horizon.pages.music.overworld": "Oververden",
|
||||
"horizon.pages.music.creative": "Kreativt",
|
||||
"horizon.pages.music.underwater": "Under vann",
|
||||
"horizon.pages.music.end": "Slutten",
|
||||
"horizon.pages.music.boss": "Sjef",
|
||||
"horizon.pages.music.credits": "Studiepoeng",
|
||||
"horizon.pages.music.overworld_biomes": "Oververdens biomer",
|
||||
"horizon.pages.music.nether_biomes": "Under biomer",
|
||||
"horizon.pages.music.end_biomes": "Avslutt biomer",
|
||||
"horizon.pages.music.other": "Annen musikk",
|
||||
"horizon.pages.music.empty": "Ingen musikkspor: spillets lyder lastes ikke inn.",
|
||||
"horizon.pages.music.tooltip": "De innebygde sporene, etter situasjon: spill et nå, eller slå av hvilket som helst slik at spillet slutter å velge det.",
|
||||
"horizon.pages.settings.tooltip": "Horizons egne innstillinger: grafikk, kontrollere, ekstrautstyr og mods.",
|
||||
"horizon.pages.graphics.tooltip": "Grafikk, ganske enkelt: hver innstilling sier hva den koster.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Hver grafikkinnstilling, Horizon er inkludert, med det hver koster.",
|
||||
"horizon.pages.controller.tooltip": "Kontrollere: deres knapper, pinner, vibrasjon og menynavigering.",
|
||||
"horizon.pages.extras.tooltip": "Valgfrie tillegg, alle av som standard: spillet forblir vanilje til du slår på en.",
|
||||
"horizon.pages.agent.tooltip": "La din egen AI-agent spille: horizon-ctl og MCP-klienter på denne datamaskinen.",
|
||||
"horizon.pages.split_profiles.tooltip": "Hvem spiller: en profil lagret i denne spillmappen.",
|
||||
"horizon.pages.split_setup.tooltip": "Vinduene og skjermene, og hvem som spiller hvor.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader-pakker fra shaderpacks-mappen: Horizons egne og de i shaders/-formatet. En løper om gangen.",
|
||||
"horizon.pages.mods.tooltip": "Mods laget for Horizon, fra horizon-mods-mappen. Hver kjører i en sandkasse og forblir av til du slår den på.",
|
||||
"horizon.pages.open.graphics": "Grafikk...",
|
||||
"horizon.pages.open.graphics_advanced": "Avansert grafikk...",
|
||||
"horizon.pages.open.shader_packs": "Shader-pakker...",
|
||||
"horizon.pages.open.controller": "Kontrollerinnstillinger...",
|
||||
"horizon.pages.open.split_setup": "Delt skjerm...",
|
||||
"horizon.pages.open.extras": "Ekstra...",
|
||||
"horizon.pages.open.agent": "AI-agent...",
|
||||
"horizon.pages.open.controller_binds": "Kontrolleren binder...",
|
||||
"horizon.pages.open.music": "Musikk...",
|
||||
"horizon.mods.empty": "Ingen mods: legg .hmod-filer i horizon-mods-mappen",
|
||||
"horizon.mods.stopped": " (Stoppet)",
|
||||
"horizon.mods.not_built": " (Ikke bygget)",
|
||||
"horizon.mods.dev": " (Utvikler)",
|
||||
"horizon.mods.settings": "Innstillinger",
|
||||
"horizon.mods.in_world": " (la verden forandre seg)",
|
||||
"horizon.mods.open_folder": "Åpne Mod-mappen",
|
||||
"horizon.shader_packs.empty": "Ingen shader-pakker: legg dem i shaderpacks-mappen",
|
||||
"horizon.shader_options.open": "Skyggeleggingsalternativer...",
|
||||
"horizon.shader_options.profile": "Profil",
|
||||
"horizon.shader_options.loading": "Leser alternativene til shader-pakken...",
|
||||
"horizon.agent.copy_details": "Kopier tilkoblingsdetaljer",
|
||||
"horizon.multiplayer.join": "Bli med",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Avbryt tilkobling på nytt",
|
||||
"horizon.multiplayer.join_friend": "Bli med en venn",
|
||||
"horizon.multiplayer.invite_code": "Invitasjonskode",
|
||||
"horizon.multiplayer.relay": "Relé (valgfritt)",
|
||||
"Preset": "Forhåndsinnstilt",
|
||||
"LOD Distance": "LOD Avstand",
|
||||
"Far Detail": "Langt detalj",
|
||||
"Chunk Updates": "Deloppdateringer",
|
||||
"Upload Budget": "Last opp budsjett",
|
||||
"Sky": "Himmel",
|
||||
"Fog": "Tåke",
|
||||
"Weather": "Vær",
|
||||
"Animated Textures": "Animerte teksturer",
|
||||
"Present Mode": "Presentasjonsmodus",
|
||||
"Worker Threads": "Arbeidstråder",
|
||||
"Resolution Scale": "Oppløsningsskala",
|
||||
"Upscaling": "Oppskalering",
|
||||
"Paper White": "Papir Hvit",
|
||||
"Peak Brightness": "Topp lysstyrke",
|
||||
"Black Level": "Svart nivå",
|
||||
"UI Brightness": "UI lysstyrke",
|
||||
"Battery Mode": "Batterimodus",
|
||||
"Graphics API": "Grafikk API",
|
||||
"Translucent Batching": "Gjennomsiktig batching",
|
||||
"Performance Overlay": "Ytelsesoverlegg",
|
||||
"Full Detail Distance": "Full detalj avstand",
|
||||
"On": "På",
|
||||
"Off": "Av",
|
||||
"Unlimited": "Ubegrenset",
|
||||
"Custom": "Egendefinert",
|
||||
"Performance": "Ytelse",
|
||||
"Balanced": "Balansert",
|
||||
"Quality": "Kvalitet",
|
||||
"Ultra Performance": "Ultra ytelse",
|
||||
"Native": "Innfødt",
|
||||
"Reduced": "Redusert",
|
||||
"Efficient": "Effektiv",
|
||||
"Precise": "Nøyaktig",
|
||||
"Mailbox": "Postkasse",
|
||||
"Immediate": "Umiddelbar",
|
||||
"FPS and Graph": "FPS og graf",
|
||||
"No cost": "Ingen kostnad",
|
||||
"Low cost": "Lav kostnad",
|
||||
"Medium cost": "Middels kostnad",
|
||||
"High cost": "Høy kostnad",
|
||||
"Saves time": "Sparer tid",
|
||||
"Graphics": "Grafikk",
|
||||
"Done": "Ferdig",
|
||||
"This feature is not yet available in Horizon.": "Denne funksjonen er ennå ikke tilgjengelig i Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Skjermen eller systemet ditt tilbyr ikke HDR-utgang.",
|
||||
"Turn HDR on to use it.": "Slå på HDR for å bruke den.",
|
||||
"Your GPU or graphics API doesn't support it.": "Din GPU eller grafikk-API støtter det ikke.",
|
||||
"%s chunks": "%s biter",
|
||||
"%s per frame": "%s per ramme",
|
||||
"%s MiB/frame": "%s MiB/ramme",
|
||||
"%s KiB/frame": "%s KiB/ramme",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Full detalj terrengområde. LOD Avstand kan utvide lokale verdener lenger.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Grovt fjernt terreng i lokale verdener. Integrerte GPU-er stopper ved 128 biter.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Redusert trekker langt grovere terreng: raskere på denne integrerte GPUen.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Redusert trekker langt grovere terreng, for GPU-er bremser den ned.",
|
||||
"Field of view. Wider shows more terrain at once.": "Synsfelt. Bredere viser mer terreng på en gang.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Caps bilder per sekund; en hette sparer strøm og varme.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Venter på skjermens oppdatering: ingen riving, litt forsinkelse.",
|
||||
"Size of menus and the HUD.": "Størrelse på menyer og HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Setter alternativene nedenfor sammen. Å endre en gjør den tilpasset.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, ild og sollys skinner sterkere enn hvitt. Trenger en HDR-skjerm.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "Skjermen er mørkest svart. 0 for OLED; LCD-skjermer lyser litt.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Hvor lyse høydepunkter blir. Auto: toppen systemet ditt rapporterer.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Hvor lyst hvitt papir og menyer ser ut. Auto: skrivebordet ditt er hvitt.",
|
||||
"How bright the HUD and menus are, against paper white.": "Hvor lyse HUD og menyene er, mot papirhvitt.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Hvor langt mobber, avlinger og maskiner fortsetter å jobbe. CPU-tid.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "Hvor langt unna mobber og gjenstander trekkes, som en andel av normalen.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Hopper over terreng skjult bak lukkede partier. Av koster mye.",
|
||||
"Hides entities behind opaque blocks.": "Skjuler enheter bak ugjennomsiktige blokker.",
|
||||
"No entity occlusion culling.": "Ingen enhet okklusjon utslakting.",
|
||||
"CPU occlusion test runs this frame.": "CPU-okklusjonstest kjører denne rammen.",
|
||||
"Occlusion checks run on a worker thread.": "Okklusjonssjekker kjøres på en arbeidertråd.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Meshed-seksjoner lastet opp per ramme. Nedre er jevnere under lasting.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Myke skygger i hjørnene. Koster kun stykke byggetid.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Mindre kopier av blokkteksturer for fjernt terreng: mindre skimmer.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Blander gress og vannfarger på tvers av biomer når du bygger biter.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Skylag: av, flatt (rask) eller 3D (fancy).",
|
||||
"How far clouds reach, in chunks.": "Hvor langt skyene når, i biter.",
|
||||
"Draws the sky, sun, moon and stars.": "Tegner himmel, sol, måne og stjerner.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Avstandståke. Off viser kanten av den lastede verdenen.",
|
||||
"Draws rain and snow.": "Tegner regn og snø.",
|
||||
"How far rain and snow fall around you.": "Hvor langt regn og snø faller rundt deg.",
|
||||
"How many particles effects spawn.": "Hvor mange partikler effekter gyter.",
|
||||
"Round shadows under mobs and items.": "Runde skygger under mobber og gjenstander.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Blader med gjennomsiktige mellomrom. Off trekker dem solid.",
|
||||
"Sorts water and glass more exactly behind each other.": "Sorterer vann og glass mer nøyaktig bak hverandre.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Hvilken chunk-rebuild venter på rammen: ingen, i nærheten eller din.",
|
||||
"Animates water, lava, fire and other moving textures.": "Animerer vann, lava, ild og andre bevegelige teksturer.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Verdenspiksler per skjermpiksel. Under 100% er raskere på GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Glatter ut taggete kanter. FXAA: mykere. TAA: jevneste, bevegelige ting kan spore.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Gjør verden mindre og gjenoppbygger den med TAA. Raskere, mykere.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "HDR-utgang på en HDR-skjerm: lava, ild og sollys skinner sterkere.",
|
||||
"How frames reach the screen while VSync is off.": "Hvordan rammer når skjermen mens VSync er av.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Effektiv sover til neste bilde; Nøyaktige spinn, som vanilje.",
|
||||
"Fills the screen (borderless).": "Fyller skjermen (utan kant).",
|
||||
"Brightens dark places.": "Lyser opp mørke steder.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Senker bildefrekvensen når den minimeres, eller inaktiv også.",
|
||||
"Sways the view while walking.": "Svinger utsikten mens du går.",
|
||||
"Fades newly loaded chunks in.": "Toker inn nylastede biter.",
|
||||
"Darkens the screen's edges.": "Gjør skjermens kanter mørkere.",
|
||||
"Shows when the world saves.": "Viser når verden redder.",
|
||||
"Shows attack cooldown.": "Viser nedkjøling av angrepet.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "60 FPS cap (30 i menyer eller ufokusert), halvparten av arbeiderne: mindre kraft.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Tråder som genererer, lyser og mesh biter. Neste start.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, deretter DirectX 12 (Windows) eller OpenGL (Linux) der det feiler. Neste start.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Vann og glass i én buffer: færre draw calls, litt mer VRAM. Neste start.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Mesh-data sendt til GPU-en per ramme. Nedre er jevnere under lasting.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Bildefrekvens og bildetider over spillet, med en graf.",
|
||||
"horizon.multiplayer.finding_friend": "Finne din venns verden...",
|
||||
"horizon.multiplayer.not_signed_in": "Denne serveren er i online-modus. Logg på med Microsoft-kontoen din i Horizon launcher for å bli med.",
|
||||
"horizon.multiplayer.invalid_address": "Ugyldig serveradresse: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Koblet direkte til vennen din...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Koblet til vennen din via reléet...",
|
||||
"General": "Generelt",
|
||||
"Advanced": "Avansert",
|
||||
"Undo": "Angre",
|
||||
"Search options": "Søkealternativer",
|
||||
"Shortcut": "Snarvei",
|
||||
"Distance": "Avstand",
|
||||
"Display": "Skjerm",
|
||||
"Interface": "Grensesnitt",
|
||||
"Sky and weather": "Himmel og vær",
|
||||
"Lighting and entities": "Belysning og enheter",
|
||||
"Textures": "Teksturer",
|
||||
"Image": "Bilde",
|
||||
"Chunk loading": "Chunk lasting",
|
||||
"Culling": "Utslakting",
|
||||
"Power": "Kraft",
|
||||
"Frames": "Rammer",
|
||||
"Overlay": "Overlegg",
|
||||
"Output": "Utgang",
|
||||
"Brightness": "Lysstyrke",
|
||||
"How far you see, the window and the HUD.": "Hvor langt du ser, vinduet og HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "Himmelen og været, lyssetting, teksturer og bildet.",
|
||||
"Chunk loading, culling and power use.": "Chunk lasting, culling og kraftbruk.",
|
||||
"How frames reach the screen, and the performance overlay.": "Hvordan rammer når skjermen, og ytelsesoverlegget.",
|
||||
"Brighter-than-white output on an HDR display.": "Lysere enn hvit utgang på en HDR-skjerm.",
|
||||
"Results for \"%s\"": "Resultater for \"%s\"",
|
||||
"No settings match.": "Ingen innstillinger samsvarer.",
|
||||
"horizon.title.not_affiliated": "Ikke tilknyttet Mojang eller Microsoft",
|
||||
"horizon.extras.header.lighting": "Belysning",
|
||||
"horizon.extras.header.third_person": "Tredje person",
|
||||
"horizon.extras.header.particles": "Partikler",
|
||||
"horizon.extras.header.inventory": "Inventar",
|
||||
"horizon.extras.header.multiplayer": "Flerspiller",
|
||||
"horizon.extras.header.sound": "Lyd",
|
||||
"horizon.extras.header.menus": "Menyer",
|
||||
"horizon.extras.fullbright": "Fullt lys",
|
||||
"horizon.extras.dynamic_lights": "Dynamiske lys",
|
||||
"horizon.extras.own_name_tag": "Egen navnelapp",
|
||||
"horizon.extras.hide_block_particles": "Skjul blokkbruddspartikler",
|
||||
"horizon.extras.auto_reconnect": "Koble til automatisk",
|
||||
"horizon.extras.ui_sound_reserve": "Behold kanaler for brukergrensesnittlyder",
|
||||
"horizon.extras.music_page": "Musikkside",
|
||||
"horizon.extras.mouse_tweaks": "Mouse Tweaks kontroller",
|
||||
"horizon.extras.mouse_tweaks_wheel": "Rullehjul flytter elementer",
|
||||
"horizon.extras.sort": "Sorter knapper",
|
||||
"horizon.extras.sort_key": "Sorteringsnøkkel",
|
||||
"horizon.extras.no_ai": "Ingen AI",
|
||||
"horizon.extras.agent_control": "Agentkontroll",
|
||||
"horizon.extras.discord_presence": "Discord Rik tilstedeværelse",
|
||||
"horizon.extras.splitscreen": "Delt skjerm",
|
||||
"horizon.extras.chat_animation": "Jevn chat",
|
||||
"horizon.extras.chat_indicator": "Meldingsindikator",
|
||||
"horizon.extras.wheel_cycles_settings": "Rullehjul endrer innstillinger",
|
||||
"horizon.extras.pack_browser": "Ressurspakkenettleser",
|
||||
"horizon.extras.fullbright.tooltip": "Vis blokker og enheter i fullt lys, selv i mørke områder.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Fakler og andre lyskilder som holdes, slippes eller brenner tenner blokkene rundt dem.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Viser navnet ditt over hodet i tredje person, slik andre spillere ser det. Skjult når serveren skjuler teamets navn eller du er usynlig.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Skjuler ruskpartiklene som brister fra en ødelagt blokk og smuldrer av den du miner. Andre partikler blir værende.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Blir med på en server igjen etter en tapt forbindelse eller en omstart, aldri etter et kick eller utestengelse.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Lyder i verden lar 16 av de 247 lydkanalene være fri, så en flom av dem (en stor eksplosjon) kan ikke dempe menyklikk.",
|
||||
"horizon.extras.music_page.tooltip": "Musikk og lyder får en Musikk...-side: spill et hvilket som helst innebygd spor nå, eller slå av et slik at spillet slutter å velge det. Ingenting endres før du slår av et spor.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Høyre-dra plasserer én per spor, venstre-dra samler det holdte elementet, Shift + venstre-dra flytter hvert spor.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Ned sender ett element i sporet til den andre siden, opp bringer ett tilbake.",
|
||||
"horizon.extras.sort.tooltip": "Sorterer inventaret, kistene, tønnene, enderkistene og shulkerboksene i kreativ fanerekkefølge, slår sammen stabler.",
|
||||
"horizon.extras.sort_key.tooltip": "Sorterer delen under markøren. Klikk og trykk deretter på en tast; Escape frigjør det.",
|
||||
"horizon.extras.no_ai.tooltip": "Skjuler og slår av hver AI-funksjon: agentkontroll, horizon-ctl og MCP-servere. Starterens bryter også.",
|
||||
"horizon.extras.agent_control.tooltip": "Din egen AI-agent kan spille dette spillet gjennom horizon-ctl eller en MCP-klient, kun på denne datamaskinen.",
|
||||
"horizon.extras.discord_presence.tooltip": "Viser Playing Horizon på Discord-profilen din mens Discord kjører: menyer, enkeltspiller eller flerspiller, dimensjonen og spilletiden. Aldri et verdensnavn eller en serveradresse.",
|
||||
"Calibrate...": "Kalibrer...",
|
||||
"Calibrate Anyway...": "Kalibrer uansett...",
|
||||
"Use Automatic Setup": "Bruk automatisk oppsett",
|
||||
"Shortcut: %s": "Snarvei: %s",
|
||||
"Next": "Neste",
|
||||
"HDR Calibration %s/%s: %s": "HDR-kalibrering %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Stiller inn de tre under fullskjerm, ett testbilde om gangen.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "En tast som åpner denne skjermen fra spillet. Ikke bundet først.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Skjermen din sa ikke hvor lys den blir: kalibrer for å konfigurere den.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Allerede konfigurert for skjermen din. Kalibrer bare hvis HDR ser feil ut.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Setter Peak Brightness og Paper White tilbake til skjermens oppsett.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Sett opp for skjermen din. La den stå på Auto med mindre det ser feil ut.",
|
||||
"Lower it until the logo just disappears into the black.": "Senk den ned til logoen bare forsvinner inn i det svarte.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Hev den til logoen bare forsvinner inn i den lyse firkanten.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Gjør kortet hvitt som er behagelig å se på lenge, i lyset du spiller i.",
|
||||
"horizon.splitscreen.profiles.forget": "Glem",
|
||||
"horizon.splitscreen.profiles.create": "Opprett",
|
||||
"horizon.splitscreen.profiles.you": " - deg",
|
||||
"horizon.splitscreen.profiles.forget_named": "Glem %s",
|
||||
"horizon.splitscreen.profiles.previous": "Forrige",
|
||||
"horizon.splitscreen.profiles.next": "Neste",
|
||||
"horizon.splitscreen.profiles.new_button": "Ny profil...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Glem profil?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Den forlater denne instansens liste.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "Verdens fremgang og innstillinger beholdes.",
|
||||
"horizon.splitscreen.profiles.new_title": "Ny profil",
|
||||
"horizon.splitscreen.profiles.name_hint": "Navn (3 til 16 bokstaver, sifre, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Bytt profil",
|
||||
"horizon.splitscreen.profiles.offline_note": "Frakoblede profiler tilhører denne forekomsten",
|
||||
"horizon.splitscreen.profiles.move": "Flytt dit du spiller",
|
||||
"horizon.splitscreen.profiles.move_place": "Flytt dit du spiller: %s",
|
||||
"horizon.friends.title": "Spill med venner",
|
||||
"horizon.friends.hint": "Venner limer inn koden etter tittelskjermens Venner-knapp, i Direct Connection eller på startprogrammets Play-side.",
|
||||
"horizon.friends.copied": "Kopiert",
|
||||
"horizon.friends.copy_code": "Kopier kode",
|
||||
"horizon.friends.stop_sharing": "Slutt å dele",
|
||||
"horizon.friends.none_yet": "Ingen venner her ennå.",
|
||||
"horizon.friends.here_now": "Her nå: %s",
|
||||
"horizon.friends.share_intro": "Del denne verden bak en invitasjonskode: den åpnes til LAN, og venner på internett blir med med koden. Ingen server av oss er involvert.",
|
||||
"horizon.friends.start_sharing": "Begynn å dele",
|
||||
"horizon.friends.troubleshooting": "Feilsøking...",
|
||||
"horizon.friends.stopped": "Sluttet å dele invitasjonskoden.",
|
||||
"horizon.friends.short_code_changed": "Noen fortsatte å prøve en feil kort kode, så den endret seg.",
|
||||
"horizon.friends.wrong_code": "Noen prøvde feil kode.",
|
||||
"horizon.friends.code_ready": "Invitasjonskoden din er klar: Spillmeny, Spill med venner. Kopier den og send den til vennene dine.",
|
||||
"horizon.friends.share_failed": "Kunne ikke dele verden: %s",
|
||||
"horizon.friends.finding_route": "Finner hvordan venner kan nå deg …",
|
||||
"horizon.osk.shift": "Skift",
|
||||
"horizon.osk.space": "Plass",
|
||||
"horizon.osk.delete": "Slett",
|
||||
"horizon.osk.symbols": "Symboler",
|
||||
"horizon.osk.close": "Lukk",
|
||||
"horizon.extras.chat_animation.tooltip": "En ny chat-linje glir inn fra venstre mens den toner inn, i stedet for å virke hel.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Den fargede linjen til venstre for en chattelinje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Rullehjulet over en knapp i innstillingene endrer verdien, som i vanilje. Av, hjulet ruller bare listen.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs får en Legg til-knapp som søker i Modrinth og installerer en pakke i pakkemappen din. Av, spillet kontakter aldri Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "I en verden kan en annen kontroller bli med som en ny spiller: trykk A når den spør, velg deretter en profil og hvor du vil spille.",
|
||||
"horizon.splitscreen.setup.monitor": "Overvåk %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "en monitor",
|
||||
"horizon.splitscreen.setup.window": "Vindu %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nytt vindu",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Fyll %s",
|
||||
"horizon.splitscreen.setup.done": "Ferdig",
|
||||
"horizon.splitscreen.setup.on": "På",
|
||||
"horizon.splitscreen.setup.off": "Av",
|
||||
"horizon.splitscreen.setup.player": "Spiller %s",
|
||||
"horizon.splitscreen.setup.revert": "Gå tilbake",
|
||||
"horizon.splitscreen.setup.apply": "Søk",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Ingen spiller i vindu 1: det gjemmer seg",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Ingen spiller i vindu %s: det lukkes ved bruk",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Ingen spiller i vindu %s: det åpnes ikke",
|
||||
"horizon.splitscreen.setup.warning.on_main": "Vindu %s fyller vindu 1s skjerm: spillerne spiller i vindu 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "Skjermen til Window %s er ikke tilkoblet: spillerne spiller i Window 1",
|
||||
"horizon.splitscreen.setup.status.off": "Delt skjerm er av.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Slå den på, og trykk deretter på en knapp på en annen kontroller i en verden.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Trykk A for å bli med og velge en profil.",
|
||||
"horizon.splitscreen.setup.status.idle": "Delt skjerm inaktiv",
|
||||
"horizon.splitscreen.setup.status.alone": "Ingen andre lokale spillere.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Solokontroller og kontrollertilbakemeldinger er uendret.",
|
||||
"horizon.splitscreen.setup.window_empty": "Vindu %s: tomt",
|
||||
"horizon.splitscreen.setup.note.select": "Klikk på en spiller, eller dra den til et vindu eller en skjerm.",
|
||||
"horizon.splitscreen.setup.note.apply": "Søk om å spille på denne måten, Gå tilbake for å angre.",
|
||||
"horizon.splitscreen.setup.note.place": "Hvor skal %s spille?",
|
||||
"horizon.splitscreen.setup.this_player": "denne spilleren",
|
||||
"horizon.splitscreen.setup.switch_disabled": "Delt skjerm slås av når de andre har gått (pausemenyen deres).",
|
||||
"horizon.splitscreen.setup.window_place": "Vindu %s (%s av %s)",
|
||||
"horizon.splitscreen.setup.names": "Navn",
|
||||
"horizon.splitscreen.setup.identify": "Identifiser",
|
||||
"horizon.splitscreen.setup.layout.two": "2 deler: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 deler: %s",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Side ved side",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Topp og bunn",
|
||||
"horizon.splitscreen.setup.layout.three.0": "En bred på toppen",
|
||||
"horizon.splitscreen.setup.layout.three.1": "En høy til venstre",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Side ved side",
|
||||
"horizon.splitscreen.setup.help.player": "Klikk eller dra %s for å flytte dem.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (side ved side når vinduet er bredt). Trykk for å endre.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Trykk for å endre.",
|
||||
"horizon.splitscreen.setup.help.switch": "Delt skjerm for denne datamaskinen, på eller av.",
|
||||
"horizon.splitscreen.setup.help.identify": "Hver spillers visning viser hvilket vindu den er i.",
|
||||
"horizon.splitscreen.setup.help.names": "Hver spillers navn i hjørnet av visningen.",
|
||||
"horizon.splitscreen.setup.help.revert": "Angre endringene som ikke er tatt i bruk ennå.",
|
||||
"horizon.splitscreen.setup.help.apply": "Spill slik kartet viser.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Ingenting er endret ennå.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Gi opp Pad",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Kontroller tilkoblet",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Kontrolleren er frakoblet",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Koble den til igjen, eller trykk på en knapp på en annen",
|
||||
"horizon.splitscreen.prompt.join.one": "%s ønsker å bli med",
|
||||
"horizon.splitscreen.prompt.join.many": "%s kontroller ønsker å bli med",
|
||||
"horizon.splitscreen.prompt.join.yes": "Bli med",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Spill som P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Nei",
|
||||
"horizon.splitscreen.prompt.lost": "%s: kontrolleren frakoblet",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Bruk en annen",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Gå",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Kontrolleren din slutter å kjøre deg og får tilbud om å bli med (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s til %s: A slipper den her, B setter den tilbake.",
|
||||
"horizon.splitscreen.setup.note.held": "%s plukket opp: venstre/høyre velger hvor, A slipper, B setter den tilbake.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Skjuler modeller bak ugjennomsiktige blokker. Asynkron sjekker av tråden; enheter krysser av.",
|
||||
"Block Entities": "Blokker enheter",
|
||||
"Shulker Boxes": "Shulker bokser",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Gjenbruker utfelte bokser; lokkbevegelsen forblir animert.",
|
||||
"Signs": "Tegn",
|
||||
"Reuses sign text until text, light, or font changes.": "Gjenbruker skilttekst til tekst, lys eller skrift endres.",
|
||||
"Decorated Pots": "Dekorerte gryter",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Gjenbruker fortsatt potter; bryte vingling og skjærendring tegne om.",
|
||||
"Bells": "Klokker",
|
||||
"Reuses still bells; ringing stays animated.": "Gjenbruker fortsatt bjeller; ringing forblir animert.",
|
||||
"Chests": "Kister",
|
||||
"Reuses closed chests; moving lids stay animated.": "Gjenbruker lukkede kister; bevegelige lokk forblir animerte.",
|
||||
"Banners": "Bannere",
|
||||
"Reuses poles; flags and pattern layers still update.": "Gjenbruker stolper; flagg og mønsterlag oppdateres fortsatt.",
|
||||
"Copper Golem Statues": "Kobber Golem statuer",
|
||||
"Reuses statue geometry until its block state changes.": "Gjenbruker statuegeometri til blokktilstanden endres.",
|
||||
"Leaf Culling": "Utskjæring av blader",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Hvert blad ansikt. Vaniljes fancy blader.",
|
||||
"Distant Plant Dither": "Fjern Plant Dither",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Holder fine planter i høyoppløsningspakker fra å falme langt unna.",
|
||||
"Screenshot Quality": "Skjermbildekvalitet",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "F2-skjermbilder med så mange ganger spillets oppløsning. Høyere tar lengre tid.",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Viser en sammensatt ramme mellom hver to ekte: jevnere, litt mer etterslep.",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Gjenbruker pakkeskyggekartet når sol, utsikt og verden matcher. Av som standard.",
|
||||
"Find BlueMap": "Finn BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "På servere finner BlueMap-nettkartet deres for langt terreng. Bli med neste gang.",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "Hvor langt rundt deg er BlueMap-fliser lastet ned. Bli med neste gang.",
|
||||
"Synchronous": "Synkron",
|
||||
"Asynchronous": "Asynkron",
|
||||
"Solid Aggressive": "Solid aggressiv",
|
||||
"Hollow": "Hul",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "Blader helt inne i en baldakin trekker fast: færre gjennomsiktige lag.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Blader med en åpen side trekker også fast: raskere, tykkere kanter.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Slipper ansikter mellom bladene: raskere, men hull ser gjennom.",
|
||||
"Hotbar: Previous Slot": "Hotbar: Forrige spilleautomat",
|
||||
"Hotbar: Next Slot": "Hotbar: Neste spilleautomat",
|
||||
"horizon.agent.connected.one": "Tilkoblet: 1 agenttilkobling",
|
||||
"horizon.agent.connected.many": "Tilkoblet: %s agentforbindelser",
|
||||
"horizon.agent.connected.one_ago": "Tilkoblet: 1 agenttilkobling, siste kommando for %s s siden",
|
||||
"horizon.agent.connected.many_ago": "Tilkoblet: %s agentforbindelser, siste kommando for %s s siden",
|
||||
"horizon.agent.waiting": "Venter på en agent.",
|
||||
"horizon.agent.off": "Av: Agenter kan ikke nå dette spillet.",
|
||||
"horizon.agent.session_opened": "Åpnet for denne økten av programmet som startet spillet.",
|
||||
"horizon.agent.socket": "Socket: %s (kopier detaljene for hele banen)",
|
||||
"horizon.agent.local_only": "Kun lokalt: agenter på denne datamaskinen, aldri nettverket.",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Skjuler modeller bak ugjennomsiktige blokker. Entitetsoppdatering. Av med tilpassede pakker.",
|
||||
"horizon.extras.repro_keys": "Opptakssnarveier",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R starter eller stopper et repro-opptak og F3+E skriver bunten. Av som standard: et opptak kopierer verden.",
|
||||
"horizon.agent.record_session": "Start opptaksøkten",
|
||||
"horizon.agent.record_stop": "Stopp opptak av økt",
|
||||
"horizon.agent.export_repro": "Eksporter Repro-pakke",
|
||||
"horizon.repro.started": "Opptaket startet; F3+R stopper den og skriver bunten.",
|
||||
"horizon.repro.stopped": "Opptaket stoppet: %s",
|
||||
"horizon.repro.bundle": "Repro-pakke: %s",
|
||||
"horizon.repro.no_recording": "Det er ingen opptak å eksportere ennå.",
|
||||
"horizon.repro.replay_started": "Replay startet: %s",
|
||||
"horizon.pack.addInfo": "Legg til en ressurspakke fra Modrinth",
|
||||
"horizon.pack.addWorking": "Spør Modrinth...",
|
||||
"horizon.pack.addNone": "Finner ingen ressurspakker",
|
||||
"horizon.pack.addTitle": "Legg til en ressurspakke",
|
||||
"horizon.pack.install": "Installer",
|
||||
"horizon.pack.installing": "Installerer",
|
||||
"horizon.pack.installed": "Installert",
|
||||
"horizon.pack.search": "Søk",
|
||||
"horizon.pack.searchHint": "Søk i Modrinth...",
|
||||
"horizon.pack.filters": "Filtre",
|
||||
"horizon.pack.filter.realistic": "Realistisk",
|
||||
"horizon.pack.filter.simplistic": "Forenklet",
|
||||
"horizon.pack.filter.vanillaLike": "Vaniljeaktig",
|
||||
"horizon.pack.addFailed": "Pakken kunne ikke legges til: %s",
|
||||
"horizon.extras.header.cosmetics": "Kosmetikk",
|
||||
"horizon.extras.cosmetics_mine": "Kosmetikken min",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skjult: hver Horizon-spiller ser deg uten kosmetikken din, inkludert du. Samme bryter som bærerakettens garderobe.",
|
||||
"horizon.extras.cosmetics_others": "Andre spilleres kosmetikk",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Hva andre Horizon-spillere har på seg: alt, bare navnelappene deres, eller ingenting. Skjult spør spillet aldri hva de har på seg.",
|
||||
"horizon.extras.cosmetics_auras": "Andres auraer og stier",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auraer og stier andre spillere har på seg. Av som standard.",
|
||||
"horizon.extras.cosmetics.shown": "Vist",
|
||||
"horizon.extras.cosmetics.hidden": "Skjult",
|
||||
"horizon.extras.cosmetics.name_tags": "Kun navnelapper"
|
||||
}
|
||||
@@ -321,6 +321,8 @@
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Używa ponownie mapy cieni paczki, gdy słońce, widok i świat się zgadzają. Domyślnie wyłączone.",
|
||||
"Synchronous": "Synchroniczne",
|
||||
"Asynchronous": "Asynchroniczne",
|
||||
"Hidden Faces": "Ukryte ściany",
|
||||
"Drops leaf faces behind two leaf layers: faster, same crowns.": "Pomija ściany liści za dwiema warstwami liści: szybciej, korony bez zmian.",
|
||||
"Solid": "Pełne",
|
||||
"Solid Aggressive": "Pełne, agresywnie",
|
||||
"Hollow": "Puste",
|
||||
@@ -354,5 +356,9 @@
|
||||
"horizon.pages.open.split_setup": "Podzielony ekran…",
|
||||
"horizon.pages.split_settings": "Ustawienia podzielonego ekranu",
|
||||
"horizon.pages.split_setup": "Podzielony ekran",
|
||||
"horizon.pages.split_setup.tooltip": "Gracze na tym komputerze: kontrolery mogą dołączyć do twojego świata."
|
||||
"horizon.pages.split_setup.tooltip": "Gracze na tym komputerze: kontrolery mogą dołączyć do twojego świata.",
|
||||
"Particle Culling": "Pomijanie cząsteczek",
|
||||
"Draws every particle.": "Rysuje wszystkie cząsteczki.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles.": "Pomija cząsteczki poza ekranem, mniejsze niż piksel i zasłonięte przez teren.",
|
||||
"Skips offscreen, subpixel and terrain-hidden particles with less render-thread work.": "Pomija cząsteczki poza ekranem, mniejsze niż piksel i zasłonięte przez teren, odciążając wątek renderowania."
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
{
|
||||
"horizon.options.controller": "Controlador",
|
||||
"horizon.options.vibration": "Vibração",
|
||||
"horizon.options.adaptive_triggers": "Gatilhos adaptativos",
|
||||
"horizon.options.lightbar": "Barra de luzes",
|
||||
"horizon.options.look_sensitivity": "Sensibilidade da câmera",
|
||||
"horizon.options.invert_x": "Inverter câmera X",
|
||||
"horizon.options.invert_y": "Inverter câmera Y",
|
||||
"horizon.options.sneak_button": "Botão de agachar",
|
||||
"horizon.options.move_deadzone": "Zona morta do movimento",
|
||||
"horizon.options.look_deadzone": "Zona morta da câmera",
|
||||
"horizon.options.button_glyphs": "Glifos de botão",
|
||||
"horizon.options.menu_navigation": "Navegação no menu",
|
||||
"horizon.options.on_screen_keyboard": "Teclado na tela",
|
||||
"horizon.options.chat_delay": "Atraso no bate-papo",
|
||||
"horizon.options.lightbar.health": "Saúde",
|
||||
"horizon.options.lightbar.dimension": "Dimensão",
|
||||
"horizon.options.glyphs.auto": "Automático",
|
||||
"horizon.options.glyphs.playstation": "Playstation",
|
||||
"horizon.options.menu_focus": "Foco",
|
||||
"horizon.options.menu_pointer": "Ponteiro",
|
||||
"horizon.options.keyboard.auto": "Automático",
|
||||
"horizon.options.keyboard.horizon": "Horizonte",
|
||||
"horizon.options.keyboard.steam": "Vapor",
|
||||
"horizon.options.unavailable": "Este recurso ainda não está disponível no Horizon.",
|
||||
"horizon.options.search": "Configurações de pesquisa...",
|
||||
"horizon.pages.mods": "Modificações",
|
||||
"horizon.pages.shader_packs": "Pacotes de sombreadores",
|
||||
"horizon.pages.shader_options": "Opções de sombreamento",
|
||||
"horizon.pages.controller": "Configurações do controlador",
|
||||
"horizon.pages.controller_binds": "Ligações de controlador",
|
||||
"horizon.pages.extras": "Extras Horizonte",
|
||||
"horizon.pages.settings": "Configurações do Horizon",
|
||||
"horizon.pages.agent": "Agente de IA",
|
||||
"horizon.split.leave": "Sair",
|
||||
"horizon.pages.split_settings": "Configurações de tela dividida",
|
||||
"horizon.pages.split_profiles": "Quem está jogando?",
|
||||
"horizon.pages.split_setup": "Configuração de tela dividida",
|
||||
"horizon.pages.music": "Música",
|
||||
"horizon.pages.music.menu": "Cardápio",
|
||||
"horizon.pages.music.overworld": "Mundo superior",
|
||||
"horizon.pages.music.creative": "Criativo",
|
||||
"horizon.pages.music.underwater": "Subaquático",
|
||||
"horizon.pages.music.end": "O Fim",
|
||||
"horizon.pages.music.boss": "Chefe",
|
||||
"horizon.pages.music.credits": "Créditos",
|
||||
"horizon.pages.music.overworld_biomes": "Biomas do mundo superior",
|
||||
"horizon.pages.music.nether_biomes": "Biomas inferiores",
|
||||
"horizon.pages.music.end_biomes": "Fim dos biomas",
|
||||
"horizon.pages.music.other": "Outras músicas",
|
||||
"horizon.pages.music.empty": "Sem faixas de música: os sons do jogo não são carregados.",
|
||||
"horizon.pages.music.tooltip": "As faixas integradas, por situação: toque uma agora ou desligue alguma para que o jogo pare de selecioná-la.",
|
||||
"horizon.pages.settings.tooltip": "Configurações próprias do Horizon: gráficos, controladores, extras e mods.",
|
||||
"horizon.pages.graphics.tooltip": "Gráficos, simplesmente: cada configuração diz quanto custa.",
|
||||
"horizon.pages.graphics_advanced.tooltip": "Todas as configurações gráficas, incluindo Horizon, com o custo de cada uma.",
|
||||
"horizon.pages.controller.tooltip": "Controladores: seus botões, manípulos, vibração e navegação no menu.",
|
||||
"horizon.pages.extras.tooltip": "Adições opcionais, todas desativadas por padrão: o jogo permanece básico até você ligar um.",
|
||||
"horizon.pages.agent.tooltip": "Deixe seu próprio agente de IA jogar: clientes Horizon-ctl e MCP neste computador.",
|
||||
"horizon.pages.split_profiles.tooltip": "Quem joga: um perfil mantido nesta pasta do jogo.",
|
||||
"horizon.pages.split_setup.tooltip": "As janelas e monitores, e quem joga onde.",
|
||||
"horizon.pages.shader_packs.tooltip": "Shader packs da pasta shaderpacks: próprios do Horizon e aqueles no formato shaders/. Um corre de cada vez.",
|
||||
"horizon.pages.mods.tooltip": "Mods feitos para Horizon, da pasta Horizon-mods. Cada um é executado em uma sandbox e permanece desligado até você ligá-lo.",
|
||||
"horizon.pages.open.graphics": "Gráficos...",
|
||||
"horizon.pages.open.graphics_advanced": "Gráficos avançados...",
|
||||
"horizon.pages.open.shader_packs": "Pacotes de sombreadores...",
|
||||
"horizon.pages.open.controller": "Configurações do controlador...",
|
||||
"horizon.pages.open.split_setup": "Tela dividida...",
|
||||
"horizon.pages.open.mods": "Modos...",
|
||||
"horizon.pages.open.agent": "Agente de IA...",
|
||||
"horizon.pages.open.horizon_graphics": "Gráficos Horizonte...",
|
||||
"horizon.pages.open.controller_binds": "Ligações do controlador...",
|
||||
"horizon.pages.open.horizon_settings": "Configurações do Horizon...",
|
||||
"horizon.pages.open.music": "Música...",
|
||||
"horizon.mods.empty": "Sem mods: coloque os arquivos .hmod na pasta horizonte-mods",
|
||||
"horizon.mods.stopped": " (Parado)",
|
||||
"horizon.mods.not_built": " (Não construído)",
|
||||
"horizon.mods.dev": " (Desenvolvedor)",
|
||||
"horizon.mods.settings": "Configurações",
|
||||
"horizon.mods.in_world": " (deixe o mundo mudar)",
|
||||
"horizon.mods.open_folder": "Abra a pasta Mod",
|
||||
"horizon.shader_packs.empty": "Sem pacotes de shader: coloque-os na pasta shaderpacks",
|
||||
"horizon.shader_packs.kind_horizon": " (Horizonte)",
|
||||
"horizon.shader_options.open": "Opções de sombreamento...",
|
||||
"horizon.shader_options.profile": "Perfil",
|
||||
"horizon.shader_options.loading": "Lendo as opções do pacote de shaders...",
|
||||
"horizon.agent.copy_details": "Copiar detalhes da conexão",
|
||||
"horizon.multiplayer.join": "Junte-se",
|
||||
"horizon.multiplayer.cancel_reconnecting": "Cancelar reconexão",
|
||||
"horizon.multiplayer.join_friend": "Junte-se a um amigo",
|
||||
"horizon.multiplayer.invite_code": "Código de convite",
|
||||
"horizon.multiplayer.relay": "Relé (opcional)",
|
||||
"Preset": "Predefinido",
|
||||
"LOD Distance": "Distância de LOD",
|
||||
"Far Detail": "Detalhe distante",
|
||||
"Cave Culling": "Abate em Caverna",
|
||||
"Entity Culling": "Seleção de entidade",
|
||||
"Chunk Updates": "Atualizações de blocos",
|
||||
"Upload Budget": "Carregar orçamento",
|
||||
"Sky": "Céu",
|
||||
"Fog": "Nevoeiro",
|
||||
"Weather": "Tempo",
|
||||
"Animated Textures": "Texturas Animadas",
|
||||
"Present Mode": "Modo Presente",
|
||||
"Frame Pacing": "Ritmo de quadro",
|
||||
"Worker Threads": "Threads de trabalho",
|
||||
"Resolution Scale": "Escala de resolução",
|
||||
"Upscaling": "Aumento de escala",
|
||||
"Paper White": "Papel Branco",
|
||||
"Peak Brightness": "Brilho máximo",
|
||||
"Black Level": "Nível de preto",
|
||||
"UI Brightness": "Brilho da IU",
|
||||
"Battery Mode": "Modo Bateria",
|
||||
"Graphics API": "API gráfica",
|
||||
"Translucent Batching": "Lote Translúcido",
|
||||
"Performance Overlay": "Sobreposição de desempenho",
|
||||
"Full Detail Distance": "Distância detalhada completa",
|
||||
"On": "Ligado",
|
||||
"Off": "Desligado",
|
||||
"Unlimited": "Ilimitado",
|
||||
"Auto": "Automático",
|
||||
"Custom": "Personalizado",
|
||||
"Performance": "Desempenho",
|
||||
"Balanced": "Equilibrado",
|
||||
"Quality": "Qualidade",
|
||||
"Ultra Performance": "Ultradesempenho",
|
||||
"Native": "Nativo",
|
||||
"Full": "Completo",
|
||||
"Reduced": "Reduzido",
|
||||
"Efficient": "Eficiente",
|
||||
"Precise": "Preciso",
|
||||
"Auto (VSync)": "Automático (VSync)",
|
||||
"Mailbox": "Caixa de correio",
|
||||
"Immediate": "Imediato",
|
||||
"Vulkan": "Vulcano",
|
||||
"DirectX 12": "DirectX12",
|
||||
"FPS and Graph": "FPS e gráfico",
|
||||
"No cost": "Sem custo",
|
||||
"Low cost": "Baixo custo",
|
||||
"Medium cost": "Custo médio",
|
||||
"High cost": "Alto custo",
|
||||
"Saves time": "Economiza tempo",
|
||||
"Graphics": "Gráficos",
|
||||
"Done": "Concluído",
|
||||
"This feature is not yet available in Horizon.": "Este recurso ainda não está disponível no Horizon.",
|
||||
"Your display or system doesn't offer HDR output.": "Seu monitor ou sistema não oferece saída HDR.",
|
||||
"Turn HDR on to use it.": "Ative o HDR para usá-lo.",
|
||||
"Your GPU or graphics API doesn't support it.": "Sua GPU ou API gráfica não oferece suporte.",
|
||||
"%s chunks": "%s pedaços",
|
||||
"%s per frame": "%s por quadro",
|
||||
"%s MiB/frame": "%s MiB/quadro",
|
||||
"%s KiB/frame": "%s KiB/quadro",
|
||||
"Auto (%s)": "Automático (%s)",
|
||||
"Full-detail terrain range. LOD Distance can extend local worlds farther.": "Gama de terreno com todos os detalhes. A distância LOD pode estender os mundos locais ainda mais.",
|
||||
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.": "Terreno áspero e distante em mundos locais. As GPUs integradas param em 128 pedaços.",
|
||||
"Reduced draws far terrain coarser: faster on this integrated GPU.": "Redução de desenhos em terrenos mais grosseiros: mais rápido nesta GPU integrada.",
|
||||
"Reduced draws far terrain coarser, for GPUs it slows down.": "Reduzido atrai terrenos muito mais grosseiros, para GPUs ele fica mais lento.",
|
||||
"Field of view. Wider shows more terrain at once.": "Campo de visão. Mais amplo mostra mais terreno ao mesmo tempo.",
|
||||
"Caps frames per second; a cap saves power and heat.": "Limita quadros por segundo; uma tampa economiza energia e calor.",
|
||||
"Waits for the display's refresh: no tearing, a little latency.": "Aguarda a atualização da tela: sem rasgos, um pouco de latência.",
|
||||
"Size of menus and the HUD.": "Tamanho dos menus e do HUD.",
|
||||
"Sets the options below together. Changing one makes it Custom.": "Define as opções abaixo juntas. Alterar um torna-o personalizado.",
|
||||
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, fogo e luz solar brilham mais que o branco. Precisa de uma tela HDR.",
|
||||
"Your display's darkest black. 0 for OLED; LCDs glow a little.": "O preto mais escuro da sua tela. 0 para OLED; Os LCDs brilham um pouco.",
|
||||
"How bright highlights get. Auto: the peak your system reports.": "Como os destaques ficam brilhantes. Automático: o pico relatado pelo seu sistema.",
|
||||
"How bright white paper and menus look. Auto: your desktop's white.": "Como ficam os menus e o papel branco brilhante. Automático: sua área de trabalho fica branca.",
|
||||
"How bright the HUD and menus are, against paper white.": "Quão brilhantes são o HUD e os menus, em contraste com o branco do papel.",
|
||||
"How far mobs, crops and machines keep working. CPU time.": "Até onde mobs, colheitas e máquinas continuam trabalhando. Tempo de CPU.",
|
||||
"How far away mobs and items are drawn, as a share of normal.": "A que distância os mobs e os itens são atraídos, em proporção ao normal.",
|
||||
"Skips terrain hidden behind closed sections. Off costs a lot.": "Ignora terreno escondido atrás de seções fechadas. Fora custa muito.",
|
||||
"Hides entities behind opaque blocks.": "Oculta entidades atrás de blocos opacos.",
|
||||
"No entity occlusion culling.": "Nenhuma seleção de oclusão de entidade.",
|
||||
"CPU occlusion test runs this frame.": "O teste de oclusão da CPU executa este quadro.",
|
||||
"Occlusion checks run on a worker thread.": "As verificações de oclusão são executadas em um thread de trabalho.",
|
||||
"Meshed sections uploaded per frame. Lower is smoother while loading.": "Seções em malha carregadas por quadro. Lower é mais suave durante o carregamento.",
|
||||
"Soft shadows in corners. Costs chunk building time only.": "Sombras suaves nos cantos. Custa apenas o tempo de construção.",
|
||||
"Smaller copies of block textures for distant terrain: less shimmer.": "Cópias menores de texturas de bloco para terrenos distantes: menos brilho.",
|
||||
"Blends grass and water colours across biomes when building chunks.": "Mistura cores de grama e água em biomas ao construir pedaços.",
|
||||
"Cloud layer: off, flat (fast) or 3D (fancy).": "Camada de nuvem: desativada, plana (rápida) ou 3D (extravagante).",
|
||||
"How far clouds reach, in chunks.": "Quão longe as nuvens chegam, em pedaços.",
|
||||
"Draws the sky, sun, moon and stars.": "Desenha o céu, o sol, a lua e as estrelas.",
|
||||
"Distance fog. Off shows the edge of the loaded world.": "Nevoeiro à distância. Off mostra o limite do mundo carregado.",
|
||||
"Draws rain and snow.": "Desenha chuva e neve.",
|
||||
"How far rain and snow fall around you.": "Quão longe a chuva e a neve caem ao seu redor.",
|
||||
"How many particles effects spawn.": "Quantos efeitos de partículas aparecem.",
|
||||
"Round shadows under mobs and items.": "Sombras redondas sob mobs e itens.",
|
||||
"Leaves with see-through gaps. Off draws them solid.": "Folhas com lacunas transparentes. Off os desenha sólidos.",
|
||||
"Sorts water and glass more exactly behind each other.": "Classifica a água e o copo de maneira mais exata, um atrás do outro.",
|
||||
"Which chunk rebuilds wait for the frame: none, nearby or yours.": "Quais pedaços são reconstruídos aguardando o quadro: nenhum, próximo ou seu.",
|
||||
"Animates water, lava, fire and other moving textures.": "Anima água, lava, fogo e outras texturas em movimento.",
|
||||
"World pixels per screen pixel. Below 100% is faster on the GPU.": "Pixels mundiais por pixel da tela. Abaixo de 100% é mais rápido na GPU.",
|
||||
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.": "Suaviza bordas irregulares. FXAA: mais suave. TAA: coisas mais suaves e em movimento podem deixar rastros.",
|
||||
"Renders the world smaller and rebuilds it with TAA. Faster, softer.": "Torna o mundo menor e o reconstrói com TAA. Mais rápido, mais suave.",
|
||||
"HDR output on an HDR display: lava, fire and sunlight shine brighter.": "Saída HDR em uma tela HDR: lava, fogo e luz solar brilham mais intensamente.",
|
||||
"How frames reach the screen while VSync is off.": "Como os frames chegam à tela enquanto o VSync está desligado.",
|
||||
"Efficient sleeps until the next frame; Precise spins, like vanilla.": "Eficiente dorme até o próximo quadro; Giros precisos, como baunilha.",
|
||||
"Fills the screen (borderless).": "Preenche a tela (sem bordas).",
|
||||
"Brightens dark places.": "Ilumina locais escuros.",
|
||||
"Lowers the frame rate when minimised, or idle too.": "Reduz a taxa de quadros quando minimizado ou também inativo.",
|
||||
"Sways the view while walking.": "Balança a visão enquanto caminha.",
|
||||
"Fades newly loaded chunks in.": "Desaparece os pedaços recém-carregados.",
|
||||
"Darkens the screen's edges.": "Escurece as bordas da tela.",
|
||||
"Shows when the world saves.": "Mostra quando o mundo salva.",
|
||||
"Shows attack cooldown.": "Mostra o tempo de espera do ataque.",
|
||||
"60 FPS cap (30 in menus or unfocused), half the workers: less power.": "Limite de 60 FPS (30 em menus ou sem foco), metade dos trabalhadores: menos energia.",
|
||||
"Threads that generate, light and mesh chunks. Next start.": "Fios que geram pedaços leves e de malha. Próximo começo.",
|
||||
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.": "Vulkan, depois DirectX 12 (Windows) ou OpenGL (Linux) onde falha. Próximo começo.",
|
||||
"Water and glass in one buffer: fewer draw calls, a bit more VRAM. Next start.": "Água e vidro em um buffer: menos chamadas de desenho, um pouco mais de VRAM. Próximo começo.",
|
||||
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Dados de malha enviados para a GPU por quadro. Lower é mais suave durante o carregamento.",
|
||||
"Frame rate and frame times over the game, with a graph.": "Taxa de quadros e tempos de quadros ao longo do jogo, com gráfico.",
|
||||
"horizon.multiplayer.finding_friend": "Encontrando o mundo do seu amigo...",
|
||||
"horizon.multiplayer.not_signed_in": "Este servidor está em modo online. Faça login com sua conta da Microsoft no inicializador do Horizon para ingressar.",
|
||||
"horizon.multiplayer.invalid_address": "Endereço de servidor inválido: %s",
|
||||
"horizon.multiplayer.connected_friend_direct": "Conectado diretamente ao seu amigo...",
|
||||
"horizon.multiplayer.connected_friend_relay": "Conectado ao seu amigo através do relé...",
|
||||
"General": "Geral",
|
||||
"Advanced": "Avançado",
|
||||
"Undo": "Desfazer",
|
||||
"Search options": "Opções de pesquisa",
|
||||
"Shortcut": "Atalho",
|
||||
"Distance": "Distância",
|
||||
"Display": "Exibição",
|
||||
"Sky and weather": "Céu e clima",
|
||||
"Lighting and entities": "Iluminação e entidades",
|
||||
"Textures": "Texturas",
|
||||
"Image": "Imagem",
|
||||
"Chunk loading": "Carregamento de pedaços",
|
||||
"Culling": "Abate",
|
||||
"Power": "Poder",
|
||||
"Frames": "Molduras",
|
||||
"Overlay": "Sobreposição",
|
||||
"Output": "Saída",
|
||||
"Brightness": "Brilho",
|
||||
"How far you see, the window and the HUD.": "Quão longe você vê, a janela e o HUD.",
|
||||
"The sky and weather, lighting, textures and the image.": "O céu e o clima, a iluminação, as texturas e a imagem.",
|
||||
"Chunk loading, culling and power use.": "Carregamento de pedaços, seleção e uso de energia.",
|
||||
"How frames reach the screen, and the performance overlay.": "Como os frames chegam à tela e a sobreposição de desempenho.",
|
||||
"Brighter-than-white output on an HDR display.": "Saída mais brilhante que o branco em uma tela HDR.",
|
||||
"Results for \"%s\"": "Resultados para \"%s\"",
|
||||
"No settings match.": "Nenhuma configuração corresponde.",
|
||||
"horizon.title.not_affiliated": "Não afiliado à Mojang ou Microsoft",
|
||||
"horizon.extras.header.lighting": "Iluminação",
|
||||
"horizon.extras.header.third_person": "Terceira pessoa",
|
||||
"horizon.extras.header.particles": "Partículas",
|
||||
"horizon.extras.header.inventory": "Inventário",
|
||||
"horizon.extras.header.chat": "Bate-papo",
|
||||
"horizon.extras.header.multiplayer": "Multijogador",
|
||||
"horizon.extras.header.sound": "Som",
|
||||
"horizon.extras.header.menus": "Cardápios",
|
||||
"horizon.extras.fullbright": "Totalmente brilhante",
|
||||
"horizon.extras.dynamic_lights": "Luzes dinâmicas",
|
||||
"horizon.extras.own_name_tag": "Crachá próprio",
|
||||
"horizon.extras.hide_block_particles": "Ocultar partículas de quebra de bloco",
|
||||
"horizon.extras.auto_reconnect": "Reconectar automaticamente",
|
||||
"horizon.extras.ui_sound_reserve": "Mantenha canais para sons da IU",
|
||||
"horizon.extras.music_page": "Página de música",
|
||||
"horizon.extras.mouse_tweaks": "Controles de ajustes do mouse",
|
||||
"horizon.extras.mouse_tweaks_wheel": "A roda de rolagem move os itens",
|
||||
"horizon.extras.sort": "Botões de classificação",
|
||||
"horizon.extras.sort_key": "Chave de classificação",
|
||||
"horizon.extras.no_ai": "Sem IA",
|
||||
"horizon.extras.agent_control": "Controle de Agente",
|
||||
"horizon.extras.discord_presence": "Presença rica em discórdia",
|
||||
"horizon.extras.splitscreen": "Tela dividida",
|
||||
"horizon.extras.chat_animation": "Bate-papo tranquilo",
|
||||
"horizon.extras.chat_indicator": "Indicador de mensagem",
|
||||
"horizon.extras.wheel_cycles_settings": "A roda de rolagem altera as configurações",
|
||||
"horizon.extras.pack_browser": "Navegador de pacote de recursos",
|
||||
"horizon.extras.fullbright.tooltip": "Mostre blocos e entidades em plena luz, mesmo em áreas escuras.",
|
||||
"horizon.extras.dynamic_lights.tooltip": "Tochas e outras fontes de luz seguradas, deixadas cair ou em chamas iluminam os blocos ao seu redor.",
|
||||
"horizon.extras.own_name_tag.tooltip": "Mostra seu nome acima de sua cabeça em terceira pessoa, como os outros jogadores o veem. Oculto quando o servidor oculta os nomes da sua equipe ou você fica invisível.",
|
||||
"horizon.extras.hide_block_particles.tooltip": "Esconde as partículas de detritos que explodem de um bloco quebrado e se desintegram daquele que você extraiu. Outras partículas permanecem.",
|
||||
"horizon.extras.auto_reconnect.tooltip": "Volta a um servidor após uma perda de conexão ou reinicialização, nunca após um kick ou ban.",
|
||||
"horizon.extras.ui_sound_reserve.tooltip": "Os sons do mundo deixam 16 dos 247 canais de som livres, então uma enxurrada deles (uma grande explosão) não consegue silenciar os cliques no menu.",
|
||||
"horizon.extras.music_page.tooltip": "Música e Sons recebe uma página Música...: reproduza qualquer faixa integrada agora ou desative uma para que o jogo pare de selecioná-la. Nada muda até que você desligue uma faixa.",
|
||||
"horizon.extras.mouse_tweaks.tooltip": "Arrastar com o botão direito posiciona um por slot, arrastar com a esquerda reúne o item retido, Shift + arrastar com a esquerda move cada slot.",
|
||||
"horizon.extras.mouse_tweaks_wheel.tooltip": "Para baixo envia um item do slot para o outro lado, para cima traz um de volta.",
|
||||
"horizon.extras.sort.tooltip": "Classifica o inventário, baús, barris, baús finais e caixas shulker em ordem de tabulação criativa, mesclando pilhas.",
|
||||
"horizon.extras.sort_key.tooltip": "Classifica a peça sob o cursor. Clique e pressione uma tecla; A fuga o desvincula.",
|
||||
"horizon.extras.no_ai.tooltip": "Oculta e desativa todos os recursos de IA: controle de agente, horizonte-ctl e servidores MCP. O interruptor do lançador também.",
|
||||
"horizon.extras.agent_control.tooltip": "Seu próprio agente de IA pode jogar este jogo por meio do horizonte-ctl ou de um cliente MCP, apenas neste computador.",
|
||||
"horizon.extras.discord_presence.tooltip": "Mostra Jogando Horizon em seu perfil do Discord enquanto o Discord é executado: menus, single player ou multiplayer, a dimensão e o tempo de jogo. Nunca o nome de um mundo ou o endereço de um servidor.",
|
||||
"Calibrate...": "Calibrar...",
|
||||
"Calibrate Anyway...": "Calibrar de qualquer maneira...",
|
||||
"Use Automatic Setup": "Usar configuração automática",
|
||||
"Shortcut: %s": "Atalho: %s",
|
||||
"Next": "Próximo",
|
||||
"HDR Calibration %s/%s: %s": "Calibração HDR %s/%s: %s",
|
||||
"Sets the three below full screen, one test picture at a time.": "Define as três imagens abaixo em tela cheia, uma imagem de teste por vez.",
|
||||
"A key that opens this screen from the game. Not bound at first.": "Uma chave que abre esta tela do jogo. Não vinculado a princípio.",
|
||||
"Your display didn't say how bright it gets: calibrate to set it up.": "Sua tela não informou o quão brilhante ela fica: calibre para configurá-la.",
|
||||
"Already set up for your display. Only calibrate if HDR looks wrong.": "Já configurado para seu display. Calibre apenas se o HDR parecer errado.",
|
||||
"Puts Peak Brightness and Paper White back to your display's setup.": "Coloca o brilho máximo e o branco do papel de volta na configuração do seu monitor.",
|
||||
"Set up for your display. Leave on Auto unless it looks wrong.": "Configure para sua exibição. Deixe em Auto, a menos que pareça errado.",
|
||||
"Lower it until the logo just disappears into the black.": "Abaixe-o até que o logotipo desapareça no preto.",
|
||||
"Raise it until the logo just disappears into the bright square.": "Levante-o até que o logotipo desapareça no quadrado brilhante.",
|
||||
"Make the card a white that's comfortable to look at for a long time, in the light you play in.": "Faça o cartão com um branco que seja confortável de olhar por muito tempo, na luz em que você joga.",
|
||||
"horizon.splitscreen.profiles.forget": "Esqueça",
|
||||
"horizon.splitscreen.profiles.create": "Criar",
|
||||
"horizon.splitscreen.profiles.you": " - você",
|
||||
"horizon.splitscreen.profiles.forget_named": "Esqueça %s",
|
||||
"horizon.splitscreen.profiles.previous": "Anterior",
|
||||
"horizon.splitscreen.profiles.next": "Próximo",
|
||||
"horizon.splitscreen.profiles.new_button": "Novo perfil...",
|
||||
"horizon.splitscreen.profiles.forget_title": "Esqueceu o perfil?",
|
||||
"horizon.splitscreen.profiles.forget_leaves": "Ele sai da lista desta instância.",
|
||||
"horizon.splitscreen.profiles.forget_kept": "O progresso e as configurações mundiais são mantidos.",
|
||||
"horizon.splitscreen.profiles.new_title": "Novo perfil",
|
||||
"horizon.splitscreen.profiles.name_hint": "Nome (3 a 16 letras, dígitos, _)",
|
||||
"horizon.splitscreen.profiles.switch_title": "Mudar perfil",
|
||||
"horizon.splitscreen.profiles.offline_note": "Perfis off-line pertencem a esta instância",
|
||||
"horizon.splitscreen.profiles.move": "Mova-se para onde você joga",
|
||||
"horizon.splitscreen.profiles.move_place": "Mova-se para onde você joga: %s",
|
||||
"horizon.friends.title": "Jogue com amigos",
|
||||
"horizon.friends.hint": "Amigos colam o código após o botão Amigos da tela de título, no Direct Connection ou na página Play do inicializador.",
|
||||
"horizon.friends.copied": "Copiado",
|
||||
"horizon.friends.copy_code": "Copiar código",
|
||||
"horizon.friends.stop_sharing": "Pare de compartilhar",
|
||||
"horizon.friends.none_yet": "Nenhum amigo aqui ainda.",
|
||||
"horizon.friends.here_now": "Aqui agora: %s",
|
||||
"horizon.friends.share_intro": "Compartilhe este mundo por meio de um código de convite: ele abre para LAN e amigos na internet entram com o código. Nenhum servidor nosso está envolvido.",
|
||||
"horizon.friends.start_sharing": "Comece a compartilhar",
|
||||
"horizon.friends.troubleshooting": "Solução de problemas...",
|
||||
"horizon.friends.stopped": "Parou de compartilhar o código de convite.",
|
||||
"horizon.friends.short_code_changed": "Alguém continuou tentando um código curto errado, então ele mudou.",
|
||||
"horizon.friends.wrong_code": "Alguém tentou um código errado.",
|
||||
"horizon.friends.code_ready": "Seu código de convite está pronto: Menu do Jogo, Jogue com Amigos. Copie e envie para seus amigos.",
|
||||
"horizon.friends.share_failed": "Não foi possível compartilhar o mundo: %s",
|
||||
"horizon.friends.finding_route": "Descobrir como os amigos podem entrar em contato com você…",
|
||||
"horizon.osk.shift": "Mudança",
|
||||
"horizon.osk.space": "Espaço",
|
||||
"horizon.osk.delete": "Excluir",
|
||||
"horizon.osk.type": "Tipo",
|
||||
"horizon.osk.symbols": "Símbolos",
|
||||
"horizon.osk.close": "Fechar",
|
||||
"horizon.extras.chat_animation.tooltip": "Uma nova linha de bate-papo aparece da esquerda enquanto desaparece, em vez de aparecer inteira.",
|
||||
"horizon.extras.chat_indicator.tooltip": "A barra colorida à esquerda de uma linha de bate-papo.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "A roda de rolagem sobre um botão nas configurações altera seu valor, como no vanilla. Desligado, a roda apenas rola a lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs obtém um botão Adicionar que pesquisa Modrinth e instala um pacote em sua pasta de pacotes. Desligado, o jogo nunca entra em contato com Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Num mundo, outro controlador pode entrar como novo jogador: pressione A quando solicitado e escolha um perfil e onde jogar.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorar %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "um monitor",
|
||||
"horizon.splitscreen.setup.window": "Janela %s",
|
||||
"horizon.splitscreen.setup.new_window": "Nova janela",
|
||||
"horizon.splitscreen.setup.fill_monitor": "Preencha %s",
|
||||
"horizon.splitscreen.setup.done": "Concluído",
|
||||
"horizon.splitscreen.setup.on": "Ligado",
|
||||
"horizon.splitscreen.setup.off": "Desligado",
|
||||
"horizon.splitscreen.setup.player": "Jogador %s",
|
||||
"horizon.splitscreen.setup.revert": "Reverter",
|
||||
"horizon.splitscreen.setup.apply": "Aplicar",
|
||||
"horizon.splitscreen.setup.warning.empty_main": "Ninguém brinca na Janela 1: ela se esconde",
|
||||
"horizon.splitscreen.setup.warning.empty_close": "Ninguém joga na janela %s: ela fecha no Apply",
|
||||
"horizon.splitscreen.setup.warning.empty_new": "Ninguém joga na janela %s: ela não abre",
|
||||
"horizon.splitscreen.setup.warning.on_main": "A janela %s preenche o monitor da Janela 1: seus jogadores jogam na Janela 1",
|
||||
"horizon.splitscreen.setup.warning.unplugged": "O monitor da janela %s não está conectado: seus jogadores jogam na janela 1",
|
||||
"horizon.splitscreen.setup.status.off": "A tela dividida está desativada.",
|
||||
"horizon.splitscreen.setup.status.enable_hint": "Ligue-o e pressione um botão em outro controlador em um mundo.",
|
||||
"horizon.splitscreen.setup.status.join_hint": "Pressione A para entrar e escolher um perfil.",
|
||||
"horizon.splitscreen.setup.status.idle": "Tela dividida inativa",
|
||||
"horizon.splitscreen.setup.status.alone": "Nenhum outro jogador local.",
|
||||
"horizon.splitscreen.setup.status.solo_controls": "Os controles Solo e o feedback do controlador permanecem inalterados.",
|
||||
"horizon.splitscreen.setup.window_empty": "Janela %s: vazia",
|
||||
"horizon.splitscreen.setup.note.select": "Clique em um player ou arraste-o para uma janela ou monitor.",
|
||||
"horizon.splitscreen.setup.note.apply": "Inscreva-se para jogar desta forma, Reverta para desfazer.",
|
||||
"horizon.splitscreen.setup.note.place": "Onde %s deveria jogar?",
|
||||
"horizon.splitscreen.setup.this_player": "este jogador",
|
||||
"horizon.splitscreen.setup.switch_disabled": "A tela dividida é desativada quando os outros saem (menu de pausa).",
|
||||
"horizon.splitscreen.setup.window_place": "Janela %s (%s de %s)",
|
||||
"horizon.splitscreen.setup.names": "Nomes",
|
||||
"horizon.splitscreen.setup.identify": "Identificar",
|
||||
"horizon.splitscreen.setup.layout.two": "2 compartilhamento: %s",
|
||||
"horizon.splitscreen.setup.layout.three": "3 compartilhamento: %s",
|
||||
"horizon.splitscreen.setup.layout.two.0": "Automático",
|
||||
"horizon.splitscreen.setup.layout.two.1": "Lado a lado",
|
||||
"horizon.splitscreen.setup.layout.two.2": "Superior e inferior",
|
||||
"horizon.splitscreen.setup.layout.three.0": "Um largo no topo",
|
||||
"horizon.splitscreen.setup.layout.three.1": "Um alto à esquerda",
|
||||
"horizon.splitscreen.setup.layout.three.2": "Lado a lado",
|
||||
"horizon.splitscreen.setup.help.player": "Clique ou arraste %s para movê-los.",
|
||||
"horizon.splitscreen.setup.help.layout_auto": "%s (lado a lado quando a janela é larga). Pressione para alterar.",
|
||||
"horizon.splitscreen.setup.help.layout": "%s. Pressione para alterar.",
|
||||
"horizon.splitscreen.setup.help.switch": "Tela dividida para este computador, ligada ou desligada.",
|
||||
"horizon.splitscreen.setup.help.identify": "A visualização de cada jogador mostra em qual janela ele está.",
|
||||
"horizon.splitscreen.setup.help.names": "O nome de cada jogador no canto da visão.",
|
||||
"horizon.splitscreen.setup.help.revert": "Desfaça as alterações ainda não aplicadas.",
|
||||
"horizon.splitscreen.setup.help.apply": "Jogue da maneira que o mapa mostra.",
|
||||
"horizon.splitscreen.setup.help.unchanged": "Nada mudou ainda.",
|
||||
"horizon.splitscreen.setup.give_up_pad": "Almofada de desistência",
|
||||
"horizon.splitscreen.prompt.controller_connected": "Controlador conectado",
|
||||
"horizon.splitscreen.prompt.controller_disconnected": "Controlador desconectado",
|
||||
"horizon.splitscreen.prompt.controller_disconnected.hint": "Reconecte-o ou pressione um botão em outro",
|
||||
"horizon.splitscreen.prompt.join.one": "%s quer participar",
|
||||
"horizon.splitscreen.prompt.join.many": "%s controladores desejam participar",
|
||||
"horizon.splitscreen.prompt.join.yes": "Junte-se",
|
||||
"horizon.splitscreen.prompt.join.as_p1": "Jogue como P1",
|
||||
"horizon.splitscreen.prompt.join.no": "Não",
|
||||
"horizon.splitscreen.prompt.lost": "%s: controlador desconectado",
|
||||
"horizon.splitscreen.prompt.lost.use_another": "Use outro",
|
||||
"horizon.splitscreen.prompt.lost.leave": "Sair",
|
||||
"horizon.splitscreen.setup.help.give_up_pad": "Seu controlador para de dirigir você e é convidado a entrar (A).",
|
||||
"horizon.splitscreen.setup.note.held_at": "%s a %s: A deixa cair aqui, B coloca de volta.",
|
||||
"horizon.splitscreen.setup.note.held": "%s pegou: esquerda/direita escolhe onde, A deixa cair, B coloca de volta.",
|
||||
"Hides models behind opaque blocks. Async checks off-thread; entities tick.": "Oculta modelos atrás de blocos opacos. Async verifica fora do thread; entidades marcam.",
|
||||
"Block Entities": "Bloquear entidades",
|
||||
"Shulker Boxes": "Caixas Shulker",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Reutiliza caixas assentadas; o movimento da tampa permanece animado.",
|
||||
"Signs": "Sinais",
|
||||
"Reuses sign text until text, light, or font changes.": "Reutiliza o texto do sinal até que o texto, a luz ou a fonte sejam alterados.",
|
||||
"Decorated Pots": "Vasos Decorados",
|
||||
"Reuses still pots; break wobble and sherd changes redraw.": "Reaproveita potes imóveis; quebrar oscilação e mudanças de fragmento redesenhar.",
|
||||
"Bells": "Sinos",
|
||||
"Reuses still bells; ringing stays animated.": "Reutiliza ainda sinos; o toque permanece animado.",
|
||||
"Chests": "Baús",
|
||||
"Reuses closed chests; moving lids stay animated.": "Reutiliza baús fechados; as tampas móveis permanecem animadas.",
|
||||
"Banners": "Bandeiras",
|
||||
"Reuses poles; flags and pattern layers still update.": "Reutiliza postes; sinalizadores e camadas de padrão ainda são atualizados.",
|
||||
"Copper Golem Statues": "Estátuas de Golem de Cobre",
|
||||
"Reuses statue geometry until its block state changes.": "Reutiliza a geometria da estátua até que o estado do bloco mude.",
|
||||
"Leaf Culling": "Abate de folhas",
|
||||
"Every leaf face. Vanilla's fancy leaves.": "Cada rosto de folha. Folhas elegantes de baunilha.",
|
||||
"Distant Plant Dither": "Vibração de plantas distantes",
|
||||
"Keeps fine plants in high-res packs from fading far away.": "Evita que plantas finas em embalagens de alta resolução desapareçam ao longe.",
|
||||
"Screenshot Quality": "Qualidade da captura de tela",
|
||||
"F2 screenshots at this many times the game's resolution. Higher takes longer.": "Capturas de tela F2 com muitas vezes a resolução do jogo. Maior leva mais tempo.",
|
||||
"Frame Generation": "Geração de quadros",
|
||||
"Shows a made-up frame between each two real ones: smoother, a little more lag.": "Mostra um quadro composto entre cada dois quadros reais: mais suave, um pouco mais de atraso.",
|
||||
"Shadow Cache": "Cache de sombra",
|
||||
"Reuses the pack shadow map when sun, view and world match. Off by default.": "Reutiliza o mapa de sombras do pacote quando o sol, a visão e o mundo correspondem. Desativado por padrão.",
|
||||
"Find BlueMap": "Encontre o BlueMap",
|
||||
"On servers, finds their BlueMap web map for far terrain. Next join.": "Nos servidores, encontra seu mapa da web BlueMap para terrenos distantes. Próxima adesão.",
|
||||
"BlueMap Radius": "Raio BlueMap",
|
||||
"How far around you BlueMap tiles are downloaded. Next join.": "A que distância ao seu redor os blocos do BlueMap são baixados. Próxima adesão.",
|
||||
"Synchronous": "Síncrono",
|
||||
"Asynchronous": "Assíncrono",
|
||||
"Solid": "Sólido",
|
||||
"Solid Aggressive": "Sólido Agressivo",
|
||||
"Hollow": "Oco",
|
||||
"Leaves fully inside a canopy draw solid: fewer see-through layers.": "As folhas totalmente dentro de um dossel ficam sólidas: menos camadas transparentes.",
|
||||
"Leaves with one open side draw solid too: faster, chunkier edges.": "Folhas com um lado aberto também ficam sólidas: bordas mais rápidas e grossas.",
|
||||
"Drops faces between leaves: faster, but gaps look through.": "Deixa cair as faces entre as folhas: mais rápido, mas as lacunas são visíveis.",
|
||||
"Hotbar: Previous Slot": "Hotbar: slot anterior",
|
||||
"Hotbar: Next Slot": "Hotbar: próximo slot",
|
||||
"horizon.agent.connected.one": "Conectado: 1 conexão de agente",
|
||||
"horizon.agent.connected.many": "Conectado: %s conexões de agente",
|
||||
"horizon.agent.connected.one_ago": "Conectado: 1 conexão de agente, último comando há %s s",
|
||||
"horizon.agent.connected.many_ago": "Conectado: %s conexões de agente, último comando há %s s",
|
||||
"horizon.agent.waiting": "Esperando por um agente.",
|
||||
"horizon.agent.off": "Desativado: os agentes não podem acessar este jogo.",
|
||||
"horizon.agent.session_opened": "Aberto para esta sessão pelo programa que iniciou o jogo.",
|
||||
"horizon.agent.socket": "Socket: %s (copie os detalhes do caminho completo)",
|
||||
"horizon.agent.local_only": "Somente local: agentes neste computador, nunca na rede.",
|
||||
"%s nits": "%s lêndeas",
|
||||
"Auto (%s nits)": "Automático (%s lêndeas)",
|
||||
"%.2f nits": "%.2f lêndeas",
|
||||
"Hides models behind opaque blocks. Entities update. Off with custom packs.": "Oculta modelos atrás de blocos opacos. Atualização de entidades. Saia com pacotes personalizados.",
|
||||
"horizon.extras.repro_keys": "Atalhos de gravação",
|
||||
"horizon.extras.repro_keys.tooltip": "F3+R inicia ou interrompe uma gravação de reprodução e F3+E grava seu pacote. Desativado por padrão: uma gravação copia o mundo.",
|
||||
"horizon.agent.record_session": "Iniciar sessão de gravação",
|
||||
"horizon.agent.record_stop": "Parar a sessão de gravação",
|
||||
"horizon.agent.export_repro": "Exportar pacote de reprodução",
|
||||
"horizon.repro.started": "A gravação começou; F3+R interrompe e grava o pacote.",
|
||||
"horizon.repro.stopped": "Gravação interrompida: %s",
|
||||
"horizon.repro.bundle": "Pacote de reprodução: %s",
|
||||
"horizon.repro.error": "Reprodução: %s",
|
||||
"horizon.repro.no_recording": "Ainda não há gravação para exportar.",
|
||||
"horizon.repro.replay_started": "Repetição iniciada: %s",
|
||||
"horizon.pack.addInfo": "Adicione um pacote de recursos do Modrinth",
|
||||
"horizon.pack.addWorking": "Perguntando a Modrinth...",
|
||||
"horizon.pack.addNone": "Nenhum pacote de recursos encontrado",
|
||||
"horizon.pack.addTitle": "Adicionar um pacote de recursos",
|
||||
"horizon.pack.install": "Instalar",
|
||||
"horizon.pack.installing": "Instalando",
|
||||
"horizon.pack.installed": "Instalado",
|
||||
"horizon.pack.search": "Pesquisar",
|
||||
"horizon.pack.searchHint": "ProcurarModrinth...",
|
||||
"horizon.pack.filters": "Filtros",
|
||||
"horizon.pack.filter.realistic": "Realista",
|
||||
"horizon.pack.filter.simplistic": "Simplista",
|
||||
"horizon.pack.filter.vanillaLike": "Semelhante à baunilha",
|
||||
"horizon.pack.addFailed": "O pacote não pôde ser adicionado: %s",
|
||||
"horizon.extras.header.cosmetics": "Cosméticos",
|
||||
"horizon.extras.cosmetics_mine": "Meus cosméticos",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Oculto: todo jogador do Horizon vê você sem seus cosméticos, inclusive você. A mesma opção do guarda-roupa do lançador.",
|
||||
"horizon.extras.cosmetics_others": "Cosméticos de outros jogadores",
|
||||
"horizon.extras.cosmetics_others.tooltip": "O que os outros jogadores do Horizon usam: tudo, apenas os estilos dos crachás ou nada. Escondido, o jogo nunca pergunta o que eles vestem.",
|
||||
"horizon.extras.cosmetics_auras": "Auras e trilhas de outros",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras e trilhas que outros jogadores usam. Desativado por padrão.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrado",
|
||||
"horizon.extras.cosmetics.hidden": "Oculto",
|
||||
"horizon.extras.cosmetics.name_tags": "Somente tags de nome"
|
||||
}
|
||||
Loaded 100 of 499 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user