From 9d44d14cc9e232f4b6592795d9665b3e3d139095 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 1 Nov 2026 15:46:28 +0100 Subject: [PATCH] galaxer och motion blur --- Content/Galaxies/milky-way.png | Bin 0 -> 186331 bytes Content/Nebulae/hourglass.jpg | Bin 0 -> 8997 bytes KnownGalaxies.json | 25 ++ README.md | 38 ++- config.json | 19 ++ src/Universe.App/Graphics/BenchmarkOverlay.cs | 19 +- src/Universe.App/Graphics/CosmicRenderer.cs | 187 +++++++++++++++ src/Universe.App/Graphics/GraphicsReport.cs | 101 +++++++- src/Universe.App/Graphics/PauseMenu.cs | 15 +- .../Graphics/PerformanceOverlay.cs | 1 + src/Universe.App/Graphics/ShaderProgram.cs | 35 +-- .../Graphics/Shaders/cosmic.frag.glsl | 4 + .../Graphics/Shaders/cosmic.vert.glsl | 23 ++ .../Graphics/Shaders/galaxy.frag.glsl | 62 +++++ .../Graphics/Shaders/sky.frag.glsl | 2 + .../Graphics/Shaders/star-trail.frag.glsl | 17 ++ .../Graphics/Shaders/star-trail.geom.glsl | 58 +++++ .../Graphics/Shaders/star-trail.vert.glsl | 35 +++ .../Graphics/StarTrailRenderer.cs | 57 +++++ src/Universe.App/Graphics/UniverseRenderer.cs | 65 +++-- src/Universe.App/Properties/AssemblyInfo.cs | 2 + src/Universe.App/Runtime/BenchmarkRunner.cs | 6 +- src/Universe.App/Runtime/FlyCamera.cs | 30 ++- .../Runtime/GalaxyJourneyBenchmark.cs | 188 +++++++++++++++ src/Universe.App/Runtime/IBenchmarkSession.cs | 24 ++ src/Universe.App/Runtime/StarMotionHistory.cs | 29 +++ src/Universe.App/Universe.App.csproj | 1 + src/Universe.App/UniverseApplication.cs | 74 +++++- .../Configuration/EngineConfiguration.cs | 6 +- .../Generation/CelestialBodies.cs | 2 + .../Generation/GalaxyUniverse.cs | 225 ++++++++++++++++++ .../Generation/StarMotionBlurSettings.cs | 23 ++ .../Generation/UniverseGenerator.cs | 17 +- src/Universe.Core/World/GalaxyAddress.cs | 28 +++ src/Universe.Core/World/UniverseScale.cs | 1 + src/Universe.Core/World/UniverseStreamer.cs | 26 ++ tests/Universe.Tests/GalaxyTests.cs | 84 +++++++ tests/Universe.Tests/StarMotionTests.cs | 42 ++++ tests/Universe.Tests/Universe.Tests.csproj | 3 + 39 files changed, 1502 insertions(+), 72 deletions(-) create mode 100644 Content/Galaxies/milky-way.png create mode 100644 Content/Nebulae/hourglass.jpg create mode 100644 KnownGalaxies.json create mode 100644 src/Universe.App/Graphics/CosmicRenderer.cs create mode 100644 src/Universe.App/Graphics/Shaders/cosmic.frag.glsl create mode 100644 src/Universe.App/Graphics/Shaders/cosmic.vert.glsl create mode 100644 src/Universe.App/Graphics/Shaders/galaxy.frag.glsl create mode 100644 src/Universe.App/Graphics/Shaders/star-trail.frag.glsl create mode 100644 src/Universe.App/Graphics/Shaders/star-trail.geom.glsl create mode 100644 src/Universe.App/Graphics/Shaders/star-trail.vert.glsl create mode 100644 src/Universe.App/Graphics/StarTrailRenderer.cs create mode 100644 src/Universe.App/Properties/AssemblyInfo.cs create mode 100644 src/Universe.App/Runtime/GalaxyJourneyBenchmark.cs create mode 100644 src/Universe.App/Runtime/IBenchmarkSession.cs create mode 100644 src/Universe.App/Runtime/StarMotionHistory.cs create mode 100644 src/Universe.Core/Generation/GalaxyUniverse.cs create mode 100644 src/Universe.Core/Generation/StarMotionBlurSettings.cs create mode 100644 src/Universe.Core/World/GalaxyAddress.cs create mode 100644 tests/Universe.Tests/GalaxyTests.cs create mode 100644 tests/Universe.Tests/StarMotionTests.cs diff --git a/Content/Galaxies/milky-way.png b/Content/Galaxies/milky-way.png new file mode 100644 index 0000000000000000000000000000000000000000..79151889469237686abbd8683e4d9de4e3e07919 GIT binary patch literal 186331 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∋< zRU6Y^GY}9ozg6!x$RKG$ixTe^OZo(^>nqXSVcYSsT@yDs3rI7u*Ni2y09Y+DG=y)C zCEV^kEICad+8Qtxqe-AAd|lS zTNiY8yvUZOVgtb)iBU1O`?lUoNkW2vm>??7i9uk$wtA(P`0wArj$(_UpP;QvzBMX~ z{46X}>WhYjR;ta3iaO$a@G}QD?F(AANSl4LJsY(`e+{XGq(GGWVFWyZO2=Q`5C3W@ zyU!Hd=~7r5u>J?p6we-qSD!M^}-ndDd zdnU&KK{Mtv?M0!=Co7-kVTp{Dwt(fkXoQKYqtP!-;95PCWkoy=S^YZWG3gFrF)#wyVoV`-T^mt(-Q_xgNxx$1B1dB4&3XfL}8SSw4cZ9 zQf*T)->bERKLK$`bYW2I6geiQ_(UYoe=em;hH(PNQKOVTS9F!bxk?N=e4`Bv$!N1~ zJRZWNU&nG=k}}Pj5=p`zzOkeDcx%0$$Tli8$q0=@s|ppY!3y)?`2%<@!T1Fjo^kI0 z`2NZWGbl>nuS5zT0{;|-GBP5x4D1{Xv5wP;NpKBOT7LD$D!6Q)XRqSUrqCrReG>yQ>!i zwQ8bh6auvMY|l1y-9E84jOu!{tJ1#^PFl2P3qED zD-bhb@iWXd(ksO~ue_u4jXI0(bVihVSDpkXWqQ5eyJ3guI|8Eq+RJtBi6CVLZ{Iu{ zPk=&==bK0f4&xY2W{)Ok(aHhexRvvr76v`ByPq6xvMNDkw9mTlJr*CA+R9u59E1EW zJgPX=bFU+=pMJl|-(K{*1!_YhnquHYT*I4IRQ&i3WIB&M#3!``W3^JM3Va;G2{pty zQiR+)SwKKl(4K`X6C3)I6>)Q9i-E9`d{xCvqkw=>cg)q-l?{fx7zRY8B6f=;C6J;C z`#X`t2)IDW+u+?W04C{}yxr>qCV_MTE@$Xd zC22ciA3nrqR)ug<8=hx`LOEzKiVCph(wHz4?67j}Qi5%R#gi8O4w*Z+5SJShdGO{% zr3&N!Spb3(VijUWqVSWB3YjJwL;lxCP@E9@gp>v}A5KK0fU+0C9F8VxTN3T6_!X5C zS)Ww87ZVqI0LzL?+{4OYce~5}w30;#DDq(ob|tP8$+-Wg*!JUt_jA;q#?ZXZXv<%o zy$TKy)y$iM)0hMjLp&#;!n7Hn;gv`J(|U`ee!%BL4{D~$?m5Aa&wofjA0cx?mQG~) zKA({K+?Zo-zY}SG5r0p|{{+KU4X?hwT=>-iaC*bmOU7{A7(7%)upS!tRf0j<=20lM zDp$QTi}cNuHK9iC`;c9|NZyhHtdSqP+kkM@bk(Kh2-Dl1pG@tfzEM31D!(W7qXvua z+S}z*VoA7+HnKNw89(xUm(CA57g<1YoIr3w;2jtV_fCHDEtDq2e`u)4)PyM*{rjwRJV1k*X${g-lm3pTsX%`AYYRQ~Ng;q8}EwBVkS zb|4I2=C*l+Pc@YNEl$z`-h-*e?|nrJuC#|NR$bZgxl$r#d$As>RH=D(>8@gk%$MiH zf}CFnT~Y5w<9GpY6RKDJ90&LC^ga!=q@I!!L%29;5Xb;)bLEV=BQs55sV$KU2Sv7{ z^-55yLI{9umWl+POUal=C;}c53_n3DWJR&%pp&|jls=ziU>T{e*f5+`K0E8fi#$!Y zGiyg*>i)pTI>ySSr)eWnqZE3v7+`Vy&^*DGP;G5W4d-h{FRE5M$Sax20Y=u#K{sG|h58HKLyRv4^tQ=yFP9tvv4<;JO-d#%P{;AYv$Vvorh@bW~wK z>PxFqDKIW`C4}S5?<|(Do=M<9Fa7lA+aB!yEjA(Gc{-ru81C;mT@H4;_gNP9c4Q;$ z1u(&C-}}f`X}3&SNV011*>)5-DE0=-3Vj(l|HVpCxwt-nNwfjADU$mci-uJ9%QfJ{NKE0F zk13bMt7IbK5xqP_hA_TejL)%0AX09DxTyHKK&50?!@J-157B7pU|}Qo!i(Uqh@a(? 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

1Y&D5gCrqkE-*N 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�vC;}3>*qX5T6JH(e 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®yqVEfhd*YB8WL5~c z#PLVv50X8|5@3=7BiPH=05<4&`?cZ07I`Xr`-M2EayYG}tJKGtiky;?NX9CxY~gg2 zsL4-NsE9RHz>D$H>mNDLn@IiZs9U|mvew<7Q$+0G(&{Jd1SO3t8RIFEuO+9IKEaj? z2m>iI@Twu=RrZ2E!tztGIPzx7^-`rPU2W+mNWf>(7EdujhTUd^}m^eHVCxG&=OgJ@;6{NbMi4FBxe->lZKH+LyE2=(Ht1KsG zxfF$)#x&;bG~gQ=iX)t3y@hTEFa$mfc{)IfN4OGa-;xp0419H^{CpHEeSo>d@(P1( z7GSyVx76`EsVl@e^w2lKeXqL0K^GjDlBmqVF9MsU%_@o3+pP`y-H#v0`zWm(0&V=! z4n(-l;NNlPci>HfsZQo3VR49mu7+|)vzM9C@7brbsqq+$+aOT8=jHu6BZIG9b2}Hr43p3+EHm67XEjlQsnngQbk^j5%dt3XHIntEmVxAxQ~CpnRCBv5+*S-H_>mn- zIVlo|;$_4fUPJnl=&T?VROA)%on1aqaGXKcrSE#b_eGT8r(X%B<5je2qJnm>^obmC zrTJP`7hI=3>mHB3BKL4R9zsx?2Z>;dUlT#OMVrF@Z%5IK<)Wl2YipeW6L8;MoLw&L z`B%FF_IqxIv|L{{XN0@3mnW}n^?zjOsf~g=?2jfUV9$H&zUwpzXpx&p(J+&)3-0+( zH)Ct+jI36+r&WOtmUSpxaN)mGe0BjNeB5bD*>s94;eR)|q^~Pk$WBQq!#HVj)5eedz`7Z9mAhps5}F_I=lcUm^>lGNXC7Sz|q^@Q`7{CN6u zUDZmdlx2?lHsMj2hc1b5mMV1mFJ>*E-=VAZzN4dI#tU*`X_x9tXO#P1HLmuKBJ54? zSmY94X5Ua}@bD-n`clTE?E3s|_=KZdQu1+bgD>)Uu7)PYgIWC4eg;b%J_4eFMJ)s# za++CPTa!Ud)Yx7~m=v5qi5foSNl5#)G%5MFIlCN^J1S2)*rv^d(^g+VyV`{7oFkNz zsDI(fm#6>h-|v1}K|==N9YUNAS%^Yu`p3Ez6@At@uT??z+ER7ZMII+e2Fb@ZmAbx; zjw6l6m2^^w7VN1s%sSk66IR^oVbaj-)9wtu{nn-`vfs!>k(63Jo7$~AJr%Iv)U}G- zry`s;N6%$3_OPqp^)Ow%1LF+%Ixak%s&>TukCjqR!h>`Y>Lrzz_NVYhzTFBm2=>E2Y>+R77la3F(0P(;DcV{baJviPFY$F;9_qhtSe zLT5H+g_8RUG>GPQQDzGS`&UhkO=ZWApqJTCnw94-_r_I4va#<$HDyFiA|J?- zC_i1jj(WDPQh~394USuwKYQUY$gkS_@OOD5HgA#T_q&=vR+l!+{cV~OQ+XWNZ@5%i zRl2YReO?cCpM>Am-VU0AtCub@=Dxvt4`(+NA4S{yyo72E+Uz>|yp9hdQO3%iv0rx> zP+l(h`_C@IUkL}$pau7|4{RM3qU%(~0>N(&t7jYS(sPU7uPiGI=B(6mPAb&DJNq

gDT8&ijV8B#?NxKQXQn^n5+G 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 literal 0 HcmV?d00001 diff --git a/Content/Nebulae/hourglass.jpg b/Content/Nebulae/hourglass.jpg new file mode 100644 index 0000000000000000000000000000000000000000..82094d76a2f47ba760492cf821044a9fcb7f3fdf GIT binary patch literal 8997 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 literal 0 HcmV?d00001 diff --git a/KnownGalaxies.json b/KnownGalaxies.json new file mode 100644 index 0000000..b32a1c3 --- /dev/null +++ b/KnownGalaxies.json @@ -0,0 +1,25 @@ +{ + "galaxies": [ + { + "id": "milky-way", + "name": "Vintergatan", + "region": [0, 0, 0], + "centerOffsetLightYears": [-26000, 0, 0], + "radiusLightYears": 50000, + "thicknessLightYears": 1800, + "axialTiltDegrees": 0, + "rotationDegrees": 0, + "spiralArms": 4, + "densityGamma": 0.7, + "texture": "Content/Galaxies/milky-way.png", + "nebulae": [ + { + "id": "milky-way:hourglass", + "positionLightYears": [26000, 0, 300], + "radiusLightYears": 80, + "texture": "Content/Nebulae/hourglass.jpg" + } + ] + } + ] +} diff --git a/README.md b/README.md index 1156f4c..4132092 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Solen och de åtta planeterna definieras i [KnownSolarSystem/solar-systems.json] | Kontroll | Funktion | | --- | --- | | WASD + mus | Fri flygning och riktning | -| P | Öppna/stäng menyn. Välj Play, Free Camera, Debug, Benchmark Earth, Benchmark Unknown eller Quit med musen eller piltangenter och Enter | +| P | Öppna/stäng menyn. Välj Play, Free Camera, Debug, Benchmark Earth, Benchmark Unknown, Galaxy Journey eller Quit med musen eller piltangenter och Enter | | Space / Ctrl | Upp / ner relativt kameran | -| Mushjul | Logaritmisk hastighet, 0,1 m/s till 1 000 000 c | +| Mushjul | Logaritmisk hastighet, från 0,1 m/s upp till galaktisk FTL-fart (10^15 c) | | Q | Mjuk flygning till närmaste planet | | E | Mjuk flygning till närmaste svarta hål, även från en planets markyta | | 1–9 | Flyg till planeten på motsvarande plats i `startup.planetHotkeys` i `config.json` | @@ -30,6 +30,8 @@ Solen och de åtta planeterna definieras i [KnownSolarSystem/solar-systems.json] | F5 | Ladda om shaders; Debug läser direkt från shaderfilerna | | F6 | Flyg till en solbelyst havsvy nära vattenytan; hastigheten sänks till 10 m/s | | F7 | Flyg till en vy ovanför närmaste molnlager; hastigheten sänks till 100 m/s | +| F8 | Flyg ut till en översiktsvy av den aktuella galaxen | +| F9 | Flyg till närmaste nebulosa i galaxen | | Esc | Stäng pausmenyn, eller avsluta när menyn är stängd | **Play** växlar till förstapersonsläge. WASD går längs marken, musen ser sig omkring, Space hoppar och Ctrl hukar. Spelaren dras mot närmaste fasta planet och orienteras efter dess lokala gravitation. Från stora avstånd används en snabbare inflygning för att landningen ska vara spelbar. **Free Camera** återställer fri flygning. Pausmenyn stoppar rörelsen medan den är öppen. Gasjättar har ingen fast mark och väljs därför inte som landningsmål. @@ -57,11 +59,41 @@ Välj **Debug** i P-menyn för en panel till vänster med asynkron GPU-tid för Detta är en visuell realtidsprototyp. Banorna är statiska och terrängen använder proceduriell relief; ingen hydrologisk erosionssimulering eller atmosfär med multipel spridning beräknas. +## Galaxer och nebulosor + +`KnownGalaxies.json` definierar Vintergatan. Den bifogade galaxbilden finns i `Content/Galaxies/milky-way.png` och används både för utseende och faktisk systemtäthet: luminans × alpha, formad av `densityGamma`, blir sannolikheten att en seedad stjärnkandidat finns. Svarta eller genomskinliga områden får inga kandidater. Solsystemet är alltid fastlagt i sin kända cell. Byt bildsökvägen i `texture`, eller utelämna den för en automatiskt genererad spiralgalax. + +Varje galaxregion är normalt en miljon ljusår bred. Proceduriella galaxer får egna radier, lutningar, spiralarmar och nebulosor från universums seed. `nebulae` i en känd galax kan ange `id`, `positionLightYears` relativt galaxcentrum, `radiusLightYears` och en valfri bild. Systemdata har `GalaxyId` och ett `NebulaId` för närmaste nebulosagrupp; gruppmedlemskapet innebär inte att varje planet ligger inne i det synliga gasmolnet. + +`GalaxyAddress` delar adressen i en galaxregion, en lokal solsystemscell och lokala meter. De två första delarna är heltal. Befintliga `UniversePosition` använder fortfarande det globala heltalsrutnätet, så lokala kameror, terräng och kollision behöver aldrig en enda gigantisk flyttalsposition. Avlägsna objekt omvandlas till kamerarelativa koordinater före GPU-rendering. + +`galaxies` i `config.json` styr regionstorlek, `systemSpacingLightYears`, `galaxyStarBudget`, `localStarBudget` och `volumeSteps`. Budgeterna begränsar representativa stjärnpunkter och volymsteg, inte universums storlek. Varje stjärnpunkt härleds från en verklig seedad systemkandidat. Glesa systemceller mellan stjärnorna är tomma. Närliggande system laddas först på solsystemsnivå; bakgrundspunkter, galaxvolymer och nebulosavolymer ersätter dyr planetgeometri på större skalor. Stjärnmoln beräknas på högst två bakgrundsjobb, högst en färdig punktmesh laddas upp per bildruta, och högst tre detaljerade system hålls kvar på GPU:n. + +Mushjulet kan nu skala fri flyghastighet upp till galaktiska avstånd. F8 och F9 ger översiktsvyer. Vyn växlar mjukt mellan galax-/nebulosapunkter, volymer, individuella systemstjärnor och lokala planeter. + +### Stjärnornas rörelsespår + +Stjärnsprites ritas som mjuka ljusstreck från föregående till aktuell skärmposition. Både kamerans förflyttning, rotation och gradvisa synfältsändring ingår; beräkningen använder kamerarelativa dubbelprecisionspositioner även mellan galaxer. Strecken växer med skärmrörelsen och ger fartkänsla när stjärnor passerar. Stilla stjärnor blir mjuka punkter igen utan kvarhängande efterbilder. Galax-/nebulosamarkörer, planetmeshar och gränssnitt ingår inte i effekten. + +`starMotionBlur` i `config.json` styr `enabled`, `shutterFraction` (hur stor del av bildintervallet som bildar spår), `maximumTrailPixels`, `minimumWidthPixels`, `movingBrightness`, `historyResetSeconds` och `starFadeSeconds`. Standardinställningen är 90 % av bildintervallet och högst 180 bildpunkters spår. Kamerahopp, återställda benchmarkvyer, paus, fönsterstorleksbyte och långa bildintervall nollställer historiken. F5 laddar även om stjärnornas vertex-, geometry- och fragmentshaders. + +Det lokala stjärnurvalet förankras över 32 stjärngridblock och tonas mellan färdiga urval under 0,3 sekunder. Det gamla urvalet visas tills ersättaren är klar. Detta minskar blinkningar vid laddning utöver den jämnare pixelprofilen och rörelsespåren. + +`Universe.App.exe --star-trails-report` kör ett OpenGL-test med syntetiska stjärnor: rörelse måste ge längre streck än stillastående, den konfigurerade maxlängden kontrolleras och stopp/kamerahopp måste ge rena punktbilder. Bilderna sparas som `graphics-report/star-motion-blur.png` och `star-motion-stopped.png`. + +### Galaxresetest + +Välj **Galaxy Journey** i P-menyn. Den fasta 94-sekundersbanan lämnar Jorden, visar Vintergatan, flyger genom tomrummet till galaxregion `(1,0,0)`, går in i ett deterministiskt utvalt solsystem och landar på dess största fasta huvudplanet, 1,8 m över terrängen. Därefter återställs Jordvyn. Båda ändsystemens grova geometri och kartor förbereds innan mätningen börjar. + +Grafen visar FPS över resans tid. Efteråt visas PASS eller FAIL och `benchmark-results/galaxy-journey-.../` innehåller `frames.csv` och `summary.json` med bildrutetider, planet-/system-/galax-id och statistik per fas. En fas måste uppfylla både `minimumBenchmarkFps` för sin 1 %-låga FPS och `maximumBenchmarkFrameMilliseconds` för sin långsammaste bildruta. Standardgränserna är 45 FPS respektive 50 ms. Ett högt medelvärde döljer alltså inte en lång laddningsbildruta. + +Kör `Universe.App.exe --galaxy-journey-report` för ett automatiskt OpenGL-test av samma flöde. Den använder fixerade simuleringssteg och mäter faktisk CPU-/GPU-tid med GPU-synkronisering; det är en accelererad integrationstestkörning utan VSync, inte ett löfte om samma FPS under alla spelinställningar. Bildfiler för galax, nebulosa, planet och resultat hamnar i `graphics-report/`. Processen returnerar kod 2 om prestandagränserna överskrids. Enhetstester kontrollerar koordinatprecision, textur-/alpha-täthet, reproducerbarhet, hierarkiskt medlemskap, markhöjd, hela resan och att injicerade bildrutetoppar ger FAIL. + ## Svarta hål I proceduriella system ersätter ett svart hål stjärnan med 5 % sannolikhet. Samma seed och cell ger samma hål, massa och skivlutning. Kända system, inklusive Solsystemet, behåller sina definierade stjärnor. Planeterna placeras utanför ackretionsskivan och belyses av dess utstrålning. -Tryck **E** för en nio sekunders kameraresa till en sned vy över skivan. Sökningen expanderar cell för cell och jämför verkliga dubbelprecisionsavstånd; den avslutas när närmaste träff är säker, med en maximal sökradie på åtta celler. Kameran lämnar först planetens yta om den behöver det. +Tryck **E** för en nio sekunders kameraresa till en sned vy över skivan. Sökningen jämför dubbelprecisionsavstånd. I galaxläget söker den bland stjärnkandidater inom åtta stjärngridblock (normalt omkring 32 ljusår); utan galaxlagret expanderar den upp till åtta solsystemsceller. Kameran lämnar först planetens yta om den behöver det. `blackHoles` i `config.json` styr `occurrenceChance` (0–1), `minimumMassSolar`, `maximumMassSolar`, `diskOuterRadiusInHorizons`, `diskBrightness` och `raySteps` (128–512). Sätt sannolikheten till 0 för att stänga av genereringen. Standardvärdena ger 200 000–600 000 solmassor, en skiva som sträcker sig till 14 horisontradier och 256 strålsteg. diff --git a/config.json b/config.json index a83580e..faac393 100644 --- a/config.json +++ b/config.json @@ -2,6 +2,25 @@ "universeSeed": "0x554E495645525345", "cellSizeAu": 100, "knownSolarSystemsFile": "KnownSolarSystem/solar-systems.json", + "starMotionBlur": { + "enabled": true, + "shutterFraction": 0.9, + "maximumTrailPixels": 180, + "minimumWidthPixels": 1.4, + "movingBrightness": 1.8, + "historyResetSeconds": 0.25, + "starFadeSeconds": 0.3 + }, + "galaxies": { + "knownGalaxiesFile": "KnownGalaxies.json", + "regionSizeLightYears": 1000000, + "systemSpacingLightYears": 4, + "galaxyStarBudget": 6000, + "localStarBudget": 2600, + "volumeSteps": 40, + "minimumBenchmarkFps": 45, + "maximumBenchmarkFrameMilliseconds": 50 + }, "blackHoles": { "occurrenceChance": 0.05, "minimumMassSolar": 200000, diff --git a/src/Universe.App/Graphics/BenchmarkOverlay.cs b/src/Universe.App/Graphics/BenchmarkOverlay.cs index 47ecb40..3c555e0 100644 --- a/src/Universe.App/Graphics/BenchmarkOverlay.cs +++ b/src/Universe.App/Graphics/BenchmarkOverlay.cs @@ -13,6 +13,8 @@ internal sealed class BenchmarkOverlay : IDisposable private long _lastRevision = -1; private int _lastTextTick = -1; private string _lastPhase = string.Empty; + private IBenchmarkSession? _session; + private (int Width, int Height) _size; public BenchmarkOverlay(GL gl) { @@ -21,8 +23,10 @@ internal sealed class BenchmarkOverlay : IDisposable _curve = new(gl, 6, [], dynamic: true); } - public void Draw(int width, int height, BenchmarkRunner benchmark) + public void Draw(int width, int height, IBenchmarkSession benchmark) { + if (_session != benchmark || _size != (width, height)) + { _session = benchmark; _size = (width, height); _lastRevision = -1; _lastTextTick = -1; _lastPhase = string.Empty; } var scale = System.Math.Clamp(System.Math.Min(width / 1440d, height / 900d), .58, 1.2); var x = width - 402 * scale; var y = 14 * scale; @@ -53,13 +57,16 @@ internal sealed class BenchmarkOverlay : IDisposable _curve.Draw(GLEnum.LineStrip); } - private void UpdatePanel(int width, int height, BenchmarkRunner benchmark, double x, double y, + private void UpdatePanel(int width, int height, IBenchmarkSession benchmark, double x, double y, double panelWidth, double panelHeight, double scale) { var vertices = new List(20_000); Rect(x, y, panelWidth, panelHeight, .018f, .028f, .05f); Rect(x, y, 3 * scale, panelHeight, .13f, .55f, .8f); - Text(benchmark.IsRunning ? "BENCHMARK LIVE" : benchmark.Phase == "STOPPED" ? "BENCHMARK STOPPED" : "BENCHMARK COMPLETE", + var title=benchmark is GalaxyJourneyBenchmark journey + ? journey.IsComplete ? journey.PerformancePassed ? "GALAXY JOURNEY PASS" : "GALAXY JOURNEY FAIL" : journey.IsRunning ? "GALAXY JOURNEY LIVE" : "GALAXY JOURNEY STOPPED" + : benchmark.IsRunning ? "BENCHMARK LIVE" : benchmark.Phase == "STOPPED" ? "BENCHMARK STOPPED" : "BENCHMARK COMPLETE"; + Text(title, x + 16 * scale, y + 11 * scale, 1.75 * scale, .76f, .91f, 1f); Text($"PHASE {benchmark.Phase}", x + 16 * scale, y + 29 * scale, 1.32 * scale, .52f, .7f, .82f); Text($"TARGET {benchmark.TargetLabel}", x + 16 * scale, y + 47 * scale, @@ -74,6 +81,11 @@ internal sealed class BenchmarkOverlay : IDisposable 1.20 * scale, .7f, .83f, .91f); Text($"MIN {Number(benchmark.MinimumFps)} 1 PCT LOW {Number(benchmark.OnePercentLowFps)}", x + 16 * scale, y + 132 * scale, 1.02 * scale, .7f, .83f, .91f); + var maximum=benchmark.GraphSamples.Count==0?60:System.Math.Max(60,System.Math.Ceiling(benchmark.GraphSamples.Max()/30)*30); + Text($"{maximum:0} FPS",x+21*scale,y+154*scale,.95*scale,.4f,.6f,.7f); + Text("0 S",x+17*scale,y+219*scale,.95*scale,.4f,.6f,.7f); + var end=$"{benchmark.ElapsedSeconds:0} S"; + Text(end,x+panelWidth-(end.Length*6*.95+17)*scale,y+219*scale,.95*scale,.4f,.6f,.7f); _panel.ReplaceVertices(vertices.ToArray()); @@ -108,7 +120,6 @@ internal sealed class BenchmarkOverlay : IDisposable var grid = new List(1800); var curve = new List(samples.Count * 6); var maximum = samples.Count == 0 ? 60 : System.Math.Max(60, System.Math.Ceiling(samples.Max() / 30) * 30); - maximum = System.Math.Min(maximum, 240); for (var i = 0; i <= 4; i++) { var py = y + graphHeight * i / 4; diff --git a/src/Universe.App/Graphics/CosmicRenderer.cs b/src/Universe.App/Graphics/CosmicRenderer.cs new file mode 100644 index 0000000..d1db187 --- /dev/null +++ b/src/Universe.App/Graphics/CosmicRenderer.cs @@ -0,0 +1,187 @@ +using System.Numerics; +using Silk.NET.OpenGL; +using Universe.App.Runtime; +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.App.Graphics; + +internal sealed class CosmicRenderer : IDisposable +{ + private sealed class Cloud(UniversePosition center, double scale, PlanetOrientation orientation) + { + public UniversePosition Center = center; + public double Scale = scale; + public PlanetOrientation Orientation = orientation; + public Task? Pending; + public GpuMesh? Mesh; + public int LastFrame; + } + private readonly GL _gl; + private readonly GalaxyUniverse _universe; + private readonly StarTrailRenderer _trails; + private readonly ShaderProgram _volume, _stars; + private readonly GpuMesh _fullscreen; + private readonly GpuMesh _markers; + private readonly Dictionary _clouds = []; + private readonly Dictionary _textures = []; + private readonly uint _white; + private int _frame; + private int _uploads; + private Cloud? _localCurrent, _localPrevious; + private double _localBlend = 1; + private string? _localGalaxyId; + private long _localCutVersion = -1; + public int StarCount { get; private set; } + public CosmicRenderer(GL gl, GalaxyUniverse universe, StarTrailRenderer trails) + { + _gl = gl; _universe = universe; _trails = trails; + _volume = ShaderProgram.Create(gl, ShaderSources.Load("fullscreen.vert"), ShaderSources.Load("galaxy.frag")); + _stars = ShaderProgram.Create(gl, ShaderSources.Load("cosmic.vert"), ShaderSources.Load("cosmic.frag")); + _fullscreen = new(gl, 3, [-1,-1,0,3,-1,0,-1,3,0]); + _markers = new(gl,7,[],dynamic:true); + _white = Texture(new RasterImage(2,2,Enumerable.Repeat((byte)255,16).ToArray())); + } + public void Draw(FlyCamera camera, int width, int height, double frameSeconds) + { + _frame++; _uploads = 0; StarCount = 0; + _gl.Disable(GLEnum.DepthTest); _gl.DepthMask(false); _gl.Enable(GLEnum.Blend); _gl.BlendFunc(GLEnum.One, GLEnum.One); + var galaxies = _universe.VisibleGalaxies(camera.Position).OrderBy(g => camera.Position.RelativeTo(g.Center, camera.CellSizeMeters).LengthSquared).ToArray(); + foreach (var galaxy in galaxies.Take(10)) + { + DrawVolume(camera, width, height, galaxy.Id, galaxy.Center, galaxy.RadiusMeters, galaxy.ThicknessMeters / galaxy.RadiusMeters, + galaxy.Orientation, galaxy.SpiralArms, galaxy.DensityGamma, galaxy.Seed, galaxy.Texture, 0); + if(camera.Position.RelativeTo(galaxy.Center,camera.CellSizeMeters).Length < galaxy.RadiusMeters*6) + { + var cloud = GetCloud(galaxy.Id, galaxy.Center, galaxy.RadiusMeters, galaxy.Orientation, + () => BuildStars(galaxy, null, _universe.Settings.GalaxyStarBudget)); + DrawCloud(camera, width, height, cloud, _universe.SpacingCells * _universe.CellSize * 24 / galaxy.RadiusMeters); + } + } + var current = _universe.GalaxyAt(camera.Position); + if (_localGalaxyId != current.Id || _localCutVersion != camera.MotionHistoryVersion) + { + _localCurrent = null; _localPrevious = null; _localBlend = 1; + _localGalaxyId = current.Id; _localCutVersion = camera.MotionHistoryVersion; + } + var markerScale=_universe.Settings.RegionSizeLightYears*UniverseScale.LightYearMeters; + var markers=new List(256); + void Marker(UniversePosition position,double radius,Vector3 color,bool forcePoint=false) + { + var relative=position.RelativeTo(camera.Position,camera.CellSizeMeters); + var angular=radius/System.Math.Max(radius,relative.Length); + var t=System.Math.Clamp((angular-.004)/.021,0,1);var fade=forcePoint?1:1-t*t*(3-2*t); + if(fade<.01)return; + var p=relative/markerScale; + markers.AddRange([(float)p.X,(float)p.Y,(float)p.Z,color.X*(float)fade,color.Y*(float)fade,color.Z*(float)fade,2.5f]); + } + var volumeIds=galaxies.Take(10).Select(g=>g.Id).ToHashSet(); + foreach(var galaxy in galaxies)Marker(galaxy.Center,galaxy.RadiusMeters,new(.3f,.3f,.5f),!volumeIds.Contains(galaxy.Id)); + var nearNebulae=current.Nebulae.OrderBy(n=>camera.Position.RelativeTo(n.Center,camera.CellSizeMeters).LengthSquared).Take(5).Select(n=>n.Id).ToHashSet(); + foreach(var nebula in current.Nebulae)Marker(nebula.Center,nebula.RadiusMeters,new(.2f,.12f,.26f),!nearNebulae.Contains(nebula.Id)); + _markers.ReplaceVertices(markers.ToArray()); + Camera(_stars,camera,width,height,camera.Position,markerScale,PlanetOrientation.Identity); + _stars.Set("uNearFade",1e-10f);_markers.Draw(GLEnum.Points); + if (camera.Position.RelativeTo(current.Center, camera.CellSizeMeters).Length < current.RadiusMeters * 1.8) + { + var at = _universe.StellarBlock(camera.Position.Sector); + // A stable sampling anchor spans 32 stellar blocks. Crossing every + // individual system grid boundary must not replace the whole sky. + var block = new SectorCoordinate(GalaxyUniverse.FloorDiv(at.X,32)*32, + GalaxyUniverse.FloorDiv(at.Y,32)*32,GalaxyUniverse.FloorDiv(at.Z,32)*32); + var origin = UniversePosition.AtCellCenter(new(block.X * _universe.SpacingCells, block.Y * _universe.SpacingCells, block.Z * _universe.SpacingCells), camera.CellSizeMeters); + var scale = _universe.SpacingCells * _universe.CellSize * 64; + var cloud = GetCloud($"local:{block.X}:{block.Y}:{block.Z}", origin, scale, PlanetOrientation.Identity, + () => BuildStars(current, (origin, scale, block), _universe.Settings.LocalStarBudget)); + if (cloud.Mesh is not null && cloud != _localCurrent) + { + _localPrevious = _localCurrent; _localCurrent = cloud; + _localBlend = 0; + } + _localBlend = System.Math.Min(1,_localBlend+System.Math.Clamp(frameSeconds,0,.1)/_trails.Settings.StarFadeSeconds); + var fadeDistance = _universe.SpacingCells * _universe.CellSize * .15 / scale; + if (_localPrevious is not null && _localBlend < 1) DrawCloud(camera,width,height,_localPrevious,fadeDistance,(float)(1-_localBlend)); + if (_localCurrent is not null) DrawCloud(camera,width,height,_localCurrent,fadeDistance,(float)_localBlend); + if (_localBlend >= 1) _localPrevious = null; + foreach (var n in current.Nebulae.OrderBy(n => camera.Position.RelativeTo(n.Center, camera.CellSizeMeters).LengthSquared).Take(5)) + DrawVolume(camera, width, height, n.Id, n.Center, n.RadiusMeters, 1, n.Orientation, 0, 1, n.Seed, n.Texture, 1); + } + if (_clouds.Count > 48) + foreach (var key in _clouds.Where(p => p.Value != _localCurrent && p.Value != _localPrevious && p.Value.LastFrame < _frame - 2 && p.Value.Pending?.IsCompleted != false).Select(p => p.Key).Take(_clouds.Count - 48).ToArray()) + { _clouds[key].Mesh?.Dispose(); _clouds.Remove(key); } + _gl.Disable(GLEnum.Blend); _gl.Enable(GLEnum.DepthTest); + } + private Cloud GetCloud(string key, UniversePosition center, double scale, PlanetOrientation orientation, Func build) + { + if (!_clouds.TryGetValue(key, out var cloud)) + { cloud = new(center, scale, orientation); _clouds.Add(key, cloud); } + cloud.LastFrame = _frame; + if (cloud.Pending is null && cloud.Mesh is null && _clouds.Values.Count(c => c.Pending?.IsCompleted == false) < 2) + cloud.Pending = Task.Run(build); + if (cloud.Pending is { IsCompletedSuccessfully: true } ready && _uploads < 1) + { cloud.Mesh = new(_gl, 7, ready.Result); cloud.Pending = null; _uploads++; } + return cloud; + } + private float[] BuildStars(GalaxyData galaxy, (UniversePosition Center, double Scale, SectorCoordinate Block)? local, int budget) + { + var random = new DeterministicRandom(galaxy.Seed ^ (local.HasValue ? GalaxyUniverse.Hash(local.Value.Block, 55) : 77)); + var vertices = new List(budget * 7); var used = new HashSet(); + for (var i = 0; i < budget * 24 && used.Count < budget; i++) + { + SectorCoordinate block; + if (local is { } l) block = new(l.Block.X + random.NextInt(-64,65), l.Block.Y + random.NextInt(-64,65), l.Block.Z + random.NextInt(-64,65)); + else + { + var p = new DVector3(random.NextDouble()*2-1,(random.NextDouble()*6-3)*galaxy.ThicknessMeters/galaxy.RadiusMeters,random.NextDouble()*2-1) * galaxy.RadiusMeters; + block = _universe.StellarBlock(galaxy.Center.Add(galaxy.Orientation.ToWorld(p), _universe.CellSize).Sector); + } + if (!_universe.Candidate(galaxy, block, out var sector) || !used.Add(sector)) continue; + var position = UniversePosition.AtCellCenter(sector, _universe.CellSize); + var delta = local is { } ll ? position.RelativeTo(ll.Center, _universe.CellSize) / ll.Scale + : galaxy.Orientation.ToBody(position.RelativeTo(galaxy.Center, _universe.CellSize)) / galaxy.RadiusMeters; + var tint = random.NextDouble(); var brightness = .14 + random.NextDouble() * .9; + vertices.AddRange([(float)delta.X,(float)delta.Y,(float)delta.Z,(float)(brightness*(.7+tint*.3)),(float)(brightness*.82),(float)(brightness*(1.0-tint*.4)),(float)(1.2+random.NextDouble()*2.3)]); + } + return vertices.ToArray(); + } + private void Camera(ShaderProgram shader, FlyCamera camera, int width, int height, UniversePosition center, double scale, PlanetOrientation orientation) + { + shader.Use(); shader.Set("uOrigin", (camera.Position.RelativeTo(center, camera.CellSizeMeters)/scale).ToVector3()); + shader.Set("uAxisX", orientation.ToWorld(new(1,0,0)).ToVector3()); shader.Set("uAxisY", orientation.ToWorld(new(0,1,0)).ToVector3()); shader.Set("uAxisZ", orientation.ToWorld(new(0,0,1)).ToVector3()); + shader.Set("uForward",camera.Forward.ToVector3()); shader.Set("uRight",camera.Right.ToVector3()); shader.Set("uUp",camera.Up.ToVector3()); + shader.Set("uAspect",(float)width/height); shader.Set("uTanHalfFov",(float)System.Math.Tan(camera.FieldOfViewRadians*.5)); + } + private void DrawCloud(FlyCamera camera,int width,int height,Cloud cloud,double nearFade,float visibility=1) + { + if (cloud.Mesh is null) return; + cloud.LastFrame = _frame; + _trails.Draw(cloud.Mesh,cloud.Center,cloud.Scale,cloud.Orientation,nearFade,visibility); + } + private void DrawVolume(FlyCamera camera,int width,int height,string id,UniversePosition center,double radius,double thickness, + PlanetOrientation orientation,int arms,double gamma,ulong seed,RasterImage? texture,int kind) + { + var relative = center.RelativeTo(camera.Position,camera.CellSizeMeters); + if (DVector3.Dot(relative,camera.Forward)<-radius*1.2 || radius / System.Math.Max(radius,relative.Length) < .0003) return; + var angular=radius/System.Math.Max(radius,relative.Length); + var opacity=System.Math.Clamp((angular-.004)/.021,0,1);opacity=opacity*opacity*(3-2*opacity); + if(opacity<=0)return; + Camera(_volume,camera,width,height,center,radius,orientation); + _volume.Set("uOpacity",(float)opacity); + _volume.Set("uThickness",(float)thickness); _volume.Set("uArms",(float)arms); _volume.Set("uGamma",(float)gamma); + _volume.Set("uSeed",(float)(seed&65535)*.001f); _volume.Set("uKind",kind); _volume.Set("uSteps",_universe.Settings.VolumeSteps); + _volume.Set("uHasTexture",texture is null?0:1); _volume.Set("uDensity",0); + _gl.ActiveTexture(GLEnum.Texture0); + if (texture is not null && !_textures.ContainsKey(id)) _textures[id]=Texture(texture); + _gl.BindTexture(GLEnum.Texture2D,texture is null?_white:_textures[id]); _fullscreen.Draw(); + } + private unsafe uint Texture(RasterImage image) + { + var handle=_gl.GenTexture();_gl.BindTexture(GLEnum.Texture2D,handle); + fixed(byte* pixels=image.Rgba) _gl.TexImage2D(GLEnum.Texture2D,0,InternalFormat.Rgba8,(uint)image.Width,(uint)image.Height,0,PixelFormat.Rgba,PixelType.UnsignedByte,pixels); + _gl.TexParameter(GLEnum.Texture2D,GLEnum.TextureMinFilter,(int)GLEnum.Linear);_gl.TexParameter(GLEnum.Texture2D,GLEnum.TextureMagFilter,(int)GLEnum.Linear); + _gl.TexParameter(GLEnum.Texture2D,GLEnum.TextureWrapS,(int)GLEnum.ClampToEdge);_gl.TexParameter(GLEnum.Texture2D,GLEnum.TextureWrapT,(int)GLEnum.ClampToEdge); + return handle; + } + public void Dispose(){foreach(var cloud in _clouds.Values)cloud.Mesh?.Dispose();foreach(var texture in _textures.Values)_gl.DeleteTexture(texture);_gl.DeleteTexture(_white);_volume.Dispose();_stars.Dispose();_fullscreen.Dispose();_markers.Dispose();} +} diff --git a/src/Universe.App/Graphics/GraphicsReport.cs b/src/Universe.App/Graphics/GraphicsReport.cs index 0dd7a18..f5c4000 100644 --- a/src/Universe.App/Graphics/GraphicsReport.cs +++ b/src/Universe.App/Graphics/GraphicsReport.cs @@ -13,6 +13,100 @@ namespace Universe.App.Graphics; ///

Offscreen tests of the production shaders; no desktop capture or mouse control. internal static class GraphicsReport { + public static void RunStarTrails(GL gl,StarMotionBlurSettings settings,int width,int height) + { + using var trails=new StarTrailRenderer(gl,settings with {Enabled=true}); + using var target=new RenderTarget(gl,width,height); + var cell=UniverseScale.DefaultCellSizeMeters;const double scale=1e12; + var center=UniversePosition.AtCellCenter(new(600_000_000_000_000_000,0,0),cell); + var camera=new FlyCamera(center,cell); + var random=new DeterministicRandom(0x5354415254524149UL);var data=new List(); + for(var i=0;i<900;i++) + { + var z=8+random.NextDouble()*52; + data.AddRange([(float)((random.NextDouble()*2-1)*z*.7),(float)((random.NextDouble()*2-1)*z*.43),-(float)z, + 1.1f,(float)(.75+random.NextDouble()*.35),.8f,(float)(1.5+random.NextDouble()*1.5)]); + } + using var stars=new GpuMesh(gl,7,data.ToArray()); + float[] Frame(GpuMesh mesh) + { + target.Bind();gl.Disable(GLEnum.DepthTest);gl.DepthMask(false); + gl.ClearColor(0,0,0,1);gl.Clear(ClearBufferMask.ColorBufferBit); + gl.Enable(GLEnum.Blend);gl.BlendFunc(GLEnum.One,GLEnum.One); + trails.BeginFrame(camera,width,height,1d/60); + trails.Draw(mesh,center,scale,PlanetOrientation.Identity); + gl.Finish();return Read(gl,width,height); + } + Frame(stars); + camera.SetPlayerPose(center.Add(new(0,0,-12*scale),cell),camera.Forward,camera.Up); + var moving=Frame(stars); + var stopped=Frame(stars); + int Coverage(float[] pixels)=>Enumerable.Range(0,width*height).Count(i=>pixels[i*4]>.035); + var movingCoverage=Coverage(moving);var stillCoverage=Coverage(stopped); + if(moving.Any(p=>!float.IsFinite(p))||movingCoverageSystem.Math.Abs(a-b)).Max()>.0001) + throw new InvalidOperationException("Star trails retained an afterimage after stopping or a camera cut."); + using var single=new GpuMesh(gl,7,[0,0,-10,1,1,1,2]); + camera.SetPlayerPose(center,camera.Forward,camera.Up,resetMotionHistory:true);Frame(single); + camera.SetPlayerPose(center.Add(new(-6*scale,0,0),cell),camera.Forward,camera.Up); + var bounded=Frame(single); + var columns=Enumerable.Range(0,width).Where(x=>Enumerable.Range(0,height).Any(y=>bounded[(y*width+x)*4]>.035)).ToArray(); + var span=columns.Length==0?0:columns[^1]-columns[0]+1; + if(spansettings.MaximumTrailPixels+14) + throw new InvalidOperationException($"Star trail pixel limit failed: span {span}, limit {settings.MaximumTrailPixels}."); + if(gl.GetError() is var error&&error!=GLEnum.NoError)throw new InvalidOperationException($"Star trails GL error: {error}"); + var directory=Path.Combine(AppContext.BaseDirectory,"graphics-report");Directory.CreateDirectory(directory); + WritePng(Path.Combine(directory,"star-motion-blur.png"),width,height,moving); + WritePng(Path.Combine(directory,"star-motion-stopped.png"),width,height,stopped); + Console.WriteLine($"Star trails passed: {movingCoverage} moving vs {stillCoverage} stationary pixels; capped trail {span} px; camera cut/stop clean; OpenGL {gl.GetStringS(GLEnum.Renderer)}."); + gl.BindFramebuffer(GLEnum.Framebuffer,0);gl.Disable(GLEnum.Blend);gl.Enable(GLEnum.DepthTest);gl.DepthMask(true); + } + + public static void RunGalaxyJourney(GL gl, UniverseRenderer renderer, FlyCamera camera, UniverseStreamer streamer, + GalaxyJourneyBenchmark journey, int width, int height) + { + var neighborhood = streamer.Load(camera.Position.Sector); + journey.RenderEnvironment = new { Width = width, Height = height, Gpu = renderer.HardwareName, Mode = "synchronized-accelerated", SimulationStepSeconds = 1d/12, FieldOfViewDegrees = camera.FieldOfViewRadians * 180 / System.Math.PI }; + var startPosition=camera.Position;var startForward=camera.Forward;var startUp=camera.Up; + var nebula=journey.HomeGalaxy.Nebulae[0]; + var radial=nebula.Orientation.ToWorld(new DVector3(.1,.05,1).Normalized()); + camera.SetPlayerPose(nebula.Center.Add(radial*nebula.RadiusMeters*2.8,camera.CellSizeMeters),-radial,nebula.Orientation.ToWorld(new(0,1,0)),resetMotionHistory:true); + for(var i=0;i<6;i++){renderer.Render(camera,streamer.Load(camera.Position.Sector),1d/60);gl.Finish();} + var nebulaDirectory=Path.Combine(AppContext.BaseDirectory,"graphics-report");Directory.CreateDirectory(nebulaDirectory); + WritePng(Path.Combine(nebulaDirectory,"galaxy-nebula.png"),width,height,Read(gl,width,height),false); + camera.SetPlayerPose(startPosition,startForward,startUp,resetMotionHistory:true); + neighborhood=streamer.Load(camera.Position.Sector); + for (var i=0;i<8;i++) {renderer.Render(camera,neighborhood,1d/60);gl.Finish();} + var directory = Path.Combine(AppContext.BaseDirectory,"graphics-report");Directory.CreateDirectory(directory); + var captured = new HashSet(); + var timer = System.Diagnostics.Stopwatch.StartNew(); + while(journey.IsRunning) + { + journey.Update(1d/12,camera); + neighborhood=streamer.Load(camera.Position.Sector); + renderer.Render(camera,neighborhood,1d/12);renderer.DrawBenchmarkOverlay(journey);gl.Finish(); + var seconds=timer.Elapsed.TotalSeconds;timer.Restart(); + journey.RecordFrame(seconds); + if(captured.Add(journey.Phase))Console.WriteLine($"Galaxy journey: {journey.Phase}; sector {camera.Position.Sector}; galaxy {neighborhood.ActiveSystem.GalaxyId}"); + // Capture late in each phase, after the point-cloud workers have had time to publish. + if(journey.ElapsedSeconds is >23.8 and <24 || journey.ElapsedSeconds is >44.8 and <45 || journey.ElapsedSeconds is >53.8 and <54 || journey.ElapsedSeconds is >89.8 and <90) + { + renderer.DrawBenchmarkOverlay(journey); + WritePng(Path.Combine(directory,$"galaxy-journey-{(int)journey.ElapsedSeconds}.png"),width,height,Read(gl,width,height),false); + timer.Restart(); + } + } + renderer.DrawBenchmarkOverlay(journey); + WritePng(Path.Combine(directory,"galaxy-journey-results.png"),width,height,Read(gl,width,height),false); + if(gl.GetError() is var error && error!=GLEnum.NoError)throw new InvalidOperationException($"Galaxy journey GL error: {error}"); + var report=journey.SaveReport(); + Console.WriteLine($"Galaxy journey completed; target {journey.TargetGalaxy.Id} / {journey.TargetPlanet.Id}; average {journey.AverageFps:F1} FPS; 1% low {journey.OnePercentLowFps:F1} FPS; performance passed {journey.PerformancePassed}; report {report}"); + if(!journey.PerformancePassed)Environment.ExitCode=2; + } public static void RunBlackHole(GL gl, UniverseRenderer renderer, UniverseNeighborhood neighborhood, StarData hole, double cellSize, int width, int height) { @@ -74,12 +168,7 @@ internal static class GraphicsReport var dir = Path.Combine(AppContext.BaseDirectory, "graphics-report"); Directory.CreateDirectory(dir); WritePng(Path.Combine(dir, "pause-menu.png"), width, height, pixels, false); var scale = System.Math.Min(width / 1440d, height / 900d); - if (renderer.MenuHit(width * .5, height * .5 - 152 * scale, width, height) != 0 - || renderer.MenuHit(width * .5, height * .5 - 95 * scale, width, height) != 1 - || renderer.MenuHit(width * .5, height * .5 - 38 * scale, width, height) != 2 - || renderer.MenuHit(width * .5, height * .5 + 19 * scale, width, height) != 3 - || renderer.MenuHit(width * .5, height * .5 + 76 * scale, width, height) != 4 - || renderer.MenuHit(width * .5, height * .5 + 133 * scale, width, height) != 5 + if (Enumerable.Range(0,7).Any(i => renderer.MenuHit(width*.5,height*.5+(-182+i*57)*scale,width,height)!=i) || renderer.MenuHit(0, 0, width, height) != -1) throw new InvalidOperationException("Menu hit testing failed."); Console.WriteLine($"Pause menu rendered and hit testing passed. Image: {dir}"); diff --git a/src/Universe.App/Graphics/PauseMenu.cs b/src/Universe.App/Graphics/PauseMenu.cs index f6676c1..313e7b0 100644 --- a/src/Universe.App/Graphics/PauseMenu.cs +++ b/src/Universe.App/Graphics/PauseMenu.cs @@ -8,7 +8,7 @@ internal sealed class PauseMenu : IDisposable private readonly GpuMesh _mesh; public int Selected { get; set; } public PauseMenu(GL gl) => _mesh = new(gl, 6, [], dynamic: true); - private static readonly string[] Labels = ["PLAY", "FREE CAMERA", "DEBUG OFF", "BENCHMARK EARTH", "BENCHMARK UNKNOWN", "QUIT"]; + private static readonly string[] Labels = ["PLAY", "FREE CAMERA", "DEBUG OFF", "BENCHMARK EARTH", "BENCHMARK UNKNOWN", "GALAXY JOURNEY", "QUIT"]; internal static readonly Dictionary Font = new() { ['A']=["01110","10001","10001","11111","10001","10001","10001"], ['C']=["01111","10000","10000","10000","10000","10000","01111"], @@ -18,6 +18,7 @@ internal sealed class PauseMenu : IDisposable ['H']=["10001","10001","10001","11111","10001","10001","10001"], ['N']=["10001","11001","10101","10011","10001","10001","10001"], ['V']=["10001","10001","10001","10001","10001","01010","00100"], ['W']=["10001","10001","10001","10101","10101","10101","01010"], ['K']=["10001","10010","10100","11000","10100","10010","10001"], + ['J']=["00111","00010","00010","00010","10010","10010","01100"], ['X']=["10001","10001","01010","00100","01010","10001","10001"], ['L']=["10000","10000","10000","10000","10000","10000","11111"], ['M']=["10001","11011","10101","10101","10001","10001","10001"], ['O']=["01110","10001","10001","10001","10001","10001","01110"], ['P']=["11110","10001","10001","11110","10000","10000","10000"], ['Q']=["01110","10001","10001","10001","10101","10010","01101"], ['R']=["11110","10001","10001","11110","10100","10010","10001"], @@ -44,7 +45,7 @@ internal sealed class PauseMenu : IDisposable var (s, cx, cy) = Layout(width, height); if (s <= 0 || x < cx - 190 * s || x > cx + 190 * s) return -1; for (var i = 0; i < Labels.Length; i++) - if (y >= cy + (-174 + i * 57) * s && y <= cy + (-130 + i * 57) * s) return i; + if (y >= cy + (-204 + i * 57) * s && y <= cy + (-160 + i * 57) * s) return i; return -1; } @@ -70,19 +71,19 @@ internal sealed class PauseMenu : IDisposable x += 6 * unit; } } - Rect(cx - 230*s, cy - 260*s, 460*s, 520*s, .025f, .04f, .075f); - Rect(cx - 230*s, cy - 260*s, 460*s, 4*s, .25f, .65f, .9f); - Text("PAUSED", cx, cy - 226*s, 5*s, .9f, .95f, 1); + Rect(cx - 230*s, cy - 290*s, 460*s, 580*s, .025f, .04f, .075f); + Rect(cx - 230*s, cy - 290*s, 460*s, 4*s, .25f, .65f, .9f); + Text("PAUSED", cx, cy - 256*s, 5*s, .9f, .95f, 1); for (var i = 0; i < Labels.Length; i++) { var selected = i == Selected; - var y = cy + (-174 + i * 57) * s; + var y = cy + (-204 + i * 57) * s; Rect(cx - 190*s, y, 380*s, 44*s, selected ? .12f : .055f, selected ? .25f : .10f, selected ? .36f : .16f); if (selected) Rect(cx - 190*s, y, 4*s, 44*s, .35f, .8f, 1); var label = i == 2 ? debugEnabled ? "DEBUG ON" : "DEBUG OFF" : Labels[i]; Text(label, cx, y + 12*s, (i >= 3 ? 3.35 : 4) * s, .92f, .95f, 1); } - Text("P OR ESC TO RESUME", cx, cy + 220*s, 2*s, .6f, .7f, .8f); + Text("P OR ESC TO RESUME", cx, cy + 250*s, 2*s, .6f, .7f, .8f); _mesh.ReplaceVertices(data.ToArray()); _mesh.Draw(GLEnum.Triangles); } public void Dispose() => _mesh.Dispose(); diff --git a/src/Universe.App/Graphics/PerformanceOverlay.cs b/src/Universe.App/Graphics/PerformanceOverlay.cs index 97f88ac..88bd2f4 100644 --- a/src/Universe.App/Graphics/PerformanceOverlay.cs +++ b/src/Universe.App/Graphics/PerformanceOverlay.cs @@ -16,6 +16,7 @@ internal sealed class PerformanceOverlay : IDisposable var rows = new List<(string Label, string Value, bool Emphasis)>(); AddMetric("GPU FRAME", "GPU FRAME", true); AddMetric("SKY / STARFIELD", "SKY / STARFIELD"); + AddMetric("GALAXIES / NEBULAE", "GALAXIES / NEBULAE"); AddMetric("PLANETS TOTAL", "PLANETS TOTAL"); foreach (var key in values.Keys.Where(k => k.StartsWith("PLANET ", StringComparison.Ordinal) && k != "PLANETS TOTAL" && k != "GPU FRAME") .OrderBy(k => k, StringComparer.Ordinal)) diff --git a/src/Universe.App/Graphics/ShaderProgram.cs b/src/Universe.App/Graphics/ShaderProgram.cs index 563929d..fe95347 100644 --- a/src/Universe.App/Graphics/ShaderProgram.cs +++ b/src/Universe.App/Graphics/ShaderProgram.cs @@ -17,24 +17,29 @@ internal sealed unsafe class ShaderProgram : IDisposable public uint Handle { get; } - public static ShaderProgram Create(GL gl, string vertexSource, string fragmentSource) + public static ShaderProgram Create(GL gl, string vertexSource, string fragmentSource, string? geometrySource = null) { - var vertex = Compile(gl, ShaderType.VertexShader, vertexSource); - var fragment = Compile(gl, ShaderType.FragmentShader, fragmentSource); - var program = gl.CreateProgram(); - gl.AttachShader(program, vertex); - gl.AttachShader(program, fragment); - gl.LinkProgram(program); - gl.GetProgram(program, GLEnum.LinkStatus, out var linked); - gl.DeleteShader(vertex); - gl.DeleteShader(fragment); - if (linked == 0) + uint vertex = 0, fragment = 0, geometry = 0, program = 0; + try { - var log = gl.GetProgramInfoLog(program); - gl.DeleteProgram(program); - throw new InvalidOperationException($"OpenGL shader link failed: {log}"); + vertex = Compile(gl, ShaderType.VertexShader, vertexSource); + fragment = Compile(gl, ShaderType.FragmentShader, fragmentSource); + if (geometrySource is not null) geometry = Compile(gl, ShaderType.GeometryShader, geometrySource); + program = gl.CreateProgram(); + gl.AttachShader(program, vertex); gl.AttachShader(program, fragment); + if (geometry != 0) gl.AttachShader(program, geometry); + gl.LinkProgram(program); + gl.GetProgram(program, GLEnum.LinkStatus, out var linked); + if (linked == 0) throw new InvalidOperationException($"OpenGL shader link failed: {gl.GetProgramInfoLog(program)}"); + return new(gl, program); + } + catch { if (program != 0) gl.DeleteProgram(program); throw; } + finally + { + if (vertex != 0) gl.DeleteShader(vertex); + if (fragment != 0) gl.DeleteShader(fragment); + if (geometry != 0) gl.DeleteShader(geometry); } - return new(gl, program); } public void Use() => _gl.UseProgram(Handle); diff --git a/src/Universe.App/Graphics/Shaders/cosmic.frag.glsl b/src/Universe.App/Graphics/Shaders/cosmic.frag.glsl new file mode 100644 index 0000000..fbb2a8a --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/cosmic.frag.glsl @@ -0,0 +1,4 @@ +#version 330 core +in vec3 vColor; +out vec4 fragColor; +void main(){vec2 p=gl_PointCoord*2.0-1.0;float r=dot(p,p);if(r>1.0)discard;fragColor=vec4(vColor*exp(-r*3.5),0);} diff --git a/src/Universe.App/Graphics/Shaders/cosmic.vert.glsl b/src/Universe.App/Graphics/Shaders/cosmic.vert.glsl new file mode 100644 index 0000000..4858ac0 --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/cosmic.vert.glsl @@ -0,0 +1,23 @@ +#version 330 core +layout(location=0) in vec3 aPosition; +layout(location=1) in vec3 aColor; +layout(location=2) in float aSize; +uniform vec3 uOrigin; +uniform vec3 uAxisX; +uniform vec3 uAxisY; +uniform vec3 uAxisZ; +uniform vec3 uForward; +uniform vec3 uRight; +uniform vec3 uUp; +uniform float uAspect; +uniform float uTanHalfFov; +uniform float uNearFade; +out vec3 vColor; +void main(){ + vec3 delta=uAxisX*aPosition.x+uAxisY*aPosition.y+uAxisZ*aPosition.z-uOrigin; + float d=length(delta),z=dot(delta,uForward); + vec2 xy=vec2(dot(delta,uRight)/uAspect,dot(delta,uUp))/uTanHalfFov; + gl_Position=z>0.0?vec4(xy,z*.999999,z):vec4(2,2,2,1); + gl_PointSize=clamp(aSize,1.0,4.5); + vColor=aColor*smoothstep(uNearFade,uNearFade*2.0,d); +} diff --git a/src/Universe.App/Graphics/Shaders/galaxy.frag.glsl b/src/Universe.App/Graphics/Shaders/galaxy.frag.glsl new file mode 100644 index 0000000..0ee77de --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/galaxy.frag.glsl @@ -0,0 +1,62 @@ +#version 330 core +#include "camera-ray.glsl" +in vec2 vUv; +uniform vec3 uOrigin; +uniform vec3 uAxisX; +uniform vec3 uAxisY; +uniform vec3 uAxisZ; +uniform float uThickness; +uniform float uArms; +uniform float uGamma; +uniform float uSeed; +uniform int uKind; +uniform int uSteps; +uniform int uHasTexture; +uniform sampler2D uDensity; +uniform float uOpacity; +out vec4 fragColor; +float turbulence(vec3 p){return .5+.23*sin(p.x*17.0+sin(p.y*11.0))*sin(p.z*19.0+p.y*7.0)+.12*sin(dot(p,vec3(61,37,83)));} +vec3 local(vec3 p){return vec3(dot(p,uAxisX),dot(p,uAxisY),dot(p,uAxisZ));} +void main(){ + vec3 rd=local(cameraRay(vUv)),ro=local(uOrigin); + vec3 bounds=uKind==0?vec3(1.05,max(.24,uThickness*5.0),1.05):vec3(1.1); + vec3 o=ro/bounds,d=rd/bounds; + float a=dot(d,d),b=dot(o,d),c=dot(o,o)-1.0,disc=b*b-a*c; + if(disc<0.0)discard; + float entry=max(0.0,(-b-sqrt(disc))/a),exit=(-b+sqrt(disc))/a; + if(exit=uSteps)break; + vec3 p=ro+rd*(entry+(float(i)+.5)*ds); + float n=turbulence(p*3.0+uSeed),density=0.0; + vec3 color; + if(uKind==0){ + float radius=length(p.xz); + vec2 uv=p.xz*vec2(.5,-.5)+.5; + vec4 map=texture(uDensity,clamp(uv,vec2(0),vec2(1))); + float spiral=atan(p.z,p.x)*uArms-log(radius+.05)*5.8; + float pattern=uHasTexture==1?dot(map.rgb,vec3(.2126,.7152,.0722))*map.a: + (.05+pow(.5+.5*cos(spiral),5.0)*.8)*pow(max(0.0,1.0-radius),.7)+exp(-radius*radius*65.0)*.8; + float disk=pow(clamp(pattern,0.0,1.0),uGamma)*exp(-abs(p.y)/uThickness)*(1.0-smoothstep(.94,1.0,radius)); + float bulge=exp(-dot(p,p)*100.0)*2.0; + density=disk*(.55+.65*n)+bulge; + color=mix(vec3(.24,.24,.65),vec3(1.6,1.35,.95),clamp(bulge+pattern*.7,0.0,1.0)); + if(uHasTexture==1)color=mix(color,map.rgb*2.1,.55); + emission+=trans*color*density*ds*1.7; + trans*=exp(-disk*(1.0-n)*ds*.7); + }else{ + float shell1=exp(-pow((length((p-vec3(0,.36,0))*vec3(1,1.25,1))-.43)/.055,2.0)); + float shell2=exp(-pow((length((p+vec3(0,.36,0))*vec3(1,1.25,1))-.43)/.055,2.0)); + float torus=exp(-pow((length(p.xz)-.21)/.055,2.0)-p.y*p.y*240.0); + density=(shell1+shell2+torus*2.5)*(.25+n*n); + color=mix(vec3(1.2,.16,.025),vec3(.05,1.1,.68),exp(-dot(p,p)*18.0)); + if(uHasTexture==1){vec4 map=texture(uDensity,clamp(p.xy*vec2(.5,-.5)+.5,vec2(0),vec2(1)));density*=.35+dot(map.rgb,vec3(.33))*map.a;color=mix(color,map.rgb*2.5,.5);} + emission+=trans*color*density*ds*.85; + trans*=exp(-density*ds*.5); + } + } + float localExposure=uKind==0&&length(ro)<1.1?.14:1.0; + fragColor=vec4(emission*uOpacity*localExposure,0); +} diff --git a/src/Universe.App/Graphics/Shaders/sky.frag.glsl b/src/Universe.App/Graphics/Shaders/sky.frag.glsl index 5c6174c..5441594 100644 --- a/src/Universe.App/Graphics/Shaders/sky.frag.glsl +++ b/src/Universe.App/Graphics/Shaders/sky.frag.glsl @@ -11,6 +11,7 @@ uniform vec3 uStarDirections[2]; uniform vec3 uStarColors[2]; uniform float uStarAngles[2]; uniform float uTime; +uniform float uBackgroundStrength; out vec4 fragColor; void main() { vec2 xy=(vUv*2.0-1.0)*uTanHalfFov; @@ -18,6 +19,7 @@ void main() { float band=pow(max(0.0,1.0-abs(dot(rd,normalize(vec3(0.32,0.72,0.48))))),17.0); float dust=smoothstep(-0.26,0.36,fbm(rd*8.0+vec3(4.1,9.2,3.4))); vec3 color=vec3(0.00013,0.00020,0.00042)+vec3(0.0013,0.0016,0.0026)*band*dust; + color*=uBackgroundStrength; for(int i=0;i<2;i++) { if(i>=uLightCount) break; float angle=acos(clamp(dot(rd,uStarDirections[i]),-1.0,1.0)); diff --git a/src/Universe.App/Graphics/Shaders/star-trail.frag.glsl b/src/Universe.App/Graphics/Shaders/star-trail.frag.glsl new file mode 100644 index 0000000..7006a01 --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/star-trail.frag.glsl @@ -0,0 +1,17 @@ +#version 330 core +noperspective in vec2 gPixel; +flat in vec3 gColor; +flat in float gLength; +flat in float gSigma; +out vec4 fragColor; +void main() { + float closest=clamp(gPixel.x,0.0,gLength); + vec2 delta=vec2(gPixel.x-closest,gPixel.y); + float sigma2=gSigma*gSigma+1.0/12.0; + float profile=exp(-dot(delta,delta)/(2.0*sigma2)); + float along=gLength>.5?mix(.32,1.0,smoothstep(0.0,1.0,closest/gLength)):1.0; + float head=exp(-((gPixel.x-gLength)*(gPixel.x-gLength)+gPixel.y*gPixel.y)/(2.0*sigma2)); + float brightness=profile*along+head*.15; + if(brightness<.003)discard; + fragColor=vec4(gColor*brightness,0); +} diff --git a/src/Universe.App/Graphics/Shaders/star-trail.geom.glsl b/src/Universe.App/Graphics/Shaders/star-trail.geom.glsl new file mode 100644 index 0000000..713dd7c --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/star-trail.geom.glsl @@ -0,0 +1,58 @@ +#version 330 core +layout(points) in; +layout(triangle_strip,max_vertices=4) out; +in VS_OUT { vec4 previousClip; vec3 color; float size; } star[]; +uniform vec3 uViewport; +uniform float uShutter; +uniform float uMaximumTrailPixels; +uniform float uMinimumWidthPixels; +uniform float uMovingBrightness; +noperspective out vec2 gPixel; +flat out vec3 gColor; +flat out float gLength; +flat out float gSigma; + +// Clip the swept segment to the viewport before bounding its length. A star +// crossing an edge still leaves a visible trail without a full-screen triangle. +bool clipAxis(float origin,float delta,float bound,inout float lo,inout float hi) { + if(abs(delta)<1e-7)return abs(origin)<=bound; + float a=(-bound-origin)/delta,b=(bound-origin)/delta; + lo=max(lo,min(a,b));hi=min(hi,max(a,b));return lo<=hi; +} +void vertex(vec2 pixel,vec2 uv) { + gl_Position=vec4(pixel/uViewport.xy*2.0,.999999,1); + gPixel=uv;EmitVertex(); +} +void main() { + if(max(max(star[0].color.r,star[0].color.g),star[0].color.b)<.00001)return; + vec4 head=gl_in[0].gl_Position,tail=star[0].previousClip; + if(uShutter<=0.0)tail=head; + const float nearPlane=.00001; + if(head.w<=nearPlane&&tail.w<=nearPlane)return; + if(head.wuMaximumTrailPixels)t=h-(h-t)*(uMaximumTrailPixels/distancePixels); + gLength=length(h-t); + vec2 direction=gLength>.001?(h-t)/gLength:vec2(1,0); + vec2 perpendicular=vec2(-direction.y,direction.x); + // A minimum Gaussian footprint averages subpixel motion instead of toggling + // a one-pixel point on and off as the camera passes it. + gSigma=max(uMinimumWidthPixels/2.0,star[0].size*.3); + float radius=gSigma*3.0; + float moving=1.0-exp(-gLength/14.0); + float boost=mix(1.0,uMovingBrightness,moving); + gColor=star[0].color*.7*boost; + vertex(t-direction*radius-perpendicular*radius,vec2(-radius,-radius)); + vertex(t-direction*radius+perpendicular*radius,vec2(-radius,radius)); + vertex(h+direction*radius-perpendicular*radius,vec2(gLength+radius,-radius)); + vertex(h+direction*radius+perpendicular*radius,vec2(gLength+radius,radius)); + EndPrimitive(); +} diff --git a/src/Universe.App/Graphics/Shaders/star-trail.vert.glsl b/src/Universe.App/Graphics/Shaders/star-trail.vert.glsl new file mode 100644 index 0000000..0423329 --- /dev/null +++ b/src/Universe.App/Graphics/Shaders/star-trail.vert.glsl @@ -0,0 +1,35 @@ +#version 330 core +layout(location=0) in vec3 aPosition; +layout(location=1) in vec3 aColor; +layout(location=2) in float aSize; +uniform vec3 uOrigin; +uniform vec3 uPreviousOrigin; +uniform vec3 uAxisX; +uniform vec3 uAxisY; +uniform vec3 uAxisZ; +uniform vec3 uForward; +uniform vec3 uRight; +uniform vec3 uUp; +uniform vec3 uPreviousForward; +uniform vec3 uPreviousRight; +uniform vec3 uPreviousUp; +uniform float uAspect; +uniform float uTanHalfFov; +uniform float uPreviousAspect; +uniform float uPreviousTanHalfFov; +uniform float uNearFade; +uniform float uVisibility; +out VS_OUT { vec4 previousClip; vec3 color; float size; } star; +vec4 projectStar(vec3 p,vec3 forward,vec3 right,vec3 up,float aspect,float fov) { + float z=dot(p,forward); + return vec4(dot(p,right)/(aspect*fov),dot(p,up)/fov,z*.999999,z); +} +void main() { + vec3 world=uAxisX*aPosition.x+uAxisY*aPosition.y+uAxisZ*aPosition.z; + vec3 current=world-uOrigin; + gl_Position=projectStar(current,uForward,uRight,uUp,uAspect,uTanHalfFov); + star.previousClip=projectStar(world-uPreviousOrigin,uPreviousForward,uPreviousRight,uPreviousUp,uPreviousAspect,uPreviousTanHalfFov); + float visibility=uNearFade>0.0?smoothstep(uNearFade,uNearFade*2.0,length(current)):1.0; + star.color=aColor*visibility*uVisibility; + star.size=aSize; +} diff --git a/src/Universe.App/Graphics/StarTrailRenderer.cs b/src/Universe.App/Graphics/StarTrailRenderer.cs new file mode 100644 index 0000000..45756d2 --- /dev/null +++ b/src/Universe.App/Graphics/StarTrailRenderer.cs @@ -0,0 +1,57 @@ +using Silk.NET.OpenGL; +using Universe.App.Runtime; +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.App.Graphics; + +/// Draws only stellar sprites as swept Gaussian capsules. No scene-color accumulation. +internal sealed class StarTrailRenderer : IDisposable +{ + private readonly GL _gl; + private ShaderProgram _program; + public StarMotionBlurSettings Settings { get; } + public StarMotionHistory History { get; } = new(); + public float Brightness { get; set; } = 1; + private double _cellSize; + + public StarTrailRenderer(GL gl, StarMotionBlurSettings settings) + { + settings.Validate(); _gl = gl; Settings = settings; _program = CreateProgram(); + } + private ShaderProgram CreateProgram() => ShaderProgram.Create(_gl, ShaderSources.Load("star-trail.vert"), + ShaderSources.Load("star-trail.frag"), ShaderSources.Load("star-trail.geom")); + public void ReloadShaders() + { + var replacement = CreateProgram(); _program.Dispose(); _program = replacement; + } + public void BeginFrame(FlyCamera camera, int width, int height, double seconds) + { + _cellSize = camera.CellSizeMeters; + History.Advance(camera, width, height, seconds, Settings); + } + public void Draw(GpuMesh mesh, UniversePosition center, double scale, PlanetOrientation orientation, + double nearFade = 0, float visibility = 1, bool directional = false) + { + var current = History.Current; var previous = History.Previous; + _program.Use(); + _program.Set("uOrigin", directional ? System.Numerics.Vector3.Zero : (current.Position.RelativeTo(center, _cellSize) / scale).ToVector3()); + _program.Set("uPreviousOrigin", directional ? System.Numerics.Vector3.Zero : (previous.Position.RelativeTo(center, _cellSize) / scale).ToVector3()); + _program.Set("uAxisX", orientation.ToWorld(new(1,0,0)).ToVector3()); + _program.Set("uAxisY", orientation.ToWorld(new(0,1,0)).ToVector3()); + _program.Set("uAxisZ", orientation.ToWorld(new(0,0,1)).ToVector3()); + _program.Set("uForward", current.Forward.ToVector3()); _program.Set("uRight", current.Right.ToVector3()); _program.Set("uUp", current.Up.ToVector3()); + _program.Set("uPreviousForward", previous.Forward.ToVector3()); _program.Set("uPreviousRight", previous.Right.ToVector3()); _program.Set("uPreviousUp", previous.Up.ToVector3()); + _program.Set("uAspect", (float)current.Width/current.Height); _program.Set("uTanHalfFov", (float)current.TanHalfFov); + _program.Set("uPreviousAspect", (float)previous.Width/previous.Height); _program.Set("uPreviousTanHalfFov", (float)previous.TanHalfFov); + _program.Set("uNearFade", (float)nearFade); _program.Set("uVisibility", visibility*Brightness); + _program.Set("uViewport", new System.Numerics.Vector3(current.Width,current.Height,0)); + _program.Set("uShutter", History.Valid ? (float)Settings.ShutterFraction : 0); + _program.Set("uMaximumTrailPixels", (float)Settings.MaximumTrailPixels); + _program.Set("uMinimumWidthPixels", (float)Settings.MinimumWidthPixels); + _program.Set("uMovingBrightness", (float)Settings.MovingBrightness); + mesh.Draw(GLEnum.Points); + } + public void Dispose() => _program.Dispose(); +} diff --git a/src/Universe.App/Graphics/UniverseRenderer.cs b/src/Universe.App/Graphics/UniverseRenderer.cs index ade005b..1baa72c 100644 --- a/src/Universe.App/Graphics/UniverseRenderer.cs +++ b/src/Universe.App/Graphics/UniverseRenderer.cs @@ -27,6 +27,9 @@ internal sealed class UniverseRenderer : IDisposable private readonly PerformanceOverlay _performanceOverlay; private readonly BenchmarkOverlay _benchmarkOverlay; private readonly GpuPassProfiler _gpuProfiler; + private readonly CosmicRenderer? _cosmos; + private readonly StarTrailRenderer _starTrails; + private readonly Dictionary _residentSystems = []; private ShaderProgram _surface; private ShaderProgram _star; private ShaderProgram _sky; @@ -77,10 +80,11 @@ internal sealed class UniverseRenderer : IDisposable public bool DebugProbeEnabled { get; set; } public double CpuRenderMilliseconds { get; private set; } public float ExposureCompensation { get; set; } = 1f; + public string HardwareName => _gl.GetStringS(GLEnum.Renderer); internal bool ProfileFrame { get; set; } internal Dictionary FrameTimings { get; } = []; - public UniverseRenderer(GL gl, int width, int height) + public UniverseRenderer(GL gl, int width, int height, GalaxyUniverse? galaxies = null, StarMotionBlurSettings? starMotionBlur = null) { _gl = gl; _noise = new(gl); @@ -96,6 +100,8 @@ internal sealed class UniverseRenderer : IDisposable _performanceOverlay = new(gl); _benchmarkOverlay = new(gl); _gpuProfiler = new(gl); + _starTrails = new(gl, starMotionBlur ?? new()); + if (galaxies is not null) _cosmos = new(gl, galaxies, _starTrails); _surface = Program("terrain.vert", "terrain.frag"); _star = Program("star.vert", "star.frag"); _points = Program("point.vert", "point.frag"); @@ -173,6 +179,7 @@ internal sealed class UniverseRenderer : IDisposable ("fullscreen.vert","gas.frag"),("rock.vert","rock.frag"),("rock-point.vert","rock-point.frag"),("fullscreen.vert","sun-glow.frag"),("fullscreen.vert","ocean.frag"), ("fullscreen.vert","cloud.frag"),("fullscreen.vert","cloud-composite.frag"),("fullscreen.vert","black-hole.frag") }) replacements.Add(Program(pair.Item1, pair.Item2)); + _starTrails.ReloadShaders(); } catch (Exception error) { @@ -196,19 +203,33 @@ internal sealed class UniverseRenderer : IDisposable { if (coordinate == _activeSystem) return; _activeSystem = coordinate; - foreach (var mesh in _planetMeshes.Values) mesh.Dispose(); - _planetMeshes.Clear(); - foreach (var textures in _planetTextures.Values) textures.Dispose(); - foreach (var rings in _ringProfiles.Values) rings.Dispose(); - foreach (var rocks in _ringParticles.Values) rocks.Dispose(); - _planetTextures.Clear(); _ringProfiles.Clear(); _ringParticles.Clear(); - foreach (var texture in _cloudDensityTextures.Values) texture.Dispose(); - _cloudDensityTextures.Clear(); _gpuProfiler.Clear(); _probeFrameCounter = 0; _debugProbePlanetId = null; } + private void RetainSystem(StarSystemData system) + { + if (system.Stars.Count == 0) return; + _residentSystems.Remove(system.Coordinate); _residentSystems.Add(system.Coordinate, system); + if (_residentSystems.Count <= 3) return; + var victim = _residentSystems.First(); _residentSystems.Remove(victim.Key); + foreach (var planet in victim.Value.Planets) + { + if (_planetMeshes.Remove(planet.Id, out var mesh)) mesh.Dispose(); + if (_planetTextures.Remove(planet.Id, out var textures)) textures.Dispose(); + if (_ringProfiles.Remove(planet.Id, out var rings)) rings.Dispose(); + if (_ringParticles.Remove(planet.Id, out var rocks)) rocks.Dispose(); + if (_cloudDensityTextures.Remove(planet.Id, out var clouds)) clouds.Dispose(); + } + } + public void PreparePlanet(StarSystemData system, PlanetData planet) + { + RetainSystem(system); + if (!planet.IsGasPlanet && !_planetMeshes.ContainsKey(planet.Id)) _planetMeshes[planet.Id] = new(_gl, planet); + if (!_planetTextures.ContainsKey(planet.Id)) _planetTextures[planet.Id] = new(_gl, planet); + } + public void Resize(int width, int height) { _width = System.Math.Max(width, 1); @@ -230,6 +251,7 @@ internal sealed class UniverseRenderer : IDisposable public void Render(FlyCamera camera, UniverseNeighborhood neighborhood, double deltaSeconds) { if (_disposed) return; + _starTrails.BeginFrame(camera,_width,_height,deltaSeconds); var cpuTimer = DebugMode ? System.Diagnostics.Stopwatch.StartNew() : null; var timer = ProfileFrame ? System.Diagnostics.Stopwatch.StartNew() : null; if (timer is not null) { _gl.Finish(); timer.Restart(); FrameTimings.Clear(); } @@ -248,6 +270,7 @@ internal sealed class UniverseRenderer : IDisposable var projection = Matrix4x4.CreatePerspectiveFieldOfView((float)camera.FieldOfViewRadians, (float)_width / _height, 0.02f, far); var vp = view * projection; var system = neighborhood.ActiveSystem; + RetainSystem(system); var center = UniversePosition.AtCellCenter(system.Coordinate, camera.CellSizeMeters); var starPositions = system.Stars.Select(s => center.Add(s.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters)).ToArray(); BindNoise(); @@ -262,6 +285,7 @@ internal sealed class UniverseRenderer : IDisposable _sky.Use(); SetCameraRay(_sky, camera); _sky.Set("uNoise", 4); _sky.Set("uTime", (float)_time); + _sky.Set("uBackgroundStrength", _cosmos is null ? 1f : 0f); _sky.Set("uLightCount", system.Stars.Count); _sky.SetVectorArray("uStarDirections", starPositions.Select(p => p.Normalized().ToVector3()).ToArray()); _sky.SetVectorArray("uStarColors", system.Stars.Select(s => s.IsBlackHole ? Vector3.Zero : s.Color).ToArray()); @@ -272,6 +296,10 @@ internal sealed class UniverseRenderer : IDisposable DrawStarfield(camera, neighborhood, vp, far); _gpuProfiler.EndPass(); Mark("sky"); + _gpuProfiler.BeginPass("GALAXIES / NEBULAE"); + _cosmos?.Draw(camera, _width, _height, deltaSeconds); + _gpuProfiler.EndPass(); + Mark("galaxies and nebulae"); _gl.DepthMask(true); _gl.Disable(GLEnum.Blend); _gpuProfiler.BeginPass("PLANETS TOTAL"); @@ -344,7 +372,7 @@ internal sealed class UniverseRenderer : IDisposable _gl.DepthMask(true); _gl.Enable(GLEnum.DepthTest); } - public void DrawBenchmarkOverlay(BenchmarkRunner benchmark) + public void DrawBenchmarkOverlay(IBenchmarkSession benchmark) { _gl.Disable(GLEnum.DepthTest); _gl.Disable(GLEnum.Blend); _gl.DepthMask(false); _overlay.Use(); @@ -381,9 +409,9 @@ internal sealed class UniverseRenderer : IDisposable { _gl.Enable(GLEnum.Blend); _gl.BlendFunc(GLEnum.One, GLEnum.One); - _points.Use(); _points.SetMatrix("uViewProjection", vp); _points.Set("uFar", far); _points.Set("uScale", far * 0.55f); - _points.Set("uBrightness", StarVisibility(camera, neighborhood.ActiveSystem)); - _backgroundStars.Draw(GLEnum.Points); + _starTrails.Brightness = StarVisibility(camera, neighborhood.ActiveSystem); + if (_cosmos is null) _starTrails.Draw(_backgroundStars,camera.Position,1,PlanetOrientation.Identity,directional:true); + var scale = _cosmos is null ? (double)far : UniverseScale.LightYearMeters; var points = new List(26 * 14); foreach (var system in neighborhood.NearbySystems) { @@ -392,12 +420,11 @@ internal sealed class UniverseRenderer : IDisposable { if (star.IsBlackHole) continue; var rel = origin.Add(star.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters); - AddPoint(points, rel, star.Color * 1.3f, 3.2f); + AddPoint(points, rel/scale, star.Color * 1.3f, 3.2f); } } _navigationStars.ReplaceVertices(points.ToArray()); - _points.Set("uScale", 1f); - _navigationStars.Draw(GLEnum.Points); + _starTrails.Draw(_navigationStars,camera.Position,scale,PlanetOrientation.Identity); _gl.Disable(GLEnum.Blend); } @@ -421,6 +448,7 @@ internal sealed class UniverseRenderer : IDisposable if (planet.IsGasPlanet) continue; var relative = relativePlanets[i]; if (DVector3.Dot(relative, camera.Forward) < -planet.MaximumTerrainRadiusMeters) continue; + if (planet.RadiusMeters / System.Math.Max(1,relative.Length) * _height / (2 * System.Math.Tan(camera.FieldOfViewRadians*.5)) < .5) continue; if (!_planetMeshes.TryGetValue(planet.Id, out var mesh)) { mesh = new(_gl, planet); _planetMeshes.Add(planet.Id, mesh); @@ -584,7 +612,8 @@ internal sealed class UniverseRenderer : IDisposable var center = UniversePosition.AtCellCenter(system.Coordinate, camera.CellSizeMeters); var candidates = system.Planets.Where(p => p.IsGasPlanet).Select(p => (Planet: p, Relative: center.Add(p.PositionMeters, camera.CellSizeMeters).RelativeTo(camera.Position, camera.CellSizeMeters))) - .Where(p => DVector3.Dot(p.Relative, camera.Forward) > -p.Planet.RadiusMeters * 1.3) + .Where(p => DVector3.Dot(p.Relative, camera.Forward) > -p.Planet.RadiusMeters * 1.3 + && p.Planet.RadiusMeters / System.Math.Max(1,p.Relative.Length) * _height / (2 * System.Math.Tan(camera.FieldOfViewRadians*.5)) >= .5) .OrderByDescending(p => p.Relative.Length); foreach (var (planet, relative) in candidates) { @@ -1078,6 +1107,8 @@ internal sealed class UniverseRenderer : IDisposable _planetTextures.Clear(); _cloudDensityTextures.Clear(); _ringProfiles.Clear(); _ringParticles.Clear(); _sphere.Dispose(); _fullscreen.Dispose(); _backgroundStars.Dispose(); _navigationStars.Dispose(); _crosshair.Dispose(); _pauseMenu.Dispose(); _performanceOverlay.Dispose(); _gpuProfiler.Dispose(); + _cosmos?.Dispose(); + _starTrails.Dispose(); _surface.Dispose(); _star.Dispose(); _sky.Dispose(); _points.Dispose(); _atmosphere.Dispose(); _bright.Dispose(); _blur.Dispose(); _final.Dispose(); _overlay.Dispose(); _ring.Dispose(); _benchmarkOverlay.Dispose(); _gas.Dispose(); _rock.Dispose(); _rockPoints.Dispose(); _emptyRing.Dispose(); _gasInput.Dispose(); diff --git a/src/Universe.App/Properties/AssemblyInfo.cs b/src/Universe.App/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c91cef7 --- /dev/null +++ b/src/Universe.App/Properties/AssemblyInfo.cs @@ -0,0 +1,2 @@ +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Universe.Tests")] diff --git a/src/Universe.App/Runtime/BenchmarkRunner.cs b/src/Universe.App/Runtime/BenchmarkRunner.cs index 80cbca8..8433f55 100644 --- a/src/Universe.App/Runtime/BenchmarkRunner.cs +++ b/src/Universe.App/Runtime/BenchmarkRunner.cs @@ -6,7 +6,7 @@ using Universe.Core.World; namespace Universe.App.Runtime; /// Repeatable camera path and frame-time capture for a selected planetary terrain scene. -internal sealed class BenchmarkRunner +internal sealed class BenchmarkRunner : IBenchmarkSession { private const double SpaceSeconds = 8; private const double DescentSeconds = 40; @@ -77,7 +77,7 @@ internal sealed class BenchmarkRunner _orbitBitangent = DVector3.Cross(_targetRadial, _orbitTangent).Normalized(); var initialRadius = _planet.RadiusMeters * 4.8; - camera.SetPlayerPose(_body.Add(_spaceRadial * initialRadius, cellSizeMeters), -_spaceRadial, _spaceUp); + camera.SetPlayerPose(_body.Add(_spaceRadial * initialRadius, cellSizeMeters), -_spaceRadial, _spaceUp, resetMotionHistory: true); } public bool IsRunning => !_stopped && _elapsed < TotalSeconds; @@ -169,7 +169,7 @@ internal sealed class BenchmarkRunner _cellSize, cameraRadiusMeters: 1.5, samples: 32); if (_elapsed >= TotalSeconds) { - camera.SetPlayerPose(_returnPosition, _returnForward, _returnUp); + camera.SetPlayerPose(_returnPosition, _returnForward, _returnUp, resetMotionHistory: true); return; } var finalPosition = proposed; diff --git a/src/Universe.App/Runtime/FlyCamera.cs b/src/Universe.App/Runtime/FlyCamera.cs index 72b093a..bef0503 100644 --- a/src/Universe.App/Runtime/FlyCamera.cs +++ b/src/Universe.App/Runtime/FlyCamera.cs @@ -18,11 +18,15 @@ public sealed class FlyCamera { Position = initialPosition; CellSizeMeters = cellSizeMeters; + TravelLimitMeters = cellSizeMeters * .45; SpeedMetersPerSecond = 250_000; } public UniversePosition Position { get; private set; } public double CellSizeMeters { get; } public double SpeedMetersPerSecond { get; private set; } + public double TravelLimitMeters { get; set; } + public long MotionHistoryVersion { get; private set; } + public void RestoreDefaultFieldOfView() => FieldOfViewRadians = _defaultFieldOfView; public bool IsFocusing => _flight is not null; public DVector3 Forward => _forward; public DVector3 Right => DVector3.Cross(_forward, _up).Normalized(); @@ -37,16 +41,18 @@ public sealed class FlyCamera _forward = RotateAround(_forward, axis, -mouseDeltaY * 0.0022).Normalized(); _up = RotateAround(beforePitchUp, axis, -mouseDeltaY * 0.0022).Normalized(); } - public void AdjustSpeed(double delta) => SpeedMetersPerSecond = System.Math.Clamp(SpeedMetersPerSecond * System.Math.Pow(1.7, delta), 0.1, UniverseScale.LightSpeedMetersPerSecond * 1_000_000); + public void AdjustSpeed(double delta) => SpeedMetersPerSecond = System.Math.Clamp(SpeedMetersPerSecond * System.Math.Pow(1.7, delta), 0.1, UniverseScale.LightSpeedMetersPerSecond * 1e15); - public void SetPlayerPose(UniversePosition position, DVector3 forward, DVector3 up) + public void SetPlayerPose(UniversePosition position, DVector3 forward, DVector3 up, bool resetMotionHistory = false) { + if (resetMotionHistory) MotionHistoryVersion++; _flight = null; Position = position; _forward = forward.Normalized(); _up = up.Normalized(); } public void SetInitialView(StarSystemData system) { + MotionHistoryVersion++; var planet = system.Planets[0]; var center = UniversePosition.AtCellCenter(system.Coordinate, CellSizeMeters); var sun = (system.Stars[0].PositionMeters - planet.PositionMeters).Normalized(); @@ -59,6 +65,7 @@ public sealed class FlyCamera public void SetInitialView(StarSystemData system, StartupConfiguration startup) { + MotionHistoryVersion++; var planet = system.Planets.FirstOrDefault(p => p.Id == startup.PlanetId) ?? throw new InvalidDataException($"Startup planet '{startup.PlanetId}' does not exist in system '{system.Id}'."); var longitude = startup.LongitudeDegrees * System.Math.PI / 180; @@ -302,6 +309,22 @@ public sealed class FlyCamera star.RadiusMeters * .08, duration: 9, fieldOfView: 32 * System.Math.PI / 180, departure: departure); } + public void RequestCosmicView(UniversePosition center,double radius,PlanetOrientation orientation,bool nebula,IEnumerable departureSystems) + { + var radial=orientation.ToWorld((nebula?new DVector3(.1,.05,1):new DVector3(.1,1,.3)).Normalized()); + var up=orientation.ToWorld(nebula?new(0,1,0):new DVector3(0,0,-1)); + Departure? departure=null; + var escape=departureSystems.SelectMany(s=>s.Planets.Where(p=>!p.IsGasPlanet).Select(p=>(System:s,Planet:p))) + .MinBy(p=>World(p.System,p.Planet.PositionMeters).RelativeTo(Position,CellSizeMeters).Length/p.Planet.RadiusMeters); + if(escape.Planet is {} solid) + { + var origin=World(escape.System,solid.PositionMeters);var start=Position.RelativeTo(origin,CellSizeMeters); + if(start.Length UniversePosition.AtCellCenter(system.Coordinate, CellSizeMeters).Add(offset, CellSizeMeters); private void BeginFlight(UniversePosition body, DVector3 radial, double finalRadius, double approachRadius, DVector3 forward, DVector3 up, double speed, @@ -361,7 +384,8 @@ public sealed class FlyCamera return; } if (movement.LengthSquared < 1e-12) return; - var distance = System.Math.Min(SpeedMetersPerSecond * System.Math.Min(deltaSeconds, 0.1), CellSizeMeters * 0.45); + var nearSurface = systems.Any(s => s.Planets.Any(p => Position.RelativeTo(World(s, p.PositionMeters), CellSizeMeters).Length < p.RadiusMeters * 10)); + var distance = System.Math.Min(SpeedMetersPerSecond * System.Math.Min(deltaSeconds, 0.1), nearSurface ? CellSizeMeters * .45 : TravelLimitMeters); var end = Position.Add(movement.Normalized() * distance, CellSizeMeters); Position = SweepNeighborhood(Position, end, systems).Position; } diff --git a/src/Universe.App/Runtime/GalaxyJourneyBenchmark.cs b/src/Universe.App/Runtime/GalaxyJourneyBenchmark.cs new file mode 100644 index 0000000..c1c1fde --- /dev/null +++ b/src/Universe.App/Runtime/GalaxyJourneyBenchmark.cs @@ -0,0 +1,188 @@ +using System.Globalization; +using System.Text; +using System.Text.Json; +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.App.Runtime; + +internal sealed class GalaxyJourneyBenchmark : IBenchmarkSession +{ + private readonly GalaxyUniverse _galaxies; + private readonly UniversePosition _home, _homeBody, _milkyView, _otherView, _targetBody; + private readonly DVector3 _homeForward, _homeUp, _homeRadial, _targetRadial, _targetHeading; + private readonly double _homeRadius; + private readonly List<(double Time, double Milliseconds, string Phase)> _frames = []; + private readonly List _graph = []; + private readonly HashSet _visited = []; + private double _elapsed, _sampleTime, _totalFrameTime; + private double _lastSampleSequenceTime; + private int _sampleFrames; + private bool _stopped, _finalRecorded; + public GalaxyData HomeGalaxy { get; } + public GalaxyData TargetGalaxy { get; } + public StarSystemData TargetSystem { get; } + public PlanetData TargetPlanet { get; } + public string? ReportDirectory { get; private set; } + public object? RenderEnvironment { get; set; } + + public GalaxyJourneyBenchmark(GalaxyUniverse galaxies, UniverseGenerator generator, StarSystemData homeSystem, PlanetData earth, + UniversePosition home, DVector3 homeForward, DVector3 homeUp) + { + _galaxies = galaxies; _home = home; _homeForward = homeForward; _homeUp = homeUp; + _homeBody = UniversePosition.AtCellCenter(homeSystem.Coordinate, galaxies.CellSize).Add(earth.PositionMeters, galaxies.CellSize); + _homeRadial = home.RelativeTo(_homeBody, galaxies.CellSize).Normalized(); + _homeRadius = home.RelativeTo(_homeBody, galaxies.CellSize).Length; + HomeGalaxy = galaxies.GalaxyAt(home); + var region = HomeGalaxy.Region; + TargetGalaxy = galaxies.Get(new(checked(region.X + 1), region.Y, region.Z)); + TargetSystem = generator.Generate(galaxies.SelectSystem(TargetGalaxy)); + TargetPlanet = TargetSystem.Planets.Where(p => !p.IsGasPlanet && p.ParentPlanetIndex is null).OrderByDescending(p => p.RadiusMeters).First(); + _targetBody = UniversePosition.AtCellCenter(TargetSystem.Coordinate, galaxies.CellSize).Add(TargetPlanet.PositionMeters, galaxies.CellSize); + // Exercise the illuminated terrain/shadow path, rather than accidentally + // benchmarking the cheap night-side shading branch. + var sun=(TargetSystem.Stars[TargetPlanet.HostStarIndex].PositionMeters-TargetPlanet.PositionMeters).Normalized(); + var side=DVector3.Cross(sun,System.Math.Abs(sun.Y)<.9?new(0,1,0):new DVector3(1,0,0)).Normalized(); + _targetRadial=(sun*.6+side*.8).Normalized(); + _targetHeading=DVector3.Cross(_targetRadial,sun).Normalized(); + _milkyView = HomeGalaxy.Center.Add(HomeGalaxy.Orientation.ToWorld(new DVector3(.1,1,.3).Normalized()) * HomeGalaxy.RadiusMeters * 2.5, galaxies.CellSize); + _otherView = TargetGalaxy.Center.Add(TargetGalaxy.Orientation.ToWorld(new DVector3(.3,.8,1).Normalized()) * TargetGalaxy.RadiusMeters * 2.5, galaxies.CellSize); + } + public bool IsRunning => !_stopped && _elapsed < DurationSeconds; + public bool IsComplete => !_stopped && _elapsed >= DurationSeconds; + public bool HasRun => true; + public double ElapsedSeconds => _elapsed; + public double DurationSeconds => 94; + public long GraphRevision { get; private set; } + public IReadOnlyList GraphSamples => _graph; + public string TargetLabel => TargetPlanet.Name.ToUpperInvariant(); + public double TargetEarthRadii => TargetPlanet.RadiusMeters / UniverseScale.EarthRadiusMeters; + public double CurrentFps { get; private set; } + public double AverageFps => _totalFrameTime <= 0 ? 0 : _frames.Count / _totalFrameTime; + public double MinimumFps => _frames.Count == 0 ? 0 : 1000 / _frames.Max(f => f.Milliseconds); + public double OnePercentLowFps => Low(_frames.Select(f => f.Milliseconds).ToArray()); + public string Phase => _stopped ? "STOPPED" : IsComplete ? "COMPLETE" : PhaseAt(_elapsed); + public IReadOnlyCollection VisitedPhases => _visited; + private static string PhaseAt(double t) => t < 18 ? "LEAVE EARTH" : t < 25 ? "MILKY WAY" : t < 40 ? "BETWEEN GALAXIES" + : t < 55 ? "ENTER GALAXY" : t < 65 ? "TARGET SYSTEM" : t < 85 ? "LANDING" : "GROUND WALK"; + public void Stop() => _stopped = true; + public void Update(double seconds, FlyCamera camera) + { + if (!IsRunning || !double.IsFinite(seconds) || seconds <= 0) return; + _elapsed = System.Math.Min(DurationSeconds, _elapsed + seconds); + _visited.Add(PhaseAt(System.Math.Min(_elapsed, DurationSeconds - .001))); + var cell = _galaxies.CellSize; + UniversePosition position; DVector3 forward; DVector3 up = _homeUp; + if (_elapsed < 18) + { + var t = Smooth(_elapsed / 18); + var viewRadial = _milkyView.RelativeTo(_homeBody,cell).Normalized(); + var radius = LogLerp(_homeRadius, _milkyView.RelativeTo(_homeBody,cell).Length, t); + var direction = DVector3.Lerp(_homeRadial,viewRadial,Smooth(System.Math.Clamp((t-.6)/.4,0,1))).Normalized(); + position = _homeBody.Add(direction * radius, cell); + var focus = System.Math.Clamp((System.Math.Log(System.Math.Max(radius,1)) - System.Math.Log(UniverseScale.LightYearMeters)) / 12, 0, 1); + forward = DVector3.Lerp(_homeForward, HomeGalaxy.Center.RelativeTo(position, cell).Normalized(), Smooth(focus)).Normalized(); + } + else if (_elapsed < 25) + { + position = _milkyView; + forward = HomeGalaxy.Center.RelativeTo(position, cell).Normalized(); + } + else if (_elapsed < 40) + { + var t = Smooth((_elapsed - 25) / 15); + position = Interpolate(_milkyView, _otherView, t, cell); + var from = HomeGalaxy.Center.RelativeTo(_milkyView, cell).Normalized(); + var to = _targetBody.RelativeTo(_otherView, cell).Normalized(); + forward = DVector3.Lerp(from, to, t).Normalized(); + } + else if (_elapsed < 55) + { + var t = Smooth((_elapsed - 40) / 15); + var radial = _otherView.RelativeTo(_targetBody, cell).Normalized(); + var radius = LogLerp(_otherView.RelativeTo(_targetBody, cell).Length, TargetPlanet.RadiusMeters * 4.8, t); + // Rotate at galaxy distances; keep the final solar approach radial and precise. + var direction = DVector3.Lerp(radial, _targetRadial, Smooth(System.Math.Min(t * 3, 1))).Normalized(); + position = _targetBody.Add(direction * radius, cell); + forward = _targetBody.RelativeTo(position, cell).Normalized(); + up = new(0,1,0); + } + else if (_elapsed < 65) + { + position = _targetBody.Add(_targetRadial * TargetPlanet.RadiusMeters * 4.8, cell); + forward = -_targetRadial; up = new(0,1,0); + } + else + { + var t = System.Math.Clamp((_elapsed - 65) / 20, 0, 1); + var surface = TerrainHeight.Sample(TargetPlanet, _targetRadial); + var radius = t < .5 ? TargetPlanet.RadiusMeters * 4.8 + (surface + 10001.8 - TargetPlanet.RadiusMeters * 4.8) * Smooth(t * 2) + : surface + 1.8 + 10000 * (1 - Smooth((t - .5) * 2)); + var radial = _targetRadial; + if (_elapsed >= 85) + { + var walk = (_elapsed - 85) / 9; + radial = (_targetRadial + _targetHeading * (walk * 12 / TargetPlanet.RadiusMeters)).Normalized(); + radius = TerrainHeight.Sample(TargetPlanet, radial) + 1.8; + } + position = _targetBody.Add(radial * radius, cell); + var landingLook = (_targetHeading * System.Math.Cos(.2) - radial * System.Math.Sin(.2)).Normalized(); + forward = DVector3.Lerp(-radial, landingLook, Smooth(System.Math.Clamp((t - .4) / .6,0,1))).Normalized(); + if (_elapsed >= 85) + { + var yaw = System.Math.Sin((_elapsed - 85) * .7) * .6; + var right = DVector3.Cross(_targetHeading, radial).Normalized(); + forward = (_targetHeading * System.Math.Cos(yaw) + right * System.Math.Sin(yaw) - radial * .15).Normalized(); + } + up = radial; + } + if (_elapsed >= DurationSeconds) { position = _home; forward = _homeForward; up = _homeUp; } + up = (up - forward * DVector3.Dot(up,forward)).Normalized(); + if (up.LengthSquared < .1) up = DVector3.Cross(forward,new(1,0,0)).Normalized(); + camera.SetPlayerPose(position, forward, up, resetMotionHistory: IsComplete); + } + public void RecordFrame(double seconds) + { + if (_stopped || _finalRecorded || !double.IsFinite(seconds) || seconds <= 0) return; + CurrentFps = 1 / seconds; _totalFrameTime += seconds; + _frames.Add((_elapsed, seconds * 1000, PhaseAt(System.Math.Min(_elapsed,DurationSeconds - .001)))); + _sampleTime += seconds; _sampleFrames++; + if (_elapsed - _lastSampleSequenceTime >= .2 || IsComplete) + { + _graph.Add(_sampleFrames / _sampleTime); _sampleTime = 0; _sampleFrames = 0; _lastSampleSequenceTime = _elapsed; GraphRevision++; + } + if (IsComplete) _finalRecorded = true; + } + public object[] PhaseResults() => _frames.GroupBy(f => f.Phase).Select(group => + { + var values = group.Select(f => f.Milliseconds).Order().ToArray(); + var p99 = values[System.Math.Min(values.Length - 1,(int)System.Math.Ceiling(values.Length * .99) - 1)]; + return (object)new { Phase = group.Key, Frames = values.Length, AverageFps = 1000 / values.Average(), LowOnePercentFps = Low(values), + P99Milliseconds = p99, MaximumMilliseconds = values.Max(), + Passed = Low(values) >= _galaxies.Settings.MinimumBenchmarkFps && values.Max() <= _galaxies.Settings.MaximumBenchmarkFrameMilliseconds }; + }).ToArray(); + public bool PerformancePassed => IsComplete && _frames.Count > 0 && _frames.GroupBy(f => f.Phase).All(g => + Low(g.Select(f => f.Milliseconds).ToArray()) >= _galaxies.Settings.MinimumBenchmarkFps + && g.Max(f => f.Milliseconds) <= _galaxies.Settings.MaximumBenchmarkFrameMilliseconds); + public string SaveReport(string? root = null) + { + if (ReportDirectory is not null) return ReportDirectory; + var directory = Path.Combine(root ?? Path.Combine(AppContext.BaseDirectory,"benchmark-results"),"galaxy-journey-"+DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")); + Directory.CreateDirectory(directory); + var csv = new StringBuilder("timeSeconds,phase,frameMilliseconds,fps\n"); + foreach (var f in _frames) csv.AppendLine(FormattableString.Invariant($"{f.Time:F4},{f.Phase},{f.Milliseconds:F4},{1000/f.Milliseconds:F4}")); + File.WriteAllText(Path.Combine(directory,"frames.csv"),csv.ToString()); + File.WriteAllText(Path.Combine(directory,"summary.json"),JsonSerializer.Serialize(new { Completed = IsComplete, PerformancePassed, + UniverseSeed = _galaxies.Seed.ToString("X16"), RenderEnvironment, + Quality = new { _galaxies.Settings.GalaxyStarBudget, _galaxies.Settings.LocalStarBudget, _galaxies.Settings.VolumeSteps, _galaxies.Settings.SystemSpacingLightYears }, + TargetGalaxy = TargetGalaxy.Id, TargetSystem = TargetSystem.Id, TargetPlanet = TargetPlanet.Id, + MinimumFps = _galaxies.Settings.MinimumBenchmarkFps, MaximumFrameMilliseconds = _galaxies.Settings.MaximumBenchmarkFrameMilliseconds, + Visited = _visited, Phases = PhaseResults() },new JsonSerializerOptions { WriteIndented = true })); + ReportDirectory = directory; return directory; + } + private static double Low(double[] milliseconds) => milliseconds.Length == 0 ? 0 : 1000 / milliseconds.OrderDescending().Take(System.Math.Max(1,(int)System.Math.Ceiling(milliseconds.Length * .01))).Average(); + private static double Smooth(double t) { t=System.Math.Clamp(t,0,1);return t*t*t*(t*(t*6-15)+10); } + private static double LogLerp(double a,double b,double t) => System.Math.Exp(System.Math.Log(a)*(1-t)+System.Math.Log(b)*t); + private static UniversePosition Interpolate(UniversePosition a,UniversePosition b,double t,double cell) => t<.5?a.Add(b.RelativeTo(a,cell)*t,cell):b.Add(a.RelativeTo(b,cell)*(1-t),cell); +} diff --git a/src/Universe.App/Runtime/IBenchmarkSession.cs b/src/Universe.App/Runtime/IBenchmarkSession.cs new file mode 100644 index 0000000..7419a6d --- /dev/null +++ b/src/Universe.App/Runtime/IBenchmarkSession.cs @@ -0,0 +1,24 @@ +using Universe.Core.Generation; + +namespace Universe.App.Runtime; + +internal interface IBenchmarkSession +{ + bool IsRunning { get; } + bool IsComplete { get; } + bool HasRun { get; } + double ElapsedSeconds { get; } + double DurationSeconds { get; } + long GraphRevision { get; } + IReadOnlyList GraphSamples { get; } + string TargetLabel { get; } + double TargetEarthRadii { get; } + double CurrentFps { get; } + double AverageFps { get; } + double MinimumFps { get; } + double OnePercentLowFps { get; } + string Phase { get; } + void Stop(); + void Update(double seconds, FlyCamera camera); + void RecordFrame(double seconds); +} diff --git a/src/Universe.App/Runtime/StarMotionHistory.cs b/src/Universe.App/Runtime/StarMotionHistory.cs new file mode 100644 index 0000000..94ac031 --- /dev/null +++ b/src/Universe.App/Runtime/StarMotionHistory.cs @@ -0,0 +1,29 @@ +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.App.Runtime; + +internal readonly record struct StarCameraPose(UniversePosition Position, DVector3 Forward, DVector3 Right, DVector3 Up, + double TanHalfFov, int Width, int Height, long CutVersion); + +internal sealed class StarMotionHistory +{ + private bool _initialized; + public StarCameraPose Current { get; private set; } + public StarCameraPose Previous { get; private set; } + public bool Valid { get; private set; } + + public void Advance(FlyCamera camera, int width, int height, double frameSeconds, StarMotionBlurSettings settings) + { + var pose = new StarCameraPose(camera.Position, camera.Forward, camera.Right, camera.Up, + System.Math.Tan(camera.FieldOfViewRadians * .5), width, height, camera.MotionHistoryVersion); + Previous = Current; + Valid = _initialized && settings.Enabled && double.IsFinite(frameSeconds) && frameSeconds > 0 + && frameSeconds <= settings.HistoryResetSeconds && Previous.Width == width && Previous.Height == height + && Previous.CutVersion == pose.CutVersion && System.Math.Abs(Previous.TanHalfFov / pose.TanHalfFov - 1) < .15; + Current = pose; + if (!Valid) Previous = Current; + _initialized = true; + } +} diff --git a/src/Universe.App/Universe.App.csproj b/src/Universe.App/Universe.App.csproj index 8bea2c9..7c86055 100644 --- a/src/Universe.App/Universe.App.csproj +++ b/src/Universe.App/Universe.App.csproj @@ -4,6 +4,7 @@ + diff --git a/src/Universe.App/UniverseApplication.cs b/src/Universe.App/UniverseApplication.cs index cf0395f..0d4e525 100644 --- a/src/Universe.App/UniverseApplication.cs +++ b/src/Universe.App/UniverseApplication.cs @@ -18,6 +18,8 @@ public sealed class UniverseApplication : IDisposable private readonly IWindow _window; private readonly UniverseGenerator _generator; private readonly UniverseStreamer _streamer; + private readonly GalaxyUniverse _galaxies; + private readonly StarMotionBlurSettings _starMotionBlur; private readonly IReadOnlyList _planetHotkeys; private readonly StarSystemData _homeSystem; private readonly PlanetData _homeEarth; @@ -26,7 +28,8 @@ public sealed class UniverseApplication : IDisposable private readonly DVector3 _homeCameraUp; private FlyCamera _camera; private PlayerController? _player; - private BenchmarkRunner? _benchmark; + private IBenchmarkSession? _benchmark; + private bool _benchmarkNeedsInitialFrame; private bool _menuOpen; private bool _debugMode; private int _menuSelection; @@ -49,19 +52,23 @@ public sealed class UniverseApplication : IDisposable { var configPath = Path.Combine(AppContext.BaseDirectory, "config.json"); var configuration = EngineConfiguration.Load(configPath); + _starMotionBlur = configuration.StarMotionBlur; _planetHotkeys = configuration.Startup.PlanetHotkeys; var catalogPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(configPath)!, configuration.KnownSolarSystemsFile)); var catalog = KnownSolarSystemCatalog.Load(catalogPath, configuration.CellSizeMeters); - _generator = new(configuration.Seed, configuration.CellSizeMeters, catalog, configuration.BlackHoles); + _galaxies = new(configuration.Seed, configuration.CellSizeMeters, configuration.Galaxies, + Path.GetFullPath(Path.Combine(Path.GetDirectoryName(configPath)!, configuration.Galaxies.KnownGalaxiesFile))); + _generator = new(configuration.Seed, configuration.CellSizeMeters, catalog, configuration.BlackHoles, _galaxies); _streamer = new(_generator); var initialSystem = configuration.ResolveStartup(catalog).System; - _homeSystem = catalog.GetById("sol"); + _homeSystem = _generator.Generate(catalog.GetById("sol").Coordinate); _homeEarth = _homeSystem.Planets.FirstOrDefault(p => p.Id.Equals("earth", StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidDataException("The known solar system must define an Earth planet with id 'earth'."); var startCoordinate = initialSystem.Coordinate; var cellCenter = UniversePosition.AtCellCenter(startCoordinate, _generator.CellSizeMeters); var startPosition = cellCenter.Add(new DVector3(0, 0, 4 * UniverseScale.AstronomicalUnitMeters), _generator.CellSizeMeters); _camera = new(startPosition, _generator.CellSizeMeters); + _camera.TravelLimitMeters = _galaxies.Settings.RegionSizeLightYears * UniverseScale.LightYearMeters * .25; _neighborhood = _streamer.Load(startCoordinate); _camera.SetInitialView(_neighborhood.ActiveSystem, configuration.Startup); _homeCameraPosition = _camera.Position; @@ -72,7 +79,7 @@ public sealed class UniverseApplication : IDisposable var options = WindowOptions.Default with { Title = "Universe | loading", - IsVisible = !Environment.GetCommandLineArgs().Any(a => a is "--graphics-report" or "--scene-report" or "--ocean-report" or "--cloud-report" or "--terrain-report" or "--menu-report" or "--terrain-perf-report" or "--black-hole-report"), + IsVisible = !Environment.GetCommandLineArgs().Any(a => a is "--graphics-report" or "--scene-report" or "--ocean-report" or "--cloud-report" or "--terrain-report" or "--menu-report" or "--terrain-perf-report" or "--black-hole-report" or "--galaxy-journey-report" or "--star-trails-report"), Size = new Vector2D(1440, 900), VSync = true, PreferredDepthBufferBits = 24, @@ -92,7 +99,18 @@ public sealed class UniverseApplication : IDisposable { var gl = _window.CreateOpenGL(); _input = _window.CreateInput(); - _renderer = new(gl, _window.FramebufferSize.X, _window.FramebufferSize.Y); + _renderer = new(gl, _window.FramebufferSize.X, _window.FramebufferSize.Y, _galaxies, _starMotionBlur); + if (Environment.GetCommandLineArgs().Contains("--star-trails-report")) + { + GraphicsReport.RunStarTrails(gl,_starMotionBlur,_window.FramebufferSize.X,_window.FramebufferSize.Y); + _window.Close(); return; + } + if (Environment.GetCommandLineArgs().Contains("--galaxy-journey-report")) + { + var journey = CreateGalaxyJourney(); + GraphicsReport.RunGalaxyJourney(gl, _renderer, _camera, _streamer, journey, _window.FramebufferSize.X, _window.FramebufferSize.Y); + _window.Close(); return; + } if (Environment.GetCommandLineArgs().Contains("--black-hole-report")) { var target = _streamer.FindNearestBlackHole(_camera.Position) @@ -138,6 +156,7 @@ public sealed class UniverseApplication : IDisposable if (_benchmark?.IsRunning == true) { + if (_benchmarkNeedsInitialFrame) return; _benchmark.Update(deltaSeconds, _camera); UpdateNeighborhood(); return; @@ -190,7 +209,10 @@ public sealed class UniverseApplication : IDisposable private void OnRender(double deltaSeconds) { _frameTime += (System.Math.Clamp(deltaSeconds, 0.001, 1) - _frameTime) * 0.08; - _benchmark?.RecordFrame(deltaSeconds); + // The first interval still includes menu preparation and GPU preloading. + // Render one initial frame before starting the measurement clock. + if (!_benchmarkNeedsInitialFrame) _benchmark?.RecordFrame(deltaSeconds); + if (_benchmark is GalaxyJourneyBenchmark { IsComplete: true, ReportDirectory: null } journey) journey.SaveReport(); if (_renderer is not null) { _renderer.DebugMode = _debugMode; @@ -200,6 +222,7 @@ public sealed class UniverseApplication : IDisposable _renderer?.DrawDebugOverlay(1 / _frameTime); if (_benchmark is { HasRun: true } benchmark) _renderer?.DrawBenchmarkOverlay(benchmark); if (_menuOpen) _renderer?.DrawMenu(_menuSelection, _debugMode); + _benchmarkNeedsInitialFrame = false; } private void UpdateNeighborhood() @@ -229,8 +252,8 @@ public sealed class UniverseApplication : IDisposable if (_menuOpen) { if (key == Key.Escape) SetMenu(false); - else if (key == Key.Up) _menuSelection = (_menuSelection + 5) % 6; - else if (key == Key.Down) _menuSelection = (_menuSelection + 1) % 6; + else if (key == Key.Up) _menuSelection = (_menuSelection + 6) % 7; + else if (key == Key.Down) _menuSelection = (_menuSelection + 1) % 7; else if (key is Key.Enter or Key.KeypadEnter) ActivateMenu(); return; } @@ -273,6 +296,16 @@ public sealed class UniverseApplication : IDisposable _camera.RequestOceanView(_neighborhood.NearbySystems.Append(_neighborhood.ActiveSystem)); else if (key == Key.F7) _camera.RequestCloudView(_neighborhood.NearbySystems.Append(_neighborhood.ActiveSystem)); + else if(key==Key.F8) + { + var galaxy=_galaxies.GalaxyAt(_camera.Position); + _camera.RequestCosmicView(galaxy.Center,galaxy.RadiusMeters,galaxy.Orientation,false,_neighborhood.NearbySystems.Append(_neighborhood.ActiveSystem)); + } + else if(key==Key.F9) + { + var nebula=_galaxies.GalaxyAt(_camera.Position).Nebulae.MinBy(n=>n.Center.RelativeTo(_camera.Position,_generator.CellSizeMeters).LengthSquared)!; + _camera.RequestCosmicView(nebula.Center,nebula.RadiusMeters,nebula.Orientation,true,_neighborhood.NearbySystems.Append(_neighborhood.ActiveSystem)); + } else if (key == Key.F1 && _mouse is not null) { _mouseCaptured = !_mouseCaptured; @@ -350,8 +383,16 @@ public sealed class UniverseApplication : IDisposable private void ActivateMenu() { - if (_menuSelection == 5) { _window.Close(); return; } + if (_menuSelection == 6) { _window.Close(); return; } if (_menuSelection == 2) { _debugMode = !_debugMode; return; } + if (_menuSelection == 5) + { + _debugMode = false; _player = null; + _benchmark = CreateGalaxyJourney(); + _benchmarkNeedsInitialFrame = true; + _camera.SetPlayerPose(_homeCameraPosition, _homeCameraForward, _homeCameraUp, resetMotionHistory: true); + UpdateNeighborhood(); SetMenu(false); return; + } if (_menuSelection is 3 or 4) { try @@ -375,6 +416,7 @@ public sealed class UniverseApplication : IDisposable private void StartBenchmark(bool unknownPlanet) { + _camera.RestoreDefaultFieldOfView(); var system = _homeSystem; var planet = _homeEarth; if (unknownPlanet) @@ -398,12 +440,24 @@ public sealed class UniverseApplication : IDisposable _debugMode = false; _player = null; - _benchmark = new(system, planet, _generator.CellSizeMeters, _camera, + _benchmark = new BenchmarkRunner(system, planet, _generator.CellSizeMeters, _camera, _homeCameraPosition, _homeCameraForward, _homeCameraUp); + _benchmarkNeedsInitialFrame = true; UpdateNeighborhood(); SetMenu(false); } + private GalaxyJourneyBenchmark CreateGalaxyJourney() + { + _camera.RestoreDefaultFieldOfView(); + var journey = new GalaxyJourneyBenchmark(_galaxies, _generator, _homeSystem, _homeEarth, + _homeCameraPosition, _homeCameraForward, _homeCameraUp); + journey.RenderEnvironment = new { Width = _window.FramebufferSize.X, Height = _window.FramebufferSize.Y, Gpu = _renderer?.HardwareName, Mode = "interactive-vsync", FieldOfViewDegrees = _camera.FieldOfViewRadians * 180 / System.Math.PI }; + foreach (var planet in _homeSystem.Planets) _renderer?.PreparePlanet(_homeSystem, planet); + foreach (var planet in journey.TargetSystem.Planets) _renderer?.PreparePlanet(journey.TargetSystem, planet); + return journey; + } + private void OnClosing() { _renderer?.Dispose(); diff --git a/src/Universe.Core/Configuration/EngineConfiguration.cs b/src/Universe.Core/Configuration/EngineConfiguration.cs index 0f10c1a..26e6032 100644 --- a/src/Universe.Core/Configuration/EngineConfiguration.cs +++ b/src/Universe.Core/Configuration/EngineConfiguration.cs @@ -24,6 +24,8 @@ public sealed record EngineConfiguration public string KnownSolarSystemsFile { get; init; } = "KnownSolarSystem/solar-systems.json"; public StartupConfiguration Startup { get; init; } = new(); public BlackHoleGenerationSettings BlackHoles { get; init; } = new(); + public GalaxySettings Galaxies { get; init; } = new(); + public StarMotionBlurSettings StarMotionBlur { get; init; } = new(); public double CellSizeMeters => CellSizeAu * UniverseScale.AstronomicalUnitMeters; public ulong Seed => UniverseSeed.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? ulong.Parse(UniverseSeed.AsSpan(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture) @@ -44,6 +46,8 @@ public sealed record EngineConfiguration { var settings = JsonSettings.Read(path); (settings.BlackHoles ?? throw new InvalidDataException("config.json: blackHoles cannot be null.")).Validate(); + (settings.Galaxies ?? throw new InvalidDataException("config.json: galaxies cannot be null.")).Validate(); + (settings.StarMotionBlur ?? throw new InvalidDataException("config.json: starMotionBlur cannot be null.")).Validate(); if (string.IsNullOrWhiteSpace(settings.UniverseSeed)) throw new InvalidDataException("config.json: universeSeed cannot be empty."); try { _ = settings.Seed; } catch (Exception error) when (error is FormatException or OverflowException or ArgumentNullException) @@ -57,7 +61,7 @@ public sealed record EngineConfiguration || !double.IsFinite(startup.DistanceInRadii) || startup.DistanceInRadii < 1.05 || !double.IsFinite(startup.LongitudeDegrees) || !double.IsFinite(startup.LatitudeDegrees) || System.Math.Abs(startup.LatitudeDegrees) > 90 || !double.IsFinite(startup.SpeedMetersPerSecond) || startup.SpeedMetersPerSecond < 0.1 - || startup.SpeedMetersPerSecond > UniverseScale.LightSpeedMetersPerSecond * 1_000_000 + || startup.SpeedMetersPerSecond > UniverseScale.LightSpeedMetersPerSecond * 1e15 || !double.IsFinite(startup.FieldOfViewDegrees) || startup.FieldOfViewDegrees is < 30 or > 120) throw new InvalidDataException("config.json: invalid startup target, planet hotkeys, view angles, distance or flight speed. Planet hotkeys must contain at most nine non-empty planet ids."); if (string.IsNullOrWhiteSpace(settings.KnownSolarSystemsFile)) throw new InvalidDataException("config.json: knownSolarSystemsFile is required."); diff --git a/src/Universe.Core/Generation/CelestialBodies.cs b/src/Universe.Core/Generation/CelestialBodies.cs index 73895b1..9641e85 100644 --- a/src/Universe.Core/Generation/CelestialBodies.cs +++ b/src/Universe.Core/Generation/CelestialBodies.cs @@ -65,4 +65,6 @@ public sealed record StarSystemData( { public string Id { get; init; } = $"procedural:{Coordinate.X},{Coordinate.Y},{Coordinate.Z}"; public string Name { get; init; } = "Proceduriellt solsystem"; + public string? GalaxyId { get; init; } + public string? NebulaId { get; init; } } diff --git a/src/Universe.Core/Generation/GalaxyUniverse.cs b/src/Universe.Core/Generation/GalaxyUniverse.cs new file mode 100644 index 0000000..ac1810b --- /dev/null +++ b/src/Universe.Core/Generation/GalaxyUniverse.cs @@ -0,0 +1,225 @@ +using System.Numerics; +using Universe.Core.Configuration; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.Core.Generation; + +public sealed record GalaxySettings +{ + public string KnownGalaxiesFile { get; init; } = "KnownGalaxies.json"; + public double RegionSizeLightYears { get; init; } = 1_000_000; + public double SystemSpacingLightYears { get; init; } = 4; + public int GalaxyStarBudget { get; init; } = 6000; + public int LocalStarBudget { get; init; } = 2600; + public int VolumeSteps { get; init; } = 40; + public double MinimumBenchmarkFps { get; init; } = 45; + public double MaximumBenchmarkFrameMilliseconds { get; init; } = 50; + public void Validate() + { + if (!double.IsFinite(RegionSizeLightYears) || RegionSizeLightYears is < 300_000 or > 100_000_000 + || !double.IsFinite(SystemSpacingLightYears) || SystemSpacingLightYears is < .01 or > 100 + || GalaxyStarBudget is < 256 or > 30000 || LocalStarBudget is < 128 or > 10000 + || VolumeSteps is < 16 or > 96 || !double.IsFinite(MinimumBenchmarkFps) || MinimumBenchmarkFps is < 1 or > 1000 + || !double.IsFinite(MaximumBenchmarkFrameMilliseconds) || MaximumBenchmarkFrameMilliseconds is < 1 or > 1000) + throw new InvalidDataException("Invalid galaxies settings in config.json."); + } +} + +public sealed record NebulaDefinition +{ + public string Id { get; init; } = "nebula"; + public double[] PositionLightYears { get; init; } = [26000, 0, 300]; + public double RadiusLightYears { get; init; } = 80; + public string? Texture { get; init; } +} +public sealed record GalaxyDefinition +{ + public string Id { get; init; } = "milky-way"; + public string Name { get; init; } = "Vintergatan"; + public long[] Region { get; init; } = [0, 0, 0]; + public double[] CenterOffsetLightYears { get; init; } = [-26000, 0, 0]; + public double RadiusLightYears { get; init; } = 50000; + public double ThicknessLightYears { get; init; } = 1800; + public double AxialTiltDegrees { get; init; } + public double RotationDegrees { get; init; } + public int SpiralArms { get; init; } = 4; + public string? Texture { get; init; } + public double DensityGamma { get; init; } = .7; + public NebulaDefinition[] Nebulae { get; init; } = []; +} +public sealed record KnownGalaxyDefinitions +{ + public GalaxyDefinition[] Galaxies { get; init; } = []; +} + +public sealed record NebulaData(string Id, UniversePosition Center, double RadiusMeters, PlanetOrientation Orientation, ulong Seed, RasterImage? Texture); + +public sealed record GalaxyData(string Id, string Name, GalaxyCoordinate Region, UniversePosition Center, double RadiusMeters, + double ThicknessMeters, PlanetOrientation Orientation, int SpiralArms, ulong Seed, double DensityGamma, RasterImage? Texture, + IReadOnlyList Nebulae) +{ + public double PlanarDensity(double x, double z) + { + var r = System.Math.Sqrt(x * x + z * z); + if (r >= 1) return 0; + double density; + if (Texture is { } map) + { + var u = System.Math.Clamp((x * .5 + .5) * (map.Width - 1), 0, map.Width - 1); + var v = System.Math.Clamp((.5 - z * .5) * (map.Height - 1), 0, map.Height - 1); + var ix = (int)u; var iy = (int)v; + double At(int px, int py) + { + var p = (py * map.Width + px) * 4; + return (map.Rgba[p] * .2126 + map.Rgba[p + 1] * .7152 + map.Rgba[p + 2] * .0722) / 255 * map.Rgba[p + 3] / 255; + } + var tx = u - ix; var ty = v - iy; + var a = At(ix, iy) * (1 - tx) + At(System.Math.Min(ix + 1, map.Width - 1), iy) * tx; + var b = At(ix, System.Math.Min(iy + 1, map.Height - 1)) * (1 - tx) + + At(System.Math.Min(ix + 1, map.Width - 1), System.Math.Min(iy + 1, map.Height - 1)) * tx; + density = a * (1 - ty) + b * ty; + } + else + { + var spiral = System.Math.Atan2(z, x) * SpiralArms - System.Math.Log(r + .05) * 5.8; + var arms = System.Math.Pow(.5 + .5 * System.Math.Cos(spiral), 5); + density = (.05 + arms * .8) * System.Math.Pow(1 - r, .7) + System.Math.Exp(-r * r * 65) * .8; + } + return System.Math.Pow(System.Math.Clamp(density, 0, 1), DensityGamma); + } + + public double Density(UniversePosition position, double cellSize) + { + var p = Orientation.ToBody(position.RelativeTo(Center, cellSize)); + var vertical = System.Math.Exp(-System.Math.Abs(p.Y) / ThicknessMeters); + return vertical < .001 ? 0 : PlanarDensity(p.X / RadiusMeters, p.Z / RadiusMeters) * vertical; + } +} + +/// Procedural macrocells and sparse deterministic stellar candidates; textures control real occupancy. +public sealed class GalaxyUniverse +{ + private readonly Dictionary _cache = []; + private readonly Dictionary _known; + private readonly string _root; + public ulong Seed { get; } + public double CellSize { get; } + public GalaxySettings Settings { get; } + public long RegionCells { get; } + public long SpacingCells { get; } + + public GalaxyUniverse(ulong seed, double cellSize, GalaxySettings settings, string catalogPath) + { + settings.Validate(); Seed = seed; CellSize = cellSize; Settings = settings; + RegionCells = checked((long)System.Math.Round(settings.RegionSizeLightYears * UniverseScale.LightYearMeters / cellSize)); + SpacingCells = System.Math.Max(2, checked((long)System.Math.Round(settings.SystemSpacingLightYears * UniverseScale.LightYearMeters / cellSize))); + _root = Path.GetDirectoryName(Path.GetFullPath(catalogPath))!; + _known = new(); + foreach (var definition in JsonSettings.Read(catalogPath).Galaxies) + { + if (definition.Region.Length != 3 || definition.CenterOffsetLightYears.Length != 3 || string.IsNullOrWhiteSpace(definition.Id) + || !double.IsFinite(definition.RadiusLightYears) || definition.RadiusLightYears <= 0 || definition.RadiusLightYears > settings.RegionSizeLightYears * .2 + || !double.IsFinite(definition.ThicknessLightYears) || definition.ThicknessLightYears <= 0 + || !double.IsFinite(definition.DensityGamma) || definition.DensityGamma is < .1 or > 8 + || definition.SpiralArms is < 1 or > 8 || definition.CenterOffsetLightYears.Any(v => !double.IsFinite(v))) + throw new InvalidDataException("Invalid galaxy in KnownGalaxies.json."); + _known.Add(new(definition.Region[0], definition.Region[1], definition.Region[2]), definition); + } + } + + public GalaxyCoordinate RegionAt(UniversePosition position) => GalaxyAddress.FromPosition(position, RegionCells).Region; + public GalaxyData GalaxyAt(UniversePosition position) => Get(RegionAt(position)); + + public GalaxyData Get(GalaxyCoordinate coordinate) + { + if (_cache.TryGetValue(coordinate, out var cached)) return cached; + var seed = Hash(new(coordinate.X, coordinate.Y, coordinate.Z), Seed ^ 0x47414C415859UL); + var random = new DeterministicRandom(seed); + _known.TryGetValue(coordinate, out var known); + var regionOrigin = new GalaxyAddress(coordinate, new(0, 0, 0), DVector3.Zero).ToPosition(RegionCells); + var offset = known is null ? new DVector3(random.NextDouble() - .5, random.NextDouble() - .5, random.NextDouble() - .5) * (Settings.RegionSizeLightYears * .15) + : new(known.CenterOffsetLightYears[0], known.CenterOffsetLightYears[1], known.CenterOffsetLightYears[2]); + var center = regionOrigin.Add(offset * UniverseScale.LightYearMeters, CellSize); + var radius = (known?.RadiusLightYears ?? (25000 + random.NextDouble() * 45000)) * UniverseScale.LightYearMeters; + var thickness = (known?.ThicknessLightYears ?? (800 + random.NextDouble() * 1800)) * UniverseScale.LightYearMeters; + var orientation = new PlanetOrientation(known?.AxialTiltDegrees ?? random.NextDouble() * 130 - 65, known?.RotationDegrees ?? random.NextDouble() * 360); + var id = known?.Id ?? $"galaxy:{coordinate.X}:{coordinate.Y}:{coordinate.Z}"; + var nebulae = new List(); + foreach (var n in known?.Nebulae ?? []) + { + if (n.PositionLightYears.Length != 3 || n.PositionLightYears.Any(v => !double.IsFinite(v)) || !double.IsFinite(n.RadiusLightYears) || n.RadiusLightYears <= 0) + throw new InvalidDataException("Invalid nebula in KnownGalaxies.json."); + var local = new DVector3(n.PositionLightYears[0], n.PositionLightYears[1], n.PositionLightYears[2]) * UniverseScale.LightYearMeters; + nebulae.Add(new(n.Id, center.Add(orientation.ToWorld(local), CellSize), n.RadiusLightYears * UniverseScale.LightYearMeters, + orientation, seed ^ (ulong)nebulae.Count, Load(n.Texture))); + } + for (var i = 0; i < 8; i++) + { + var r = .18 + random.NextDouble() * .6; var phi = random.NextDouble() * System.Math.Tau; + var local = new DVector3(System.Math.Cos(phi) * r * radius, (random.NextDouble() - .5) * thickness, System.Math.Sin(phi) * r * radius); + nebulae.Add(new($"{id}:nebula:{i}", center.Add(orientation.ToWorld(local), CellSize), (150 + random.NextDouble() * 700) * UniverseScale.LightYearMeters, + new(random.NextDouble() * 180, random.NextDouble() * 360), random.NextUInt64(), null)); + } + var galaxy = new GalaxyData(id, known?.Name ?? $"Galax {coordinate.X}:{coordinate.Y}:{coordinate.Z}", coordinate, center, radius, thickness, orientation, + known?.SpiralArms ?? random.NextInt(2, 6), seed, known?.DensityGamma ?? .7, Load(known?.Texture), nebulae); + if (_cache.Count >= 96) + { + var victim = _cache.Keys.FirstOrDefault(k => !_known.ContainsKey(k)); + _cache.Remove(victim); + } + _cache[coordinate] = galaxy; + return galaxy; + } + + private RasterImage? Load(string? path) => path is null ? null : RasterImage.Load(Path.GetFullPath(Path.Combine(_root, path))); + public IReadOnlyList VisibleGalaxies(UniversePosition position) + { + var r = RegionAt(position); var result = new List(27); + for (var z = -1; z <= 1; z++) for (var y = -1; y <= 1; y++) for (var x = -1; x <= 1; x++) + { + try { result.Add(Get(new(checked(r.X + x), checked(r.Y + y), checked(r.Z + z)))); } + catch (OverflowException) { } + } + return result; + } + + public static long FloorDiv(long a, long b) { var q = a / b; return a < 0 && a % b != 0 ? q - 1 : q; } + public SectorCoordinate StellarBlock(SectorCoordinate sector) => new(FloorDiv(sector.X, SpacingCells), FloorDiv(sector.Y, SpacingCells), FloorDiv(sector.Z, SpacingCells)); + public bool Candidate(GalaxyData galaxy, SectorCoordinate block, out SectorCoordinate sector) + { + var random = new DeterministicRandom(Hash(block, Seed ^ 0x5354415253UL)); + long Offset() => (long)((.12 + random.NextDouble() * .76) * SpacingCells); + sector = new(checked(block.X * SpacingCells + Offset()), checked(block.Y * SpacingCells + Offset()), checked(block.Z * SpacingCells + Offset())); + return random.NextDouble() < galaxy.Density(UniversePosition.AtCellCenter(sector, CellSize), CellSize); + } + public bool HasSystem(SectorCoordinate sector) + { + var position = UniversePosition.AtCellCenter(sector, CellSize); + return Candidate(GalaxyAt(position), StellarBlock(sector), out var candidate) && candidate == sector; + } + public IReadOnlyList NearbySystemCells(SectorCoordinate sector, int radius = 3, int maximum = 16) + { + var center = UniversePosition.AtCellCenter(sector, CellSize); var galaxy = GalaxyAt(center); var block = StellarBlock(sector); + var found = new List(); + for (var z = -radius; z <= radius; z++) for (var y = -radius; y <= radius; y++) for (var x = -radius; x <= radius; x++) + if (Candidate(galaxy, new(block.X + x, block.Y + y, block.Z + z), out var c)) found.Add(c); + // Sol is pinned and never rejected by the density map. + if (galaxy.Id == "milky-way" && center.RelativeTo(UniversePosition.AtCellCenter(new(), CellSize), CellSize).Length < SpacingCells * CellSize * (radius + 1)) found.Add(new()); + return found.Distinct().OrderBy(c => UniversePosition.AtCellCenter(c, CellSize).RelativeTo(center, CellSize).LengthSquared).Take(maximum).ToArray(); + } + public SectorCoordinate SelectSystem(GalaxyData galaxy) + { + var random = new DeterministicRandom(galaxy.Seed ^ 0x544152474554UL); + for (var i = 0; i < 4096; i++) + { + var local = new DVector3(random.NextDouble() * 1.4 - .7, (random.NextDouble() - .5) * galaxy.ThicknessMeters / galaxy.RadiusMeters, + random.NextDouble() * 1.4 - .7) * galaxy.RadiusMeters; + var block = StellarBlock(galaxy.Center.Add(galaxy.Orientation.ToWorld(local), CellSize).Sector); + if (Candidate(galaxy, block, out var sector)) return sector; + } + throw new InvalidOperationException("Galaxy density contains no selectable system. Check the texture brightness and alpha."); + } + public string NebulaFor(GalaxyData galaxy, UniversePosition position) => galaxy.Nebulae.MinBy(n => position.RelativeTo(n.Center, CellSize).LengthSquared)!.Id; + public static ulong Hash(SectorCoordinate p, ulong seed) => DeterministicRandom.Mix(DeterministicRandom.Mix(DeterministicRandom.Mix(seed ^ unchecked((ulong)p.X)) ^ unchecked((ulong)p.Y)) ^ unchecked((ulong)p.Z)); +} diff --git a/src/Universe.Core/Generation/StarMotionBlurSettings.cs b/src/Universe.Core/Generation/StarMotionBlurSettings.cs new file mode 100644 index 0000000..d54cc10 --- /dev/null +++ b/src/Universe.Core/Generation/StarMotionBlurSettings.cs @@ -0,0 +1,23 @@ +namespace Universe.Core.Generation; + +public sealed record StarMotionBlurSettings +{ + public bool Enabled { get; init; } = true; + public double ShutterFraction { get; init; } = .9; + public double MaximumTrailPixels { get; init; } = 180; + public double MinimumWidthPixels { get; init; } = 1.4; + public double MovingBrightness { get; init; } = 1.8; + public double HistoryResetSeconds { get; init; } = .25; + public double StarFadeSeconds { get; init; } = .3; + + public void Validate() + { + if (!double.IsFinite(ShutterFraction) || ShutterFraction is < 0 or > 2 + || !double.IsFinite(MaximumTrailPixels) || MaximumTrailPixels is < 1 or > 1024 + || !double.IsFinite(MinimumWidthPixels) || MinimumWidthPixels is < .5 or > 6 + || !double.IsFinite(MovingBrightness) || MovingBrightness is < 1 or > 4 + || !double.IsFinite(HistoryResetSeconds) || HistoryResetSeconds is < .05 or > 2 + || !double.IsFinite(StarFadeSeconds) || StarFadeSeconds is < .05 or > 2) + throw new InvalidDataException("config.json: invalid starMotionBlur settings."); + } +} diff --git a/src/Universe.Core/Generation/UniverseGenerator.cs b/src/Universe.Core/Generation/UniverseGenerator.cs index f189b83..e61c1ac 100644 --- a/src/Universe.Core/Generation/UniverseGenerator.cs +++ b/src/Universe.Core/Generation/UniverseGenerator.cs @@ -16,7 +16,7 @@ public sealed class UniverseGenerator [new(0.13f, 0.32f, 1f), new(0.16f, 0.42f, 1f), new(0.09f, 0.22f, 0.88f), new(0.80f, 0.48f, 0.25f)]; public UniverseGenerator(ulong universeSeed, double cellSizeMeters = UniverseScale.DefaultCellSizeMeters, KnownSolarSystemCatalog? knownSystems = null, - BlackHoleGenerationSettings? blackHoles = null) + BlackHoleGenerationSettings? blackHoles = null, GalaxyUniverse? galaxies = null) { if (!double.IsFinite(cellSizeMeters) || cellSizeMeters <= 0) throw new ArgumentOutOfRangeException(nameof(cellSizeMeters)); @@ -25,15 +25,26 @@ public sealed class UniverseGenerator _knownSystems = knownSystems; BlackHoles = blackHoles ?? new(); BlackHoles.Validate(); + Galaxies = galaxies; } public ulong UniverseSeed { get; } public double CellSizeMeters { get; } public BlackHoleGenerationSettings BlackHoles { get; } + public GalaxyUniverse? Galaxies { get; } public StarSystemData Generate(SectorCoordinate coordinate) { - if (_knownSystems is not null && _knownSystems.TryGet(coordinate, out var known)) return known; + StarSystemData Assign(StarSystemData s) + { + if (Galaxies is null) return s; + var position = UniversePosition.AtCellCenter(coordinate, CellSizeMeters); + var galaxy = Galaxies.GalaxyAt(position); + return s with { GalaxyId = galaxy.Id, NebulaId = Galaxies.NebulaFor(galaxy, position) }; + } + if (_knownSystems is not null && _knownSystems.TryGet(coordinate, out var known)) return Assign(known); + if (Galaxies is not null && !Galaxies.HasSystem(coordinate)) + return new(coordinate, SeedFor(coordinate), [], []) { Name = "Interstellar space" }; var systemSeed = SeedFor(coordinate); var random = new DeterministicRandom(systemSeed); var noiseOffset = ProceduralNoise.SeedOffset(systemSeed); @@ -125,7 +136,7 @@ public sealed class UniverseGenerator }); moonCount++; } - return new(coordinate, systemSeed, stars, planets); + return Assign(new(coordinate, systemSeed, stars, planets)); } public IEnumerable GenerateNeighborhood(SectorCoordinate center, int radius = 1) diff --git a/src/Universe.Core/World/GalaxyAddress.cs b/src/Universe.Core/World/GalaxyAddress.cs new file mode 100644 index 0000000..55e3397 --- /dev/null +++ b/src/Universe.Core/World/GalaxyAddress.cs @@ -0,0 +1,28 @@ +using Universe.Core.Math; + +namespace Universe.Core.World; + +public readonly record struct GalaxyCoordinate(long X, long Y, long Z); + +/// Integer macrocell, integer solar-system cell, then local metres. No huge global float position. +public readonly record struct GalaxyAddress(GalaxyCoordinate Region, SectorCoordinate SystemCell, DVector3 LocalMeters) +{ + public static GalaxyAddress FromPosition(UniversePosition position, long regionCells) + { + static (long Region, long Local) Split(long value, long size) + { + var wide = (Int128)value + size / 2; + var region = wide / size; + if (wide < 0 && wide % size != 0) region--; + return (checked((long)region), checked((long)((Int128)value - region * size))); + } + if (regionCells < 2) throw new ArgumentOutOfRangeException(nameof(regionCells)); + var x = Split(position.Sector.X, regionCells); var y = Split(position.Sector.Y, regionCells); var z = Split(position.Sector.Z, regionCells); + return new(new(x.Region, y.Region, z.Region), new(x.Local, y.Local, z.Local), position.LocalOffset); + } + + public UniversePosition ToPosition(long regionCells) => new(new( + checked((long)((Int128)Region.X * regionCells + SystemCell.X)), + checked((long)((Int128)Region.Y * regionCells + SystemCell.Y)), + checked((long)((Int128)Region.Z * regionCells + SystemCell.Z))), LocalMeters); +} diff --git a/src/Universe.Core/World/UniverseScale.cs b/src/Universe.Core/World/UniverseScale.cs index 9a9c09b..3eb6191 100644 --- a/src/Universe.Core/World/UniverseScale.cs +++ b/src/Universe.Core/World/UniverseScale.cs @@ -4,6 +4,7 @@ public static class UniverseScale { public const double AstronomicalUnitMeters = 149_597_870_700d; public const double LightSpeedMetersPerSecond = 299_792_458d; + public const double LightYearMeters = 9_460_730_472_580_800d; public const double SolarRadiusMeters = 695_700_000d; public const double EarthRadiusMeters = 6_371_000d; public const double DefaultCellSizeMeters = 100d * AstronomicalUnitMeters; diff --git a/src/Universe.Core/World/UniverseStreamer.cs b/src/Universe.Core/World/UniverseStreamer.cs index 6bce67b..68cae7a 100644 --- a/src/Universe.Core/World/UniverseStreamer.cs +++ b/src/Universe.Core/World/UniverseStreamer.cs @@ -8,10 +8,20 @@ public sealed record UniverseNeighborhood(StarSystemData ActiveSystem, IReadOnly public sealed class UniverseStreamer(UniverseGenerator generator) { private readonly Dictionary _cache = []; + private SectorCoordinate? _nearBlock; + private IReadOnlyList _nearSystems = []; public (StarSystemData System, StarData Hole)? FindNearestBlackHole(UniversePosition position) { if (generator.BlackHoles.OccurrenceChance <= 0) return null; + if (generator.Galaxies is { } galaxies) + { + return galaxies.NearbySystemCells(position.Sector, 8, 2048).Select(GetOrCreate) + .SelectMany(s => s.Stars.Where(h => h.IsBlackHole).Select(h => ((StarSystemData System, StarData Hole)?)(s, h))) + .OrderBy(b => UniversePosition.AtCellCenter(b!.Value.System.Coordinate, generator.CellSizeMeters) + .Add(b.Value.Hole.PositionMeters, generator.CellSizeMeters).RelativeTo(position, generator.CellSizeMeters).LengthSquared) + .FirstOrDefault(); + } (StarSystemData System, StarData Hole)? nearest = null; var bestDistance = double.PositiveInfinity; var cell = generator.CellSizeMeters; @@ -44,6 +54,21 @@ public sealed class UniverseStreamer(UniverseGenerator generator) public UniverseNeighborhood Load(SectorCoordinate activeCoordinate) { var active = GetOrCreate(activeCoordinate); + if (generator.Galaxies is { } galaxies) + { + var block = galaxies.StellarBlock(activeCoordinate); + if (_nearBlock != block) + { + _nearSystems = galaxies.NearbySystemCells(activeCoordinate).Select(GetOrCreate).ToArray(); + _nearBlock = block; + } + if (_cache.Count > 96) + { + var keep = _nearSystems.Select(s => s.Coordinate).Append(activeCoordinate).ToHashSet(); + foreach (var key in _cache.Keys.ToArray()) if (!keep.Contains(key)) _cache.Remove(key); + } + return new(active, _nearSystems.Where(s => s.Coordinate != activeCoordinate).ToArray()); + } var nearby = new List(26); for (var z = -1; z <= 1; z++) for (var y = -1; y <= 1; y++) @@ -71,6 +96,7 @@ public sealed class UniverseStreamer(UniverseGenerator generator) if (!_cache.TryGetValue(coordinate, out var system)) { system = generator.Generate(coordinate); + if (system.Stars.Count == 0) return system; _cache.Add(coordinate, system); } return system; diff --git a/tests/Universe.Tests/GalaxyTests.cs b/tests/Universe.Tests/GalaxyTests.cs new file mode 100644 index 0000000..c7c69fd --- /dev/null +++ b/tests/Universe.Tests/GalaxyTests.cs @@ -0,0 +1,84 @@ +using Universe.App.Runtime; +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.Tests; + +public sealed class GalaxyTests +{ + private static GalaxyUniverse Create() => new(0x554E495645525345UL,UniverseScale.DefaultCellSizeMeters,new(),Path.Combine(AppContext.BaseDirectory,"KnownGalaxies.json")); + [Fact] + public void HierarchicalAddressKeepsLocalMetresAtRemoteGalaxyCoordinates() + { + var universe=Create(); + var address=new GalaxyAddress(new(1_000_000_000,-1_000_000_000,500),new(-53,21,700),new(456.75,901.125,18)); + var position=address.ToPosition(universe.RegionCells); + Assert.Equal(address,GalaxyAddress.FromPosition(position,universe.RegionCells)); + var moved=position.Add(new(.125,.25,.5),universe.CellSize); + Assert.Equal(new DVector3(.125,.25,.5),moved.RelativeTo(position,universe.CellSize)); + var negative=new UniversePosition(new(-universe.RegionCells/2-1,0,0),DVector3.Zero); + Assert.Equal(-1,GalaxyAddress.FromPosition(negative,universe.RegionCells).Region.X); + } + [Fact] + public void TextureBrightnessAndAlphaControlActualSystemOccupancy() + { + var universe=Create();var galaxy=universe.Get(new()); + var white=new RasterImage(2,2,Enumerable.Repeat((byte)255,16).ToArray()); + var transparent=white.Rgba.ToArray();for(var i=3;i<16;i+=4)transparent[i]=0; + var bright=galaxy with {Texture=white,Center=UniversePosition.AtCellCenter(new(),universe.CellSize)}; + var dark=bright with {Texture=new(2,2,new byte[16])}; + var alpha=bright with {Texture=new(2,2,transparent)}; + Assert.Equal(1,bright.PlanarDensity(0,0),10); + Assert.Equal(0,dark.PlanarDensity(0,0));Assert.Equal(0,alpha.PlanarDensity(0,0)); + var count=0; + for(var x=-3;x<=3;x++)for(var z=-3;z<=3;z++) + { + if(universe.Candidate(bright,new(x,0,z),out _))count++; + Assert.False(universe.Candidate(dark,new(x,0,z),out _)); + Assert.False(universe.Candidate(alpha,new(x,0,z),out _)); + } + Assert.True(count>20); + } + [Fact] + public void GeneratedGalaxiesAreDeterministicAndSystemsHaveParents() + { + var a=Create();var b=Create();var coordinate=new GalaxyCoordinate(1,0,0); + var first=a.Get(coordinate);var second=b.Get(coordinate); + Assert.Equal(first.Center,second.Center);Assert.Equal(first.Seed,second.Seed); + var sector=a.SelectSystem(first);Assert.Equal(sector,b.SelectSystem(second)); + var generator=new UniverseGenerator(a.Seed,galaxies:a); + var system=generator.Generate(sector); + Assert.NotEmpty(system.Stars);Assert.NotEmpty(system.Planets); + Assert.Equal(first.Id,system.GalaxyId);Assert.NotNull(system.NebulaId); + Assert.Empty(generator.Generate(first.Center.Add(new(first.RadiusMeters*2,0,0),a.CellSize).Sector).Stars); + } + [Fact] + public void JourneyVisitsBothGalaxiesLandsAtEyeHeightAndDetectsFrameSpikes() + { + var galaxies=Create(); + var home=new UniverseGenerator(123).Generate(new()); + var earth=home.Planets[0] with {Id="earth"};home=home with {Planets=[earth]}; + var body=UniversePosition.AtCellCenter(home.Coordinate,galaxies.CellSize).Add(earth.PositionMeters,galaxies.CellSize); + var radial=new DVector3(1,.2,.1).Normalized();var start=body.Add(radial*earth.RadiusMeters*3,galaxies.CellSize); + var generator=new UniverseGenerator(galaxies.Seed,galaxies:galaxies); + var journey=new GalaxyJourneyBenchmark(galaxies,generator,home,earth,start,-radial,new(0,1,0)); + var camera=new FlyCamera(start,galaxies.CellSize);var streamer=new UniverseStreamer(generator);var visitedTarget=false; + while(journey.IsRunning) + { + journey.Update(.25,camera); + var loaded=streamer.Load(camera.Position.Sector); + if(loaded.ActiveSystem.Coordinate==journey.TargetSystem.Coordinate && loaded.ActiveSystem.Planets.Count>0)visitedTarget=true; + if(journey.Phase=="GROUND WALK") + { + var targetBody=UniversePosition.AtCellCenter(journey.TargetSystem.Coordinate,galaxies.CellSize).Add(journey.TargetPlanet.PositionMeters,galaxies.CellSize); + var delta=camera.Position.RelativeTo(targetBody,galaxies.CellSize); + Assert.InRange(delta.Length-TerrainHeight.Sample(journey.TargetPlanet,delta),1.79,1.81); + } + journey.RecordFrame(journey.ElapsedSeconds is >30 and <31?.1:.01); + } + Assert.True(visitedTarget);Assert.Equal(7,journey.VisitedPhases.Count);Assert.Equal(start,camera.Position); + Assert.False(journey.PerformancePassed); + Assert.Contains(journey.GraphSamples,fps=>fps<20); + } +} diff --git a/tests/Universe.Tests/StarMotionTests.cs b/tests/Universe.Tests/StarMotionTests.cs new file mode 100644 index 0000000..b44cbea --- /dev/null +++ b/tests/Universe.Tests/StarMotionTests.cs @@ -0,0 +1,42 @@ +using Universe.App.Runtime; +using Universe.Core.Generation; +using Universe.Core.Math; +using Universe.Core.World; + +namespace Universe.Tests; + +public sealed class StarMotionTests +{ + [Fact] + public void HistoryRetainsSmallMovementAtLargeCoordinatesAndResetsOnCameraCuts() + { + var cell=UniverseScale.DefaultCellSizeMeters; + var position=UniversePosition.AtCellCenter(new(600_000_000_000_000_000,-500,200),cell); + var camera=new FlyCamera(position,cell); + var history=new StarMotionHistory();var settings=new StarMotionBlurSettings(); + history.Advance(camera,1440,900,1d/60,settings); + Assert.False(history.Valid); + camera.SetPlayerPose(position.Add(new(.125,.25,.5),cell),camera.Forward,camera.Up); + history.Advance(camera,1440,900,1d/60,settings); + Assert.True(history.Valid); + Assert.Equal(new DVector3(.125,.25,.5),history.Current.Position.RelativeTo(history.Previous.Position,cell)); + camera.SetPlayerPose(position.Add(new(1e20,0,0),cell),camera.Forward,camera.Up,resetMotionHistory:true); + history.Advance(camera,1440,900,1d/60,settings); + Assert.False(history.Valid); + Assert.Equal(history.Current,history.Previous); + } + + [Fact] + public void PauseResizeAndLongFramesCannotSmearUnrelatedFramesTogether() + { + var camera=new FlyCamera(UniversePosition.AtCellCenter(new(),UniverseScale.DefaultCellSizeMeters),UniverseScale.DefaultCellSizeMeters); + var history=new StarMotionHistory();var settings=new StarMotionBlurSettings(); + history.Advance(camera,1440,900,1d/60,settings); + history.Advance(camera,1440,900,1d/60,settings);Assert.True(history.Valid); + history.Advance(camera,1920,1080,1d/60,settings);Assert.False(history.Valid); + history.Advance(camera,1920,1080,0,settings);Assert.False(history.Valid); + history.Advance(camera,1920,1080,.5,settings);Assert.False(history.Valid); + history.Advance(camera,1920,1080,1d/60,settings);Assert.True(history.Valid); + history.Advance(camera,1920,1080,1d/60,settings with {Enabled=false});Assert.False(history.Valid); + } +} diff --git a/tests/Universe.Tests/Universe.Tests.csproj b/tests/Universe.Tests/Universe.Tests.csproj index 37554f5..318c732 100644 --- a/tests/Universe.Tests/Universe.Tests.csproj +++ b/tests/Universe.Tests/Universe.Tests.csproj @@ -21,6 +21,9 @@ + + +